From 927c12574a99559d06e30e9a9553ecfdc4f6f422 Mon Sep 17 00:00:00 2001 From: Veloman Yunkan Date: Sat, 9 Apr 2022 13:32:34 +0400 Subject: [PATCH] Preliminary support for Accept-Language: header In the absence of the "userlang" query parameter in the URL, the value of the "Accept-Language" header is used. However, it is assumed that "Accept-Language" specifies a single language (rather than a comma separated list of languages possibly weighted with quality values). Example: Accept-Language: fr // should work Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 // The requested language will be considered to be // "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5". // The i18n code will fail to find resources for such a language // and will use the default "en" instead. --- src/server/request_context.cpp | 10 ++++- test/server.cpp | 74 +++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/server/request_context.cpp b/src/server/request_context.cpp index 5ac9fe8dd..0946b123b 100644 --- a/src/server/request_context.cpp +++ b/src/server/request_context.cpp @@ -195,7 +195,15 @@ std::string RequestContext::get_query() const { std::string RequestContext::get_user_language() const { - return get_optional_param("userlang", "en"); + try { + return get_argument("userlang"); + } catch(const std::out_of_range&) {} + + try { + return get_header("Accept-Language"); + } catch(const std::out_of_range&) {} + + return "en"; } } diff --git a/test/server.cpp b/test/server.cpp index b724ca41c..2fa31f203 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -57,7 +57,6 @@ std::string removeEOLWhitespaceMarkers(const std::string& s) return std::regex_replace(s, pattern, ""); } - class ZimFileServer { public: // types @@ -879,6 +878,79 @@ TEST_F(ServerTest, 500) EXPECT_EQ(r->body, expectedBody); } +TEST_F(ServerTest, UserLanguageControl) +{ + struct TestData + { + const std::string url; + const std::string acceptLanguageHeader; + const std::string expectedH1; + + operator TestContext() const + { + return TestContext{ + {"url", url}, + {"acceptLanguageHeader", acceptLanguageHeader}, + }; + } + }; + + const TestData testData[] = { + { + /*url*/ "/ROOT/zimfile/invalid-article", + /*Accept-Language:*/ "", + /* expected

*/ "Not Found" + }, + { + /*url*/ "/ROOT/zimfile/invalid-article?userlang=en", + /*Accept-Language:*/ "", + /* expected

*/ "Not Found" + }, + { + /*url*/ "/ROOT/zimfile/invalid-article?userlang=hy", + /*Accept-Language:*/ "", + /* expected

*/ "Սխալ հասցե" + }, + { + /*url*/ "/ROOT/zimfile/invalid-article", + /*Accept-Language:*/ "*", + /* expected

*/ "Not Found" + }, + { + /*url*/ "/ROOT/zimfile/invalid-article", + /*Accept-Language:*/ "hy", + /* expected

*/ "Սխալ հասցե" + }, + { + // userlang query parameter takes precedence over Accept-Language + /*url*/ "/ROOT/zimfile/invalid-article?userlang=en", + /*Accept-Language:*/ "hy", + /* expected

*/ "Not Found" + }, + { + // The value of the Accept-Language header is not currently parsed. + // In case of a comma separated list of languages (optionally weighted + // with quality values) the default (en) language is used instead. + /*url*/ "/ROOT/zimfile/invalid-article", + /*Accept-Language:*/ "hy;q=0.9, en;q=0.2", + /* expected

*/ "Not Found" + }, + }; + + const std::regex h1Regex("

(.+)

"); + for ( const auto& t : testData ) { + std::smatch h1Match; + Headers headers; + if ( !t.acceptLanguageHeader.empty() ) { + headers.insert({"Accept-Language", t.acceptLanguageHeader}); + } + const auto r = zfs1_->GET(t.url.c_str(), headers); + std::regex_search(r->body, h1Match, h1Regex); + const std::string h1(h1Match[1]); + EXPECT_EQ(h1, t.expectedH1) << t; + } +} + TEST_F(ServerTest, RandomPageRedirectsToAnExistingArticle) { auto g = zfs1_->GET("/ROOT/random?content=zimfile");