diff --git a/lib/src/extensions/decode.dart b/lib/src/extensions/decode.dart index 764686c..188f19c 100644 --- a/lib/src/extensions/decode.dart +++ b/lib/src/extensions/decode.dart @@ -147,10 +147,22 @@ extension _$Decode on QS { dynamic val; // Bare key without '=', interpret as null vs empty-string per strictNullHandling. if (pos == -1) { - key = options.decoder(part, charset: charset); + // Protect %2E/%2e in keys so dot-splitting never sees them as literal dots. + String keyInput = part; + if (options.allowDots && keyInput.contains('%2')) { + keyInput = + keyInput.replaceAll('%2E', '%252E').replaceAll('%2e', '%252e'); + } + key = options.decoder(keyInput, charset: charset); val = options.strictNullHandling ? null : ''; } else { - key = options.decoder(part.slice(0, pos), charset: charset); + // Protect %2E/%2e in the key slice only; values decode normally. + String keyInput = part.slice(0, pos); + if (options.allowDots && keyInput.contains('%2')) { + keyInput = + keyInput.replaceAll('%2E', '%252E').replaceAll('%2e', '%252e'); + } + key = options.decoder(keyInput, charset: charset); // Decode the substring *after* '=', applying list parsing and the configured decoder. val = Utils.apply( _parseListValue( @@ -257,7 +269,7 @@ extension _$Decode on QS { ? root.slice(1, root.length - 1) : root; final String decodedRoot = options.decodeDotInKeys - ? cleanRoot.replaceAll('%2E', '.') + ? cleanRoot.replaceAll('%2E', '.').replaceAll('%2e', '.') : cleanRoot; final int? index = int.tryParse(decodedRoot); if (!options.parseLists && decodedRoot == '') {