fix(reolink): do not percent-encode RTMP credential params (#2058)

The Reolink RTMP server does not percent-decode query parameter values - it compares the raw bytes from the URL against the stored password. URLSearchParams.set() percent-encodes values when the URL is serialised (WHATWG application/x-www-form-urlencoded), which corrupts passwords containing characters such as '!' (-> '%21'), '#' (-> '%23'), '+' (-> '%2B'), space, etc.

Affected: users whose firmware causes getLoginParameters to return {user, password} (instead of a Login-API token) and whose password contains any of those characters. Symptom: rebroadcast prebuffer fails with 'Socket received FIN' immediately after 'Sending play command'.

Append the RTMP credential pairs as raw bytes instead of going through URLSearchParams. Token-only path is unaffected (hex tokens contain no characters that require encoding). Mirrors the rationale of #1509 for the HTTP API path.

Fixes #2057

Co-authored-by: thllxb <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
thllxb
2026-06-01 18:17:42 -04:00
committed by GitHub
parent 5606a95fb3
commit 41f2d87a48

View File

@@ -779,13 +779,21 @@ class ReolinkCamera extends RtspSmartCamera implements Camera, DeviceProvider, R
if (url.protocol !== 'rtmp:') {
url.username = this.storage.getItem('username');
url.password = this.storage.getItem('password') || '';
} else {
const params = url.searchParams;
for (const [k, v] of Object.entries(this.client.parameters)) {
params.set(k, v);
}
return url.toString();
}
return url.toString();
// The Reolink RTMP server does not percent-decode query parameter
// values - it compares the raw bytes from the URL against the stored
// password. URLSearchParams.set() percent-encodes values when the URL
// is serialised (WHATWG application/x-www-form-urlencoded), which
// corrupts passwords containing characters such as '!', '#', '+',
// space, etc. Append the credential parameters as raw bytes so the
// password is delivered to the camera exactly as the user entered it.
// See #1509 for the same class of issue on the HTTP API path.
const sep = rtspUrl.includes('?') ? '&' : '?';
const extras = Object.entries(this.client.parameters)
.map(([k, v]) => `${k}=${v}`)
.join('&');
return rtspUrl + sep + extras;
}
async createVideoStream(vso: UrlMediaStreamOptions): Promise<MediaObject> {