diff --git a/static/skin/index.js b/static/skin/index.js index b93253aaa..c8c346003 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -197,6 +197,58 @@ } } + function makeURLSearchString(params, keysToURIEncode) { + let output = ''; + for (const [key, value] of params.entries()) { + let finalValue = (keysToURIEncode.indexOf(key) >= 0) ? encodeURIComponent(value) : value; + output += `&${key}=${finalValue}`; + } + return output; + } + + /* hack for library.kiwix.org magnet links (created by MirrorBrain) + See https://github.com/kiwix/container-images/issues/242 */ + async function getFixedMirrorbrainMagnet(magnetLink) { + // parse as query parameters + const params = new URLSearchParams( + magnetLink.replaceAll('&', '&').replace(/^magnet:/, '')); + + const zimUrl = params.get('as'); // as= is fallback URL + // download metalink to build list of mirrored URLs + let mirrorUrls = []; + + const metalink = await fetch(`${zimUrl}.meta4`).then(response => { + return response.ok ? response.text() : ''; + }).catch((_error) => ''); + if (metalink) { + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(metalink, "application/xml"); + doc.querySelectorAll("url").forEach((node) => { + if (node.hasAttribute("priority")) { // ensures its a mirror link + mirrorUrls.push(node.innerHTML); + } + }); + } catch (err) { + // not a big deal, magnet will only contain primary URL + console.debug(`Failed to parse mirror links for ${zimUrl}`); + } + } + + // set webseed (ws=) URL to primary download URL (redirects to mirror) + params.set('ws', zimUrl); + // if we got metalink mirror URLs, append them all + if (mirrorUrls) { + mirrorUrls.forEach((url) => { + params.append('ws', url); + }); + } + + params.set('xs', `${zimUrl}.torrent`); // adding xs= to point to torrent URL + + return 'magnet:?' + makeURLSearchString(params, ['ws', 'as', 'dn', 'xs', 'tr']); + } + async function getMagnetLink(downloadLink) { const magnetUrl = downloadLink + '.magnet'; const controller = new AbortController(); @@ -204,6 +256,9 @@ const magnetLink = await fetch(magnetUrl, { signal: controller.signal }).then(response => { return response.ok ? response.text() : ''; }).catch((_error) => ''); + if (magnetLink) { + return await getFixedMirrorbrainMagnet(magnetLink); + } return magnetLink; } diff --git a/test/server.cpp b/test/server.cpp index a565a3536..684e7e6b3 100644 --- a/test/server.cpp +++ b/test/server.cpp @@ -63,7 +63,7 @@ const ResourceCollection resources200Compressible{ { 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=e1b1ae55" }, + { STATIC_CONTENT, "/ROOT%23%3F/skin/index.js?cacheid=ce19da2a" }, { 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" }, @@ -288,7 +288,7 @@ R"EXPECTEDRESULT( href="/ROOT%23%3F/skin/index.css?cacheid=e4d76d16" - +