From 842e91b34ac94000cf36ebf1f5a8a546dbc23bc0 Mon Sep 17 00:00:00 2001 From: Ian Renton Date: Sun, 16 Aug 2026 15:23:05 +0100 Subject: [PATCH] Re-add translation of QRZ/HamQTH query params to header params, since it turns out someone was using that --- server/handlers/api/v1_compatability.py | 35 ++++++++++++++----------- server/handlers/api/v1_spots.py | 26 ++++++++++++++++++ templates/add_spot.html | 2 +- templates/alerts.html | 2 +- templates/bands.html | 4 +-- templates/base.html | 10 +++---- templates/conditions.html | 2 +- templates/map.html | 4 +-- templates/spots.html | 4 +-- templates/status.html | 2 +- 10 files changed, 60 insertions(+), 31 deletions(-) diff --git a/server/handlers/api/v1_compatability.py b/server/handlers/api/v1_compatability.py index 702c5db..fa6bcbb 100644 --- a/server/handlers/api/v1_compatability.py +++ b/server/handlers/api/v1_compatability.py @@ -3,27 +3,39 @@ from tornado.httpclient import AsyncHTTPClient from tornado.httputil import HTTPHeaders +_LEGACY_PARAM_TO_HEADER_MAP = { + "qrz_username": "X-QRZ-Username", + "qrz_password": "X-QRZ-Password", + "qrz_session_key": "X-QRZ-Session-Key", + "hamqth_username": "X-HamQTH-Username", + "hamqth_password": "X-HamQTH-Password", + "hamqth_session_id": "X-HamQTH-Session-ID", +} + class V1RedirectHandler(tornado.web.RequestHandler): """Transparently proxies requests from the old API to the new one, returning whatever the v2 endpoint returns, for endpoints with no breaking changes.""" - SUPPORTED_METHODS = ("GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS") - - # Methods where an HTTP body isn't valid - _BODYLESS_METHODS = {"GET", "HEAD", "OPTIONS"} - async def _proxy(self, path): new_url = f"{self.request.protocol}://{self.request.host}/api/v2/{path}" if self.request.query: new_url += f"?{self.request.query}" + # Copy the incoming headers so we can add translated legacy credentials without changing the original + # request. + headers = HTTPHeaders(self.request.headers) + for param, header in _LEGACY_PARAM_TO_HEADER_MAP.items(): + value = self.get_query_argument(param, default=None) + if value: + headers[header] = value + client = AsyncHTTPClient() try: response = await client.fetch( new_url, method=self.request.method, headers=self.request.headers, - body=None if self.request.method in self._BODYLESS_METHODS else (self.request.body or b""), + body=None if self.request.method == "GET" else (self.request.body or b""), raise_error=False, follow_redirects=False, request_timeout=10.0, @@ -48,13 +60,4 @@ class V1RedirectHandler(tornado.web.RequestHandler): await self._proxy(path) async def post(self, path): - await self._proxy(path) - - async def put(self, path): - await self._proxy(path) - - async def delete(self, path): - await self._proxy(path) - - async def patch(self, path): - await self._proxy(path) + await self._proxy(path) \ No newline at end of file diff --git a/server/handlers/api/v1_spots.py b/server/handlers/api/v1_spots.py index cb85284..fc05ffb 100644 --- a/server/handlers/api/v1_spots.py +++ b/server/handlers/api/v1_spots.py @@ -3,11 +3,33 @@ import re from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler _GRID_SOURCE_RE = re.compile(r'"dx_location_source":\s*"GRID"') +_LEGACY_PARAM_TO_HEADER_MAP = { + "qrz_username": "X-QRZ-Username", + "qrz_password": "X-QRZ-Password", + "qrz_session_key": "X-QRZ-Session-Key", + "hamqth_username": "X-HamQTH-Username", + "hamqth_password": "X-HamQTH-Password", + "hamqth_session_id": "X-HamQTH-Session-ID", +} +def _handle_legacy_params(handler): + """Copy v1 query-string QRZ/HamQTH credentials into the v2 headers, so the v2 handler can see them""" + + for param, header in _LEGACY_PARAM_TO_HEADER_MAP.items(): + if header in handler.request.headers: + continue + value = handler.get_query_argument(param, default=None) + if value: + handler.request.headers[header] = value + class V1APISpotsHandler(APISpotsHandler): """API request handler for /api/v1/spots (GET). Included in early Spothole v2 for backwards compatibility.""" + def prepare(self): + _handle_legacy_params(self) + super().prepare() + def write(self, chunk): if isinstance(chunk, str): chunk = _GRID_SOURCE_RE.sub('"dx_location_source": "SPOT"', chunk) @@ -17,6 +39,10 @@ class V1APISpotsHandler(APISpotsHandler): class V1APISpotsStreamHandler(APISpotsStreamHandler): """API request handler for /api/v1/spots/stream (SSE). Included in early Spothole v2 for backwards compatibility.""" + def prepare(self): + _handle_legacy_params(self) + super().prepare() + def write_message(self, *args, **kwargs): args = list(args) for i, a in enumerate(args): diff --git a/templates/add_spot.html b/templates/add_spot.html index 171a454..e22a22b 100644 --- a/templates/add_spot.html +++ b/templates/add_spot.html @@ -76,7 +76,7 @@ - + diff --git a/templates/alerts.html b/templates/alerts.html index dbd87b8..c910343 100644 --- a/templates/alerts.html +++ b/templates/alerts.html @@ -84,7 +84,7 @@ - + diff --git a/templates/bands.html b/templates/bands.html index df355ee..c5c51f1 100644 --- a/templates/bands.html +++ b/templates/bands.html @@ -76,8 +76,8 @@ - - + + diff --git a/templates/base.html b/templates/base.html index b14d88b..2bd2f4b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,6 +1,6 @@ {% extends "skeleton.html" %} {% block head_extra %} - + @@ -15,10 +15,10 @@ window.fetchEventSource = fetchEventSource; - - - - + + + + {% end %} {% block body %}
diff --git a/templates/conditions.html b/templates/conditions.html index 46dbe00..13a873a 100644 --- a/templates/conditions.html +++ b/templates/conditions.html @@ -284,7 +284,7 @@
- + diff --git a/templates/map.html b/templates/map.html index 0e13066..3393263 100644 --- a/templates/map.html +++ b/templates/map.html @@ -109,8 +109,8 @@ - - + + diff --git a/templates/spots.html b/templates/spots.html index 7178c9d..98c3c1a 100644 --- a/templates/spots.html +++ b/templates/spots.html @@ -113,8 +113,8 @@ - - + + diff --git a/templates/status.html b/templates/status.html index 66d4e23..403c2c8 100644 --- a/templates/status.html +++ b/templates/status.html @@ -86,7 +86,7 @@ - +