mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-09-21 17:23:59 +02:00
Restores #2993 verbatim. It was merged, then reverted by the builder's revert-on-failure job a minute later - not because of anything in it, but because EndBug/add-and-commit's floating v11 tag had moved to a release whose action.yml no longer loads, so prebuild-sanitize failed before running a step. The tag is pinned back to v11.0.0 in #2996, which has to land first for the builder to get past that job. The change itself is unchanged and still verified against the real njs module: the rewritten /_next paths carry the add-on version, njs strips the marker before proxying, so a browser holding the year-cached rewritten bundle fetches fresh URLs on the first load after the update. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
100 lines
3.9 KiB
JavaScript
100 lines
3.9 KiB
JavaScript
/*
|
|
* Repair the query string of a Home Assistant ingress request.
|
|
*
|
|
* Supervisor proxies ingress traffic with `params=request.query`
|
|
* (supervisor/api/ingress.py), so aiohttp/yarl re-encodes an already-decoded
|
|
* query string on the way to this add-on. yarl's "safe" set is much wider than
|
|
* the one Seerr's express-openapi-validator will accept: yarl emits a space as
|
|
* "+" and passes ":", "/", "?", "@", "!", "$", "'", "(", ")", "*" and ","
|
|
* through bare, while the validator checks the raw, still-encoded value against
|
|
*
|
|
* RESERVED_CHARS = /[\:\/\?#\[\]@!\$&\'()\*\+,;=]/
|
|
*
|
|
* and answers 400 "Parameter '<name>' must be url encoded". Every search for a
|
|
* title containing a space or punctuation therefore fails - "Monsters, Inc.",
|
|
* "Ocean's Eleven", "Mission: Impossible" - which Seerr's UI reports as a
|
|
* 500. The same requests succeed on the directly published port 5055, which
|
|
* does not pass through Supervisor.
|
|
*
|
|
* "?" deserves a note: the validator strips one with `qs.replace('?', '')`
|
|
* before testing, so a single bare "?" slips through by accident and only a
|
|
* second one ("Who? What?") produces the 400. It is encoded here regardless.
|
|
*
|
|
* Re-encoding those characters here is lossless, because yarl only ever emits
|
|
* them bare when they were literal characters of the value: anything the user
|
|
* actually typed that is ambiguous comes through already percent-encoded
|
|
* (a typed "+" arrives as "%2B", "&" as "%26", "=" as "%3D").
|
|
*
|
|
* "&" and "=" are deliberately NOT re-encoded: they are the query string's own
|
|
* separators, so a bare one is always structural.
|
|
*/
|
|
|
|
/*
|
|
* Every character of the validator's RESERVED_CHARS except "&" and "=", which
|
|
* are the query string's own separators and are handled above. Deriving the
|
|
* set from what the validator rejects - rather than from what yarl currently
|
|
* emits bare - keeps this correct if either side changes its safe set.
|
|
*/
|
|
var NEEDS_ENCODING = /[:\/?#\[\]@!$'()*,;]/g;
|
|
|
|
function encodePart(part) {
|
|
return part
|
|
/* yarl encodes a space as "+"; a literal "+" arrives as "%2B". */
|
|
.replace(/\+/g, "%20")
|
|
.replace(NEEDS_ENCODING, function (c) {
|
|
return "%" + c.charCodeAt(0).toString(16).toUpperCase();
|
|
});
|
|
}
|
|
|
|
/*
|
|
* The cache-busting marker servers/ingress.conf inserts in front of every
|
|
* rewritten "/_next" path, e.g. "/ha-3-4-1-3/_next/static/chunks/x.js". It
|
|
* gives each add-on release its own asset URLs - Seerr serves /_next/static/ as
|
|
* immutable for a year and sub_filter strips the validators, so identical URLs
|
|
* would pin the rewritten bundle in the browser forever. Seerr knows nothing
|
|
* about the marker, so it is removed again here, on the way in.
|
|
*
|
|
* Any marker is accepted, not just the one this container serves: a tab opened
|
|
* before an add-on update keeps requesting its dynamic chunks under the marker
|
|
* it was handed, and those have to keep working until it is reloaded. The
|
|
* lookahead keeps a real Seerr path that merely starts with "ha-" untouched.
|
|
*/
|
|
var ASSET_TAG = /^\/ha-[0-9A-Za-z-]+(?=\/_next(\/|$))/;
|
|
|
|
/*
|
|
* Returns the request URI with the path untouched byte-for-byte apart from the
|
|
* cache-busting marker, and only the query string repaired. Used as the
|
|
* proxy_pass target.
|
|
*/
|
|
function uri(r) {
|
|
var raw = r.variables.request_uri.replace(ASSET_TAG, "");
|
|
var split = raw.indexOf("?");
|
|
|
|
if (split < 0) {
|
|
return raw;
|
|
}
|
|
|
|
var path = raw.substring(0, split);
|
|
var args = raw.substring(split + 1);
|
|
|
|
/* A bare trailing "?" is forwarded as-is, so the URI stays byte-for-byte. */
|
|
if (args === "") {
|
|
return raw;
|
|
}
|
|
|
|
var repaired = args
|
|
.split("&")
|
|
.map(function (pair) {
|
|
var eq = pair.indexOf("=");
|
|
if (eq < 0) {
|
|
return encodePart(pair);
|
|
}
|
|
return encodePart(pair.substring(0, eq)) + "=" + encodePart(pair.substring(eq + 1));
|
|
})
|
|
.join("&");
|
|
|
|
return path + "?" + repaired;
|
|
}
|
|
|
|
export default { uri };
|