Skip to content

Commit 923c81f

Browse files
committed
JSON-escape and percent-encode string elements
Encode string list elements by JSON-escaping then percent-encoding so quotes, backslashes and newlines remain valid in the server-decoded JSON, while characters like &, =, +, and # are protected from querystring parsing. Introduces _encodeStringElement (uses jsonEncode then Uri.encodeComponent) and updates _encodeStringElements to use it. Tests updated: stricter List<String>/List<int> typing, added a test to verify JSON-escaping of quotes and backslashes, and adjusted existing expectations for encoded delimiters.
1 parent a7d50cf commit 923c81f

2 files changed

Lines changed: 43 additions & 18 deletions

File tree

packages/dart/lib/src/network/parse_query.dart

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,14 +318,23 @@ class QueryBuilder<T extends ParseObject> {
318318
);
319319
}
320320

321-
/// Percent-encodes String elements of [value] so that characters such as
322-
/// `&`, `=`, `+`, and `#` survive URL querystring parsing when the list is
323-
/// serialized into `where={...}` via [buildQuery]. Non-String elements are
324-
/// left untouched. Mirrors the per-argument encoding applied by the scalar
325-
/// [whereEqualTo] / [whereContains] family of methods.
321+
/// JSON-escapes [value] and then percent-encodes the escaped content. The
322+
/// JSON escaping keeps `"`, `\` and newlines valid inside the surrounding
323+
/// string literal once the server URL-decodes the query; the percent
324+
/// encoding keeps `&`, `=`, `+` and `#` from being chewed up by querystring
325+
/// parsing on the way in.
326+
String _encodeStringElement(String value) {
327+
final String jsonString = jsonEncode(value);
328+
return Uri.encodeComponent(
329+
jsonString.substring(1, jsonString.length - 1),
330+
);
331+
}
332+
333+
/// Runs [_encodeStringElement] on each String in [value]; other elements
334+
/// are passed through unchanged.
326335
List<dynamic> _encodeStringElements(List<dynamic> value) {
327336
return value
328-
.map<dynamic>((dynamic e) => e is String ? Uri.encodeComponent(e) : e)
337+
.map<dynamic>((dynamic e) => e is String ? _encodeStringElement(e) : e)
329338
.toList();
330339
}
331340

packages/dart/test/src/network/parse_query_test.dart

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -699,18 +699,15 @@ void main() {
699699
test(
700700
'list-based where methods encode special characters in String elements',
701701
() {
702-
// arrange
703702
final queryBuilder = QueryBuilder.name('Diet_Plans');
704703

705-
// act
706-
queryBuilder.whereContainedIn('topics', <dynamic>[
704+
queryBuilder.whereContainedIn('topics', <String>[
707705
"RFI's & Change Orders",
708706
'Schedule (Impacts, Delays, Inspections)',
709707
]);
710-
queryBuilder.whereNotContainedIn('excluded', <dynamic>['a=b', 'c+d']);
711-
queryBuilder.whereArrayContainsAll('tags', <dynamic>['x#y', 'z&w']);
708+
queryBuilder.whereNotContainedIn('excluded', <String>['a=b', 'c+d']);
709+
queryBuilder.whereArrayContainsAll('tags', <String>['x#y', 'z&w']);
712710

713-
// assert
714711
final queryString = queryBuilder.buildQuery();
715712
const encodedAmp = '%26'; // &
716713
const encodedEq = '%3D'; // =
@@ -722,22 +719,41 @@ void main() {
722719
expect(queryString, contains(encodedPlus));
723720
expect(queryString, contains(encodedHash));
724721

725-
// Ensure the raw unencoded delimiters do NOT appear inside the JSON
726-
// values (they would break querystring parsing on the server).
722+
// Raw delimiters would break querystring parsing on the server, so
723+
// verify they are not present in the encoded output.
727724
expect(queryString.contains('& Change Orders'), isFalse);
728725
expect(queryString.contains('a=b'), isFalse);
729726
expect(queryString.contains('c+d'), isFalse);
730727
expect(queryString.contains('x#y'), isFalse);
731728
});
732729

730+
test(
731+
'list-based where methods JSON-escape quotes and backslashes in String elements',
732+
() {
733+
final queryBuilder = QueryBuilder.name('Diet_Plans');
734+
735+
queryBuilder.whereContainedIn('topics', <String>[
736+
'He said "hi"',
737+
r'C:\path',
738+
]);
739+
740+
final queryString = queryBuilder.buildQuery();
741+
742+
// Encoded form of `\"` and `\\`: the backslash must survive URL decoding
743+
// so the server sees valid JSON (e.g. "He said \"hi\"").
744+
expect(queryString, contains('%5C%22'));
745+
expect(queryString, contains('%5C%5C'));
746+
747+
// Unescaped `"` and `\` inside the string values would corrupt the JSON.
748+
expect(queryString.contains('"He said "hi""'), isFalse);
749+
expect(queryString.contains(r'C:\path'), isFalse);
750+
});
751+
733752
test('list-based where methods leave non-String elements untouched', () {
734-
// arrange
735753
final queryBuilder = QueryBuilder.name('Diet_Plans');
736754

737-
// act
738-
queryBuilder.whereContainedIn('nums', <dynamic>[1, 2, 3]);
755+
queryBuilder.whereContainedIn('nums', <int>[1, 2, 3]);
739756

740-
// assert
741757
final queryString = queryBuilder.buildQuery();
742758
expect(queryString, contains('"\$in":[1,2,3]'));
743759
});

0 commit comments

Comments
 (0)