stringToSign function
Builds the canonical string Cloudinary signs.
Parameters sort by key, null and empty values drop out, and iterables join
with commas. Signature version 2 and above escapes & within each pair,
which is what stops a parameter value from smuggling additional parameters
into the signed string.
Implementation
String stringToSign(Map<String, dynamic> params, {int version = 2}) {
final pairs = <MapEntry<String, String>>[];
for (final entry in params.entries) {
final value = entry.value;
if (value == null) continue;
final rendered = value is Iterable ? value.join(',') : '$value';
if (rendered.isEmpty) continue;
pairs.add(MapEntry(entry.key, rendered));
}
// Sort by key, as Cloudinary's Node and Ruby SDKs do. Python sorts the
// joined "key=value" strings; the orders diverge only where one key prefixes
// another before a character below '=', which no Cloudinary parameter is.
pairs.sort((a, b) => a.key.compareTo(b.key));
return pairs
.map((p) {
final pair = '${p.key}=${p.value}';
return version >= 2 ? pair.replaceAll('&', '%26') : pair;
})
.join('&');
}