fix(http): don't encode values that are allowed in query (#9651)
This implements a new class, QueryEncoder, that provides methods for encoding keys and values of query parameter. The encoder encodes with encodeURIComponent, and then decodes a whitelist of allowed characters back to their unencoded form. BREAKING CHANGE: The changes to Http's URLSearchParams serialization now prevent encoding of these characters inside query parameters which were previously converted to percent-encoded values: @ : $ , ; + ; ? / The default encoding behavior can be overridden by extending QueryEncoder, as documented in the URLSearchParams service. Fixes #9348
This commit is contained in:
@ -14,7 +14,7 @@ function paramParser(rawParams: string = ''): Map<string, string[]> {
|
||||
if (rawParams.length > 0) {
|
||||
var params: string[] = rawParams.split('&');
|
||||
params.forEach((param: string) => {
|
||||
var split: string[] = param.split('=');
|
||||
var split: string[] = param.split('=', 2);
|
||||
var key = split[0];
|
||||
var val = split[1];
|
||||
var list = isPresent(map.get(key)) ? map.get(key) : [];
|
||||
@ -24,6 +24,27 @@ function paramParser(rawParams: string = ''): Map<string, string[]> {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
/**
|
||||
* @experimental
|
||||
**/
|
||||
export class QueryEncoder {
|
||||
encodeKey(k: string): string { return standardEncoding(k); }
|
||||
|
||||
encodeValue(v: string): string { return standardEncoding(v); }
|
||||
}
|
||||
|
||||
function standardEncoding(v: string): string {
|
||||
return encodeURIComponent(v)
|
||||
.replace(/%40/gi, '@')
|
||||
.replace(/%3A/gi, ':')
|
||||
.replace(/%24/gi, '$')
|
||||
.replace(/%2C/gi, ',')
|
||||
.replace(/%3B/gi, ';')
|
||||
.replace(/%2B/gi, '+')
|
||||
.replace(/%3D/gi, ';')
|
||||
.replace(/%3F/gi, '?')
|
||||
.replace(/%2F/gi, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Map-like representation of url search parameters, based on
|
||||
@ -33,11 +54,39 @@ function paramParser(rawParams: string = ''): Map<string, string[]> {
|
||||
* - appendAll()
|
||||
* - replaceAll()
|
||||
*
|
||||
* This class accepts an optional second parameter of ${@link QueryEncoder},
|
||||
* which is used to serialize parameters before making a request. By default,
|
||||
* `QueryEncoder` encodes keys and values of parameters using `encodeURIComponent`,
|
||||
* and then un-encodes certain characters that are allowed to be part of the query
|
||||
* according to IETF RFC 3986: https://tools.ietf.org/html/rfc3986.
|
||||
*
|
||||
* These are the characters that are not encoded: `! $ \' ( ) * + , ; A 9 - . _ ~ ? /`
|
||||
*
|
||||
* If the set of allowed query characters is not acceptable for a particular backend,
|
||||
* `QueryEncoder` can be subclassed and provided as the 2nd argument to URLSearchParams.
|
||||
*
|
||||
* ```
|
||||
* import {URLSearchParams, QueryEncoder} from '@angular/http';
|
||||
* class MyQueryEncoder extends QueryEncoder {
|
||||
* encodeKey(k: string): string {
|
||||
* return myEncodingFunction(k);
|
||||
* }
|
||||
*
|
||||
* encodeValue(v: string): string {
|
||||
* return myEncodingFunction(v);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* let params = new URLSearchParams('', new MyQueryEncoder());
|
||||
* ```
|
||||
* @experimental
|
||||
*/
|
||||
export class URLSearchParams {
|
||||
paramsMap: Map<string, string[]>;
|
||||
constructor(public rawParams: string = '') { this.paramsMap = paramParser(rawParams); }
|
||||
constructor(
|
||||
public rawParams: string = '', private queryEncoder: QueryEncoder = new QueryEncoder()) {
|
||||
this.paramsMap = paramParser(rawParams);
|
||||
}
|
||||
|
||||
clone(): URLSearchParams {
|
||||
var clone = new URLSearchParams();
|
||||
@ -133,7 +182,9 @@ export class URLSearchParams {
|
||||
toString(): string {
|
||||
var paramsList: string[] = [];
|
||||
this.paramsMap.forEach((values, k) => {
|
||||
values.forEach(v => paramsList.push(encodeURIComponent(k) + '=' + encodeURIComponent(v)));
|
||||
values.forEach(
|
||||
v => paramsList.push(
|
||||
this.queryEncoder.encodeKey(k) + '=' + this.queryEncoder.encodeValue(v)));
|
||||
});
|
||||
return paramsList.join('&');
|
||||
}
|
||||
|
Reference in New Issue
Block a user