From 41f2d87a48e8c983dda8c42cb6505d0bbdddf665 Mon Sep 17 00:00:00 2001 From: thllxb <26775504+thllxb@users.noreply.github.com> Date: Mon, 1 Jun 2026 18:17:42 -0400 Subject: [PATCH] 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> --- plugins/reolink/src/main.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/reolink/src/main.ts b/plugins/reolink/src/main.ts index 0809c7711..59a62cce1 100644 --- a/plugins/reolink/src/main.ts +++ b/plugins/reolink/src/main.ts @@ -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 {