From 3d6392f39f7e132c7329bcddddf70afd77b02299 Mon Sep 17 00:00:00 2001 From: Strycher Date: Fri, 31 Jul 2026 00:29:47 -0400 Subject: [PATCH] fix(#469): broker blank-field GET leaked the echoed key line extractScalarValue required ' = ' (space-equals-space) and trimRight()'d first, so a blank field's reply 'key =' (no space after =) failed the match and the whole 'mqtt.broker.N.field =' line leaked in as the value. Split on the first '=' and trim instead: blank -> empty, values with '=' preserved. Regression tests added. Co-Authored-By: Claude Opus 4.8 --- lib/connector/observer_config_client.dart | 15 ++++++---- .../observer_config_client_test.dart | 28 +++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/connector/observer_config_client.dart b/lib/connector/observer_config_client.dart index 39a460d..0edfb30 100644 --- a/lib/connector/observer_config_client.dart +++ b/lib/connector/observer_config_client.dart @@ -241,12 +241,17 @@ class ObserverConfigClient { } } - /// Extract the value from a scalar reply ("key = value\n" → "value"). - /// Falls back to the trimmed whole string if there's no " = ". + /// Extract the value from a scalar reply (`key = value` -> `value`). + /// + /// Splits on the FIRST `=` (config keys never contain `=`; values may). A + /// blank field replies `key =` with nothing after the `=`, so the old + /// ` = ` (space-equals-space) match failed and leaked the whole `key =` line + /// as the value (#469). Splitting on `=` and trimming handles blank values + /// (-> empty) and values containing `=` correctly. static String extractScalarValue(String reply) { - final trimmed = reply.replaceAll('\n', '').trimRight(); - final i = trimmed.indexOf(' = '); - return i < 0 ? trimmed : trimmed.substring(i + 3); + final line = reply.replaceAll('\n', ''); + final i = line.indexOf('='); + return i < 0 ? line.trim() : line.substring(i + 1).trim(); } /// Read a NUL-terminated UTF-8 string from [f] starting at [start]. diff --git a/test/connector/observer_config_client_test.dart b/test/connector/observer_config_client_test.dart index 08b6401..cf38a15 100644 --- a/test/connector/observer_config_client_test.dart +++ b/test/connector/observer_config_client_test.dart @@ -208,4 +208,32 @@ void main() { expect(list, isNull); }); }); + + group('extractScalarValue (#469)', () { + String v(String reply) => ObserverConfigClient.extractScalarValue(reply); + + test('blank field returns empty, not the echoed key line', () { + // The bug: a blank field replies "key =" (no space after =); the old + // " = " match failed and leaked the whole line. + expect(v('mqtt.broker.1.iata_override ='), ''); + expect(v('mqtt.broker.1.iata_override =\n'), ''); + expect(v('mqtt.broker.1.iata_override = '), ''); + }); + + test('normal value extracted', () { + expect( + v('mqtt.broker.0.url = mqtt://mqtt1.okimesh.org:1883\n'), + 'mqtt://mqtt1.okimesh.org:1883', + ); + expect(v('mqtt.broker.0.enabled = 1'), '1'); + }); + + test('value containing = is preserved (split on first =)', () { + expect(v('mqtt.broker.0.jwt_token = a=b=c'), 'a=b=c'); + }); + + test('no = falls back to the trimmed line', () { + expect(v(' bareword '), 'bareword'); + }); + }); }