fix(translate): forward upstream redirects instead of following them
Some checks failed
Validate Collection URLs / validate-urls (push) Has been cancelled

urlopen() follows redirects silently. Kiwix answers a book root such as
/content/<book> with a 302 to the ZIM's main page, so the proxy served
that page's HTML at the original, shallower URL. Every relative link and
stylesheet then resolved one directory too high: the page lost its
styling and every article link 404'd (e.g. atenolol/ -> /content/atenolol/).
Kiwix's /random is a 302 too and broke the same way.

Use an opener that returns 3xx to the browser like any other non-200
response, and keep an absolute Location on this origin rather than the
upstream container name.

Bumps the catalog image to 0.1.1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Sherwood 2026-09-25 13:15:35 -07:00
parent a483e15f7d
commit 66346ea295
2 changed files with 26 additions and 2 deletions

View File

@ -550,7 +550,7 @@ export default class ServiceSeeder extends BaseSeeder {
description:
'Read the Information Library in another language. Machine translation that works offline, on CPU',
icon: 'IconWorld',
container_image: 'ghcr.io/crosstalk-solutions/project-nomad-translate:0.1.0',
container_image: 'ghcr.io/crosstalk-solutions/project-nomad-translate:0.1.1',
source_repo: 'https://github.com/browsermt/bergamot-translator',
container_command: null,
container_config: JSON.stringify({

View File

@ -40,6 +40,24 @@ MAX_TRANSLATE_BYTES = int(os.environ.get("MAX_TRANSLATE_BYTES", str(4 * 1024 * 1
COOKIE = "nomadlang"
class _NoRedirect(urllib.request.HTTPRedirectHandler):
"""Hand upstream redirects back to the browser instead of following them.
Kiwix answers a book root such as /content/<book> with a 302 to the ZIM's
main page. Following it here would serve that page's HTML at the ORIGINAL
URL, so every relative link and stylesheet in it resolves one directory
too high: styling is lost and every article link 404s.
"""
def redirect_request(self, *args, **kwargs):
return None
# urlopen() follows redirects by default; this opener raises them as HTTPError
# so they are forwarded below like any other non-200 response.
_opener = urllib.request.build_opener(_NoRedirect)
# Display names for the languages Bergamot's tiny model set covers. A language
# only appears in the control if its model is actually on disk.
LANG_NAMES = {
@ -226,12 +244,18 @@ class Handler(BaseHTTPRequestHandler):
# HTML can be rewritten without decompressing first.
try:
response = urllib.request.urlopen(request, timeout=120)
response = _opener.open(request, timeout=120)
status, headers, data = response.status, response.headers, response.read()
except urllib.error.HTTPError as exc:
# Forwarded as-is, which is how Kiwix's own 404 and 400 pages keep
# coming back byte for byte.
status, headers, data = exc.code, exc.headers, exc.read()
# An absolute Location would point the browser at the upstream
# container name, which it cannot reach. Keep it on this origin.
location = headers.get("Location", "")
if location.startswith(UPSTREAM):
del headers["Location"]
headers["Location"] = location[len(UPSTREAM):] or "/"
except Exception as exc:
message = f"Translation proxy could not reach the library: {exc}".encode()
self.send_response(502)