Skip to content

Commit 8bb04cf

Browse files
authored
feat: use qs_dart in buildUrlString for enhanced query string encoding (#158)
* ➕ add qs_dart and validators dependencies * ⚡ improve URL validation and query parameter handling in buildUrlString * ✅ enhance URL parameter handling tests in buildUrlString * 📝 update error message for invalid URL in buildUrlString * ⬆️ bump qs dependency
1 parent 88efb75 commit 8bb04cf

File tree

3 files changed

+254
-102
lines changed

3 files changed

+254
-102
lines changed

lib/utils/query_parameters.dart

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,36 @@
1+
import 'package:qs_dart/qs_dart.dart' as qs;
2+
import 'package:validators/validators.dart' as validators;
3+
14
/// Takes a string and appends [parameters] as query parameters of [url].
25
///
3-
/// It validates the URL structure and properly encodes both keys and values
4-
/// to prevent URL injection attacks.
6+
/// Throws [ArgumentError] if [url] is not a valid URL.
57
String buildUrlString(String url, Map<String, dynamic>? parameters) {
6-
// Avoids unnecessary processing.
7-
if (parameters == null) return url;
8-
9-
// Check if there are parameters to add.
10-
if (parameters.isNotEmpty) {
11-
// Validate URL structure to prevent injection
12-
// First check if it looks like a valid HTTP/HTTPS URL
13-
if (!url.startsWith('http://') && !url.startsWith('https://')) {
14-
throw ArgumentError(
15-
'Invalid URL structure: $url - must be a valid HTTP/HTTPS URL',
16-
);
17-
}
18-
19-
try {
20-
final uri = Uri.parse(url);
21-
// Additional validation: ensure it has a host
22-
if (uri.host.isEmpty) {
23-
throw ArgumentError(
24-
'Invalid URL structure: $url - must have a valid host',
25-
);
26-
}
27-
} catch (e) {
28-
if (e is ArgumentError) {
29-
rethrow;
30-
}
31-
throw ArgumentError('Invalid URL structure: $url');
32-
}
8+
late final Uri uri;
339

34-
// Checks if the string url already has parameters.
35-
if (url.contains("?")) {
36-
url += "&";
37-
} else {
38-
url += "?";
10+
try {
11+
if (!validators.isURL(url)) {
12+
throw FormatException('Invalid URL format');
3913
}
40-
41-
// Concat every parameter to the string url with proper encoding
42-
parameters.forEach((key, value) {
43-
// Encode the key to prevent injection
44-
final encodedKey = Uri.encodeQueryComponent(key);
45-
46-
if (value is List) {
47-
if (value is List<String>) {
48-
for (String singleValue in value) {
49-
url += "$encodedKey=${Uri.encodeQueryComponent(singleValue)}&";
50-
}
51-
} else {
52-
for (dynamic singleValue in value) {
53-
url +=
54-
"$encodedKey=${Uri.encodeQueryComponent(singleValue.toString())}&";
55-
}
56-
}
57-
} else if (value is String) {
58-
url += "$encodedKey=${Uri.encodeQueryComponent(value)}&";
59-
} else {
60-
url += "$encodedKey=${Uri.encodeQueryComponent(value.toString())}&";
61-
}
62-
});
63-
64-
// Remove last '&' character.
65-
url = url.substring(0, url.length - 1);
14+
uri = Uri.parse(url);
15+
} on FormatException {
16+
throw ArgumentError.value(url, 'url', 'Must be a valid URL');
6617
}
6718

68-
return url;
19+
return parameters?.isNotEmpty ?? false
20+
? uri
21+
.replace(
22+
query: qs.encode(
23+
<String, dynamic>{
24+
...uri.queryParametersAll,
25+
...?parameters,
26+
},
27+
qs.EncodeOptions(
28+
listFormat: qs.ListFormat.repeat,
29+
skipNulls: false,
30+
strictNullHandling: false,
31+
),
32+
),
33+
queryParameters: null)
34+
.toString()
35+
: url;
6936
}

pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ environment:
1212

1313
dependencies:
1414
http: ^1.2.1
15+
qs_dart: ^1.3.8
16+
validators: ^3.0.0
1517

1618
dev_dependencies:
1719
lints: ^4.0.0

0 commit comments

Comments
 (0)