From cb24638ec9d8e5bc13317641d1f49b504eac9dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Mon, 2 Sep 2024 10:15:04 +0200 Subject: [PATCH] lib/api: Correct ordering of Accept-Language codes by weight (fixes #9670) (#9671) The preference for languages in the Accept-Language header field should not be deduced from the listed order, but from the passed "quality values", according to the HTTP specification: https://httpwg.org/specs/rfc9110.html#field.accept-language This implements the parsing of q=values and ordering within the API backend, to not complicate things further in the GUI code. Entries with invalid (unparseable) quality values are discarded completely. * gui: Fix API endpoint in comment. --- gui/default/syncthing/core/localeService.js | 6 ++--- lib/api/api.go | 26 +++++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/gui/default/syncthing/core/localeService.js b/gui/default/syncthing/core/localeService.js index ed4ae8d02..94cfb62b4 100644 --- a/gui/default/syncthing/core/localeService.js +++ b/gui/default/syncthing/core/localeService.js @@ -74,9 +74,9 @@ angular.module('syncthing.core') } matching = _availableLocales.filter(function (possibleLang) { - // The langs returned by the /svc/langs call will be in lower - // case. We compare to the lowercase version of the language - // code we have as well. + // The langs returned by the /rest/svc/langs call will be in + // lower case. We compare to the lowercase version of the + // language code we have as well. possibleLang = possibleLang.toLowerCase(); if (possibleLang.indexOf(browserLang) !== 0) { // Prefix does not match diff --git a/lib/api/api.go b/lib/api/api.go index f25ec4e1f..59adad52d 100644 --- a/lib/api/api.go +++ b/lib/api/api.go @@ -1483,11 +1483,33 @@ func (*service) getDeviceID(w http.ResponseWriter, r *http.Request) { func (*service) getLang(w http.ResponseWriter, r *http.Request) { lang := r.Header.Get("Accept-Language") - var langs []string + var weights = make(map[string]float64) for _, l := range strings.Split(lang, ",") { parts := strings.SplitN(l, ";", 2) - langs = append(langs, strings.ToLower(strings.TrimSpace(parts[0]))) + code := strings.ToLower(strings.TrimSpace(parts[0])) + weights[code] = 1.0 + if len(parts) < 2 { + continue + } + weight := strings.ToLower(strings.TrimSpace(parts[1])) + if !strings.HasPrefix(weight, "q=") { + continue + } + if q, err := strconv.ParseFloat(weight[2:], 32); err != nil { + // Completely dismiss entries with invalid weight + delete(weights, code) + } else { + weights[code] = q + } } + var langs = make([]string, 0, len(weights)) + for code := range weights { + langs = append(langs, code) + } + // Reorder by descending q value + sort.SliceStable(langs, func(i, j int) bool { + return weights[langs[i]] > weights[langs[j]] + }) sendJSON(w, langs) }