Merge pull request #997 from kiwix/cookieless_user_language_control

This commit is contained in:
Matthieu Gautier 2023-09-11 14:04:48 +02:00 committed by GitHub
commit bde737f63b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 70 additions and 126 deletions

View File

@ -754,7 +754,8 @@ std::unique_ptr<Response> InternalServer::handle_viewer_settings(const RequestCo
const kainjow::mustache::object data{
{"enable_toolbar", m_withTaskbar ? "true" : "false" },
{"enable_link_blocking", m_blockExternalLinks ? "true" : "false" },
{"enable_library_button", m_withLibraryButton ? "true" : "false" }
{"enable_library_button", m_withLibraryButton ? "true" : "false" },
{"default_user_language", request.get_user_language() }
};
return ContentResponse::build(*this, RESOURCE::templates::viewer_settings_js, data, "application/javascript; charset=utf-8");
}

View File

@ -202,21 +202,12 @@ std::string RequestContext::get_user_language() const
return userlang.lang;
}
bool RequestContext::user_language_comes_from_cookie() const
{
return userlang.selectedBy == UserLanguage::SelectorKind::COOKIE;
}
RequestContext::UserLanguage RequestContext::determine_user_language() const
{
try {
return {UserLanguage::SelectorKind::QUERY_PARAM, get_argument("userlang")};
} catch(const std::out_of_range&) {}
try {
return {UserLanguage::SelectorKind::COOKIE, cookies.at("userlang")};
} catch(const std::out_of_range&) {}
try {
const std::string acceptLanguage = get_header("Accept-Language");
const auto userLangPrefs = parseUserLanguagePreferences(acceptLanguage);

View File

@ -119,15 +119,12 @@ class RequestContext {
std::string get_user_language() const;
std::string get_requested_format() const;
bool user_language_comes_from_cookie() const;
private: // types
struct UserLanguage
{
enum SelectorKind
{
QUERY_PARAM,
COOKIE,
ACCEPT_LANGUAGE_HEADER,
DEFAULT
};

View File

@ -387,13 +387,6 @@ MHD_Result Response::send(const RequestContext& request, MHD_Connection* connect
MHD_add_response_header(response, p.first.c_str(), p.second.c_str());
}
if ( ! request.user_language_comes_from_cookie() ) {
const std::string cookie = "userlang=" + request.get_user_language()
+ ";Path=" + request.get_root_path()
+ ";Max-Age=31536000";
MHD_add_response_header(response, MHD_HTTP_HEADER_SET_COOKIE, cookie.c_str());
}
if (m_returnCode == MHD_HTTP_OK && m_byteRange.kind() == ByteRange::RESOLVED_PARTIAL_CONTENT)
m_returnCode = MHD_HTTP_PARTIAL_CONTENT;

View File

@ -69,30 +69,19 @@ function $t(msgId, params={}) {
}
}
function getCookie(cookieName) {
const name = cookieName + "=";
let result;
decodeURIComponent(document.cookie).split('; ').forEach(val => {
if (val.indexOf(name) === 0) {
result = val.substring(name.length);
}
});
return result;
}
const DEFAULT_UI_LANGUAGE = 'en';
Translations.load(DEFAULT_UI_LANGUAGE, /*asDefault=*/true);
function getUserLanguage() {
return new URLSearchParams(window.location.search).get('userlang')
|| getCookie('userlang')
|| window.localStorage.getItem('userlang')
|| viewerSettings.defaultUserLanguage
|| DEFAULT_UI_LANGUAGE;
}
function setUserLanguage(lang, callback) {
setPermanentGlobalCookie('userlang', lang);
window.localStorage.setItem('userlang', lang);
Translations.load(lang);
Translations.whenReady(callback);
}

View File

@ -46,7 +46,7 @@
window.modalUILanguageSelector.close();
const s = document.getElementById("ui_language");
const lang = s.options[s.selectedIndex].value;
setPermanentGlobalCookie('userlang', lang);
localStorage.setItem('userlang', lang);
window.location.reload();
}
@ -585,11 +585,6 @@
setInterval(updateNavVisibilityState, 250);
};
// required by i18n.js:setUserLanguage()
window.setPermanentGlobalCookie = function(name, value) {
document.cookie = `${name}=${value};path=${root};max-age=31536000`;
}
window.onload = () => { setUserLanguage(getUserLanguage(), onload); }
})();

View File

@ -549,7 +549,3 @@ function finishViewerSetupOnceTranslationsAreLoaded()
viewerSetupComplete = true;
}
function setPermanentGlobalCookie(name, value) {
document.cookie = `${name}=${value};path=${root};max-age=31536000`;
}

View File

@ -37,6 +37,7 @@
src: url("{{root}}/skin/fonts/Roboto.ttf?KIWIXCACHEID") format("truetype");
}
</style>
<script type="text/javascript" src="./viewer_settings.js"></script>
<script type="module" src="{{root}}/skin/i18n.js?KIWIXCACHEID" defer></script>
<script type="text/javascript" src="{{root}}/skin/languages.js?KIWIXCACHEID" defer></script>
<script src="{{root}}/skin/isotope.pkgd.min.js?KIWIXCACHEID" defer></script>

View File

@ -1,5 +1,6 @@
const viewerSettings = {
toolbarEnabled: {{enable_toolbar}},
linkBlockingEnabled: {{enable_link_blocking}},
libraryButtonEnabled: {{enable_library_button}}
libraryButtonEnabled: {{enable_library_button}},
defaultUserLanguage: "{{default_user_language}}"
}

View File

@ -34,12 +34,12 @@
<div class="kiwix" style="display:none" id="kiwixtoolbarwrapper">
<div id="kiwixtoolbar" class="ui-widget-header">
<div class="kiwix_centered">
<a id="uiLanguageSelectorButton"
onclick="window.modalUILanguageSelector.show()"
<a onclick="window.modalUILanguageSelector.show()"
alt="Select UI language"
aria-label="Select UI language"
title="Select UI language">
<img src="./skin/langSelector.svg?KIWIXCACHEID">
<img id="uiLanguageSelectorButton"
src="./skin/langSelector.svg?KIWIXCACHEID">
</a>
<div class="kiwix_searchform">
<form class="kiwixsearch" method="GET" action="javascript:performSearch()" id="kiwixsearchform">

View File

@ -59,11 +59,11 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/css/autoComplete.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/css/autoComplete.css?cacheid=08951e06" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/i18n.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=2cf0f8c5" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.css?cacheid=e4d76d16" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/index.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=07b06fca" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=e1b1ae55" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/isotope.pkgd.min.js" },
@ -73,7 +73,7 @@ const ResourceCollection resources200Compressible{
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/taskbar.css?cacheid=bbdaf425" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/viewer.js" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=bb748367" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=201653b8" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf" },
{ STATIC_CONTENT, "/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837" },
{ DYNAMIC_CONTENT, "/ROOT%23%3F/skin/fonts/Roboto.ttf" },
@ -284,11 +284,11 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/index.css?cacheid=e4d76d16"
<meta name="msapplication-config" content="/ROOT%23%3F/skin/favicon/browserconfig.xml?cacheid=f29a7c4a">
src: url("/ROOT%23%3F/skin/fonts/Poppins.ttf?cacheid=af705837") format("truetype");
src: url("/ROOT%23%3F/skin/fonts/Roboto.ttf?cacheid=84d10248") format("truetype");
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=2cf0f8c5" defer></script>
<script type="module" src="/ROOT%23%3F/skin/i18n.js?cacheid=6a8c6fb2" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/languages.js?cacheid=648526e1" defer></script>
<script src="/ROOT%23%3F/skin/isotope.pkgd.min.js?cacheid=2e48d392" defer></script>
<script src="/ROOT%23%3F/skin/iso6391To3.js?cacheid=ecde2bb3"></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=07b06fca" defer></script>
<script type="text/javascript" src="/ROOT%23%3F/skin/index.js?cacheid=e1b1ae55" defer></script>
<img src="/ROOT%23%3F/skin/feed.svg?cacheid=055b333f"
<img src="/ROOT%23%3F/skin/langSelector.svg?cacheid=00b59961"
)EXPECTEDRESULT"
@ -310,12 +310,12 @@ R"EXPECTEDRESULT( <img src="${root}/skin/download
/* url */ "/ROOT%23%3F/viewer",
R"EXPECTEDRESULT( <link type="text/css" href="./skin/taskbar.css?cacheid=bbdaf425" rel="Stylesheet" />
<link type="text/css" href="./skin/css/autoComplete.css?cacheid=08951e06" rel="Stylesheet" />
<script type="module" src="./skin/i18n.js?cacheid=2cf0f8c5" defer></script>
<script type="module" src="./skin/i18n.js?cacheid=6a8c6fb2" defer></script>
<script type="text/javascript" src="./skin/languages.js?cacheid=648526e1" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=bb748367" defer></script>
<script type="text/javascript" src="./skin/viewer.js?cacheid=201653b8" defer></script>
<script type="text/javascript" src="./skin/autoComplete.min.js?cacheid=1191aaaf"></script>
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";
<img src="./skin/langSelector.svg?cacheid=00b59961">
src="./skin/langSelector.svg?cacheid=00b59961">
<label for="kiwix_button_show_toggle"><img src="./skin/caret.png?cacheid=22b942b4" alt=""></label>
src="./skin/blank.html?cacheid=6b1fa032" title="ZIM content" width="100%"
)EXPECTEDRESULT"
@ -1130,7 +1130,6 @@ TEST_F(ServerTest, UserLanguageControl)
const std::string url;
const std::string acceptLanguageHeader;
const char* const requestCookie; // Cookie: header of the request
const char* const responseSetCookie; // Set-Cookie: header of the response
const std::string expectedH1;
operator TestContext() const
@ -1157,7 +1156,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@ -1165,7 +1163,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@ -1173,7 +1170,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=test",
/*Accept-Language:*/ "",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
@ -1181,7 +1177,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "*",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
@ -1189,71 +1184,20 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is respected",
"userlang cookie is ignored",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "anothercookie=123; userlang=test",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test; anothercookie=abc",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"userlang cookie is correctly parsed",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "cookie1=abc; userlang=test; cookie2=xyz",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
"Multiple userlang cookies are not a problem",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "cookie1=abc; userlang=en; userlang=test; cookie2=xyz",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
/* expected <h1> */ "Not Found"
},
{
"userlang query parameter takes precedence over Accept-Language",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
"userlang query parameter takes precedence over its cookie counterpart",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article?userlang=en",
/*Accept-Language:*/ "",
/*Request Cookie:*/ "userlang=test",
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
{
"userlang in cookies takes precedence over Accept-Language",
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test",
/*Request Cookie:*/ "userlang=en",
/*Response Set-Cookie:*/ NO_COOKIE,
/* expected <h1> */ "Not Found"
},
{
@ -1263,7 +1207,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.9, en;q=0.2",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=test;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "[I18N TESTING] Content not found, but at least the server is alive"
},
{
@ -1273,7 +1216,6 @@ TEST_F(ServerTest, UserLanguageControl)
/*url*/ "/ROOT%23%3F/content/zimfile/invalid-article",
/*Accept-Language:*/ "test;q=0.2, en;q=0.9",
/*Request Cookie:*/ NO_COOKIE,
/*Response Set-Cookie:*/ "userlang=en;Path=/ROOT%23%3F;Max-Age=31536000",
/* expected <h1> */ "Not Found"
},
};
@ -1289,12 +1231,7 @@ TEST_F(ServerTest, UserLanguageControl)
headers.insert({"Cookie", t.requestCookie});
}
const auto r = zfs1_->GET(t.url.c_str(), headers);
if ( t.responseSetCookie ) {
ASSERT_TRUE(r->has_header("Set-Cookie")) << t;
EXPECT_EQ(t.responseSetCookie, getHeaderValue(r->headers, "Set-Cookie")) << t;
} else {
EXPECT_FALSE(r->has_header("Set-Cookie"));
}
EXPECT_FALSE(r->has_header("Set-Cookie"));
std::regex_search(r->body, h1Match, h1Regex);
const std::string h1(h1Match[1]);
EXPECT_EQ(h1, t.expectedH1) << t;
@ -1989,7 +1926,8 @@ TEST_F(ServerTest, viewerSettings)
R"(const viewerSettings = {
toolbarEnabled: false,
linkBlockingEnabled: false,
libraryButtonEnabled: false
libraryButtonEnabled: false,
defaultUserLanguage: "en"
}
)");
}
@ -2000,7 +1938,8 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: false,
linkBlockingEnabled: true,
libraryButtonEnabled: false
libraryButtonEnabled: false,
defaultUserLanguage: "en"
}
)");
}
@ -2011,7 +1950,8 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: false
libraryButtonEnabled: false,
defaultUserLanguage: "en"
}
)");
}
@ -2022,7 +1962,47 @@ R"(const viewerSettings = {
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true
libraryButtonEnabled: true,
defaultUserLanguage: "en"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "fr"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "fr"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "test;q=0.2, en;q=0.9"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "en"
}
)");
}
{
resetServer(ZimFileServer::WITH_TASKBAR_AND_LIBRARY_BUTTON);
const Headers headers{ {"Accept-Language", "test;q=0.9, en;q=0.2"} };
ASSERT_EQ(zfs1_->GET("/ROOT%23%3F/viewer_settings.js", headers)->body,
R"(const viewerSettings = {
toolbarEnabled: true,
linkBlockingEnabled: false,
libraryButtonEnabled: true,
defaultUserLanguage: "test"
}
)");
}