From c5c0dc4507f4b284d1a765d112f92f32245f80bf Mon Sep 17 00:00:00 2001 From: afrmtbl Date: Sun, 1 Aug 2021 17:54:27 -0400 Subject: [PATCH] Implement 'Accept-Language' header parsing --- src/invidious/helpers/utils.cr | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 6ee07d7a..35b8ea0c 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -471,3 +471,81 @@ def fetch_random_instance return filtered_instance_list.sample(1)[0] end + +struct LanguageEntry + class InvalidLanguageEntry < Exception + end + + property tag, subtags, quality : Float64 + + def initialize(@tag : String, @subtags : Array(String), @quality : Float64) + @quality = @quality.clamp(0.0, 1.0) + end + + def to_s(io) + io << tag + unless subtags.empty? + io << '-' << @subtags.join '-' + end + end + + def self.from_string(language_str : String) : self + range_and_quality = language_str.split ';' + + raise LanguageEntry::InvalidLanguageEntry.new if range_and_quality.empty? + + if range_and_quality[1]? + quality = parse_quality(range_and_quality[1]) + else + quality = 1.00 + end + + language_range = range_and_quality[0] + tags = language_range.split '-' + + language_tag = tags[0] + subtags = tags[1..] + + return LanguageEntry.new(language_tag, subtags, quality) + end + + private def self.parse_quality(quality_str : String) : Float64 + parts = quality_str.split "=" + + begin + return parts[1].to_f64.clamp(0.00, 1.00) + rescue + raise LanguageEntry::InvalidLanguageEntry.new("'" + quality_str + "'" + " is not a valid quality") + end + end +end + +alias LanguageEntries = Array(LanguageEntry) + +def parse_accept_language_header(header : String) : LanguageEntries + return [] of LanguageEntry if header.empty? + + results = [] of LanguageEntry + + header.split ',' do |lang| + begin + results.push LanguageEntry.from_string(lang) + rescue LanguageEntry::InvalidLanguageEntry + # Skip invalid language entry strings + end + end + + results.sort_by! { |lang| -lang.quality } + + return results +end + +# Alternate matching schemes at https://datatracker.ietf.org/doc/html/rfc4647#section-3 +# Performs a simple exact match search for now +def first_language_match(available_languages : Set(String), preferred_languages : LanguageEntries) : String? + first_lang = preferred_languages.find do |lang| + available_languages.includes? lang.to_s + end + + first_lang.try &.to_s +end