Skip to content

Commit c795611

Browse files
committed
Consider equal and comma are safe in URLs for proper Youtube JS player operation
1 parent 295fefc commit c795611

10 files changed

Lines changed: 68 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Do not URL-encode `=` and `,` characters when rewriting document URIs, fixing YouTube JS player breakage (#316)
13+
1014
## [5.4.0] - 2026-05-28
1115

1216
### Added

docs/technical_architecture.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Below is an example case of the rewrite operation on an image URL found in an HT
4444
- Document original URL: `https://kiwix.org/a/article/document.html`
4545
- Document ZIM path: `kiwix.org/a/article/document.html`
4646
- Image original URL: `//xn--exmple-cva.com/a/resource/image.png?foo=bar`
47-
- Image rewritten URL: `../../../ex%C3%A9mple.com/a/resource/image.png%3Ffoo%3Dbar`
47+
- Image rewritten URL: `../../../ex%C3%A9mple.com/a/resource/image.png%3Ffoo=bar`
4848
- Image ZIM Path: `exémple.com/a/resource/image.png?foo=bar`
4949

5050
## JS Rewriting

javascript/src/wombatSetup.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,11 @@ export function urlRewriteFunction(
194194

195195
// Reencode everything but '/' (we decode it afterwards for simplicity)
196196
const finalUrl =
197-
prefix + encodeURIComponent(fuzzifiedPath).replaceAll('%2F', '/');
197+
prefix +
198+
encodeURIComponent(fuzzifiedPath)
199+
.replaceAll('%2F', '/')
200+
.replaceAll('%3D', '=')
201+
.replaceAll('%2C', ',');
198202

199203
console.debug(
200204
'urlRewriten:\n\t- current_url: ' +

javascript/test/wombatUrlRewriting.js

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ test('contentWithEncodedQuestionMarkAndQueryParam', (t) => {
335335
undefined,
336336
undefined,
337337
),
338-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/cont%3Fnt.txt%3Fquery%3Dvalue',
338+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/cont%3Fnt.txt%3Fquery=value',
339339
);
340340
});
341341

@@ -454,7 +454,24 @@ test('contentWithSimpleQueryString', (t) => {
454454
undefined,
455455
undefined,
456456
),
457-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery%3Dvalue',
457+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery=value',
458+
);
459+
});
460+
461+
test('contentWithSimplePathWithEqualAndComma', (t) => {
462+
t.is(
463+
urlRewriteFunction(
464+
t.context.currentUrl,
465+
t.context.originalHost,
466+
t.context.originalScheme,
467+
t.context.originalUrl,
468+
t.context.prefix,
469+
'https://www.example.com/javascript/content.txt/query1=value1,param2=value2',
470+
undefined,
471+
undefined,
472+
undefined,
473+
),
474+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt/query1=value1,param2=value2',
458475
);
459476
});
460477

@@ -471,7 +488,7 @@ test('contentWithQueryValueEqualSign', (t) => {
471488
undefined,
472489
undefined,
473490
),
474-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery%3Dval%3Deue',
491+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery=val=eue',
475492
);
476493
});
477494

@@ -488,7 +505,7 @@ test('contentWithQueryValuePercentSign', (t) => {
488505
undefined,
489506
undefined,
490507
),
491-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery%3Dval%25eue',
508+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery=val%25eue',
492509
);
493510
});
494511

@@ -505,7 +522,7 @@ test('contentWithQueryParamPercentSign', (t) => {
505522
undefined,
506523
undefined,
507524
),
508-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fque%25ry%3Dvaleue',
525+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fque%25ry=valeue',
509526
);
510527
});
511528

@@ -522,7 +539,7 @@ test('contentWithQueryParamPlusSign', (t) => {
522539
undefined,
523540
undefined,
524541
),
525-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fparam%3Dval%20ue',
542+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fparam=val%20ue',
526543
);
527544
});
528545

@@ -811,7 +828,7 @@ test('youtubeFuzzyNotEncoded', (t) => {
811828
undefined,
812829
undefined,
813830
),
814-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/path1/youtube.fuzzy.replayweb.page/get_video_info%3Fvideo_id%3D123ah',
831+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/path1/youtube.fuzzy.replayweb.page/get_video_info%3Fvideo_id=123ah',
815832
);
816833
});
817834

@@ -828,7 +845,7 @@ test('youtubeFuzzyEncoded', (t) => {
828845
undefined,
829846
undefined,
830847
),
831-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/path1/youtube.fuzzy.replayweb.page/get_video_info%3Fvideo_id%3D12%3D3ah',
848+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/path1/youtube.fuzzy.replayweb.page/get_video_info%3Fvideo_id=12=3ah',
832849
);
833850
});
834851

@@ -931,7 +948,7 @@ test('relAlreadyEncoded', (t) => {
931948
undefined,
932949
undefined,
933950
),
934-
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery%3Dvalue',
951+
'http://library.kiwix.org/content/myzim_yyyy-mm/www.example.com/javascript/content.txt%3Fquery=value',
935952
);
936953
});
937954

@@ -1001,7 +1018,8 @@ test('relAnotherHostAlreadyRewrittenEmptyPath', (t) => {
10011018
});
10021019

10031020
// this is an edge case where the URL has already been statically rewritten and is located
1004-
// on a different fuzzified domain name => we do not touch it at all
1021+
// on a different fuzzified domain name => we do not touch it at all (for instance it keeps
1022+
// %3D even if it should not have them)
10051023
test('relAnotherFuzzifiedHostAlreadyRewritten', (t) => {
10061024
t.is(
10071025
urlRewriteFunction(

src/zimscraperlib/rewriting/url_rewriting.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
start by resolving it into an absolute URL (based on the containing document absolute
3535
URI), applying the transformation to compute the corresponding ZIM path and we
3636
url-encode the whole ZIM path, so that readers will have one single blob to process,
37-
url-decode and find corresponding ZIM entry. Only '/' separators are considered safe
38-
and not url-encoded.
37+
url-decode and find corresponding ZIM entry. Only '/', '=' and ',' are considered safe
38+
and not url-encoded. '=' and ',' are kept unencoded because some sites (e.g. YouTube)
39+
embed scripts that parse their own script URL and expect these characters to appear
40+
literally in it.
3941
"""
4042

4143
import re
@@ -332,7 +334,7 @@ def get_document_uri(self, item_path: ZimPath, item_fragment: str) -> str:
332334
relative_path += "/"
333335

334336
return (
335-
f"{quote(relative_path, safe='/')}"
337+
f"{quote(relative_path, safe='/=,')}"
336338
f"{'#' + item_fragment if item_fragment else ''}"
337339
)
338340

src/zimscraperlib/zim/items.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def __init__(
270270
if self.headers.get("Content-Encoding", "identity") != "identity":
271271
raise ValueError("Can't trust Content-Length for size")
272272
# non-html, non-compressed data.
273-
self.size = int(self.headers["Content-Length"])
273+
self.size = int(self.headers["Content-Length"]) # pragma: no cover
274274
except Exception:
275275
# we couldn't retrieve size so we have to download resource to
276276
target, self.size = self.download_for_size(

tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def gzip_html_url() -> str:
7474

7575
@pytest.fixture(scope="module")
7676
def gzip_nonhtml_url() -> str:
77-
return "http://mirror.download.kiwix.org/robots.txt"
77+
return "http://lb.download.kiwix.org/robots.txt"
7878

7979

8080
def file_src(fname: str) -> pathlib.Path:

tests/rewriting/test_css_rewriting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def test_rewrite():
190190

191191
expected = """
192192
/* A comment with a link : http://foo.com */
193-
@import url(../fonts.googleapis.com/icon%3Ffamily%3DMaterial%20Icons);
193+
@import url(../fonts.googleapis.com/icon%3Ffamily=Material%20Icons);
194194
195195
p, input {
196196
color: rbg(1, 2, 3);

tests/rewriting/test_html_rewriting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -787,7 +787,7 @@ def test_rewrite_base_href(rewrite_base_href_content: ContentForTests):
787787
),
788788
pytest.param(
789789
"""<img src="image.png?param1=value1&param2=value2">""",
790-
"""<img src="image.png%3Fparam1%3Dvalue1%26param2%3Dvalue2">""",
790+
"""<img src="image.png%3Fparam1=value1%26param2=value2">""",
791791
id="badly_escaped_src",
792792
),
793793
],

tests/rewriting/test_url_rewriting.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def test_missing_zim_paths(
170170
"foo.html?foo=bar",
171171
RewriteResult(
172172
"https://kiwix.org/a/article/foo.html?foo=bar",
173-
"foo.html%3Ffoo%3Dbar",
173+
"foo.html%3Ffoo=bar",
174174
ZimPath("kiwix.org/a/article/foo.html?foo=bar"),
175175
),
176176
["kiwix.org/a/article/foo.html?foo=bar"],
@@ -181,7 +181,7 @@ def test_missing_zim_paths(
181181
"foo.html?foo=b%24ar",
182182
RewriteResult(
183183
"https://kiwix.org/a/article/foo.html?foo=b%24ar",
184-
"foo.html%3Ffoo%3Db%24ar",
184+
"foo.html%3Ffoo=b%24ar",
185185
ZimPath("kiwix.org/a/article/foo.html?foo=b$ar"),
186186
),
187187
["kiwix.org/a/article/foo.html?foo=b$ar"],
@@ -192,7 +192,7 @@ def test_missing_zim_paths(
192192
"foo.html?foo=b%3Far", # a query string with an encoded ? char in value
193193
RewriteResult(
194194
"https://kiwix.org/a/article/foo.html?foo=b%3Far",
195-
"foo.html%3Ffoo%3Db%3Far",
195+
"foo.html%3Ffoo=b%3Far",
196196
ZimPath("kiwix.org/a/article/foo.html?foo=b?ar"),
197197
),
198198
["kiwix.org/a/article/foo.html?foo=b?ar"],
@@ -316,7 +316,7 @@ def test_missing_zim_paths(
316316
"foo?param=value",
317317
RewriteResult(
318318
"https://kiwix.org/a/article/foo?param=value",
319-
"foo%3Fparam%3Dvalue",
319+
"foo%3Fparam=value",
320320
ZimPath("kiwix.org/a/article/foo?param=value"),
321321
),
322322
["kiwix.org/a/article/foo?param=value"],
@@ -327,7 +327,7 @@ def test_missing_zim_paths(
327327
"foo?param=value%2F",
328328
RewriteResult(
329329
"https://kiwix.org/a/article/foo?param=value%2F",
330-
"foo%3Fparam%3Dvalue/",
330+
"foo%3Fparam=value/",
331331
ZimPath("kiwix.org/a/article/foo?param=value/"),
332332
),
333333
["kiwix.org/a/article/foo?param=value/"],
@@ -338,12 +338,26 @@ def test_missing_zim_paths(
338338
"foo?param=value%2Fend",
339339
RewriteResult(
340340
"https://kiwix.org/a/article/foo?param=value%2Fend",
341-
"foo%3Fparam%3Dvalue/end",
341+
"foo%3Fparam=value/end",
342342
ZimPath("kiwix.org/a/article/foo?param=value/end"),
343343
),
344344
["kiwix.org/a/article/foo?param=value/end"],
345345
False,
346346
),
347+
(
348+
# regression test for #316: '=' and ',' must not be encoded so that
349+
# e.g. the YouTube JS player can parse its own script URL properly,
350+
# while other reserved characters (e.g. '?') are still encoded
351+
"https://kiwix.org/a/article/document.html",
352+
"foo?k=ytembeds.base.en_US,foo.bar",
353+
RewriteResult(
354+
"https://kiwix.org/a/article/foo?k=ytembeds.base.en_US,foo.bar",
355+
"foo%3Fk=ytembeds.base.en_US,foo.bar",
356+
ZimPath("kiwix.org/a/article/foo?k=ytembeds.base.en_US,foo.bar"),
357+
),
358+
["kiwix.org/a/article/foo?k=ytembeds.base.en_US,foo.bar"],
359+
False,
360+
),
347361
(
348362
"https://kiwix.org/a/article/document.html",
349363
"foo/",
@@ -393,7 +407,7 @@ def test_missing_zim_paths(
393407
"/foo.html?param=val+ue",
394408
RewriteResult(
395409
"https://kiwix.org/foo.html?param=val+ue",
396-
"../../foo.html%3Fparam%3Dval%20ue",
410+
"../../foo.html%3Fparam=val%20ue",
397411
ZimPath("kiwix.org/foo.html?param=val ue"),
398412
),
399413
["kiwix.org/foo.html?param=val ue"],

0 commit comments

Comments
 (0)