diff --git a/static/skin/viewer.js b/static/skin/viewer.js
index ca49833fa..ea47e532f 100644
--- a/static/skin/viewer.js
+++ b/static/skin/viewer.js
@@ -54,6 +54,22 @@ function gotoRandomPage() {
gotoUrl(`/random?content=${currentBook}`);
}
+// URI-encodes only the specified special symbols (note, however, that '%' is
+// always considered a special symbol).
+function quasiUriEncode(s, specialSymbols) {
+ if ( specialSymbols.match(/[A-Za-z0-9]/) ) {
+ throw "Alphanumeric symbols cannot be special";
+ }
+
+ // Set's guarantee of iterating in insertion order ensures that
+ // all %s in s will be encoded first.
+ for ( const c of new Set('%' + specialSymbols) ) {
+ s = s.replaceAll(c, encodeURIComponent(c));
+ }
+
+ return s;
+}
+
function performSearch() {
const searchbox = document.getElementById('kiwixsearchbox');
const q = encodeURIComponent(searchbox.value);
@@ -386,8 +402,13 @@ function setupSuggestions() {
const uriEncodedBookName = encodeURIComponent(currentBook);
let url;
if (data.value.kind == "path") {
- const path = encodeURIComponent(htmlDecode(data.value.path));
- url = `/content/${uriEncodedBookName}/${path}`;
+ // The double quote and backslash symbols are included in the list
+ // of special symbols to URI-encode so that the resulting URL can
+ // be safely quoted inside a dynamically executed piece of
+ // Javascript code a few lines later.
+ const path = htmlDecode(data.value.path);
+ const quasiUriEncodedPath = quasiUriEncode(path, '#?"\\');
+ url = `/content/${uriEncodedBookName}/${quasiUriEncodedPath}`;
} else {
const pattern = encodeURIComponent(htmlDecode(data.value.value));
url = `/search?content=${uriEncodedBookName}&pattern=${pattern}`;
diff --git a/test/server.cpp b/test/server.cpp
index 53ee47df1..4e2086704 100644
--- a/test/server.cpp
+++ b/test/server.cpp
@@ -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=cb9b1f75" },
+ { STATIC_CONTENT, "/ROOT%23%3F/skin/viewer.js?cacheid=bb748367" },
{ 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" },
@@ -312,7 +312,7 @@ R"EXPECTEDRESULT(
-
+
const blankPageUrl = root + "/skin/blank.html?cacheid=6b1fa032";