From ae27af73992fc95a7a00b721811e9b5a4c96928a Mon Sep 17 00:00:00 2001 From: Jason Aden Date: Thu, 6 Jul 2017 17:10:25 -0700 Subject: [PATCH] fix(router): encode URLs the same way AngularJS did (closer to spec) (#17890) fixes #16067 --- packages/router/src/url_tree.ts | 18 +++++++++++++++++- packages/router/test/url_serializer.spec.ts | 16 ++++++++++++++++ packages/router/upgrade/src/upgrade.ts | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/router/src/url_tree.ts b/packages/router/src/url_tree.ts index 48a792d892..587c889725 100644 --- a/packages/router/src/url_tree.ts +++ b/packages/router/src/url_tree.ts @@ -325,8 +325,24 @@ function serializeSegment(segment: UrlSegmentGroup, root: boolean): string { } } +/** + * This method is intended for encoding *key* or *value* parts of query component. We need a custom + * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be + * encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ export function encode(s: string): string { - return encodeURIComponent(s); + return encodeURIComponent(s) + .replace(/%40/g, '@') + .replace(/%3A/gi, ':') + .replace(/%24/g, '$') + .replace(/%2C/gi, ',') + .replace(/%3B/gi, ';'); } export function decode(s: string): string { diff --git a/packages/router/test/url_serializer.spec.ts b/packages/router/test/url_serializer.spec.ts index 3314f9fc69..a318011b10 100644 --- a/packages/router/test/url_serializer.spec.ts +++ b/packages/router/test/url_serializer.spec.ts @@ -219,6 +219,22 @@ describe('url serializer', () => { expect(url.serialize(tree)).toEqual(u); }); + it('should encode query params leaving sub-delimiters intact', () => { + const percentChars = '/?#[]&+= '; + const percentCharsEncoded = '%2F%3F%23%5B%5D%26%2B%3D%20'; + const intactChars = '!$\'()*,;:'; + const params = percentChars + intactChars; + const paramsEncoded = percentCharsEncoded + intactChars; + const mixedCaseString = 'sTrInG'; + + expect(percentCharsEncoded).toEqual(encode(percentChars)); + expect(intactChars).toEqual(encode(intactChars)); + // Verify it replaces repeated characters correctly + expect(paramsEncoded + paramsEncoded).toEqual(encode(params + params)); + // Verify it doesn't change the case of alpha characters + expect(mixedCaseString + paramsEncoded).toEqual(encode(mixedCaseString + params)); + }); + it('should encode/decode fragment', () => { const u = `/one#${encodeURI("one two=three four")}`; const tree = url.parse(u); diff --git a/packages/router/upgrade/src/upgrade.ts b/packages/router/upgrade/src/upgrade.ts index 12b511000e..82336fd35d 100644 --- a/packages/router/upgrade/src/upgrade.ts +++ b/packages/router/upgrade/src/upgrade.ts @@ -71,6 +71,6 @@ export function setUpLocationSync(ngUpgrade: UpgradeModule) { ngUpgrade.$injector.get('$rootScope') .$on('$locationChangeStart', (_: any, next: string, __: string) => { url.href = next; - router.navigateByUrl(url.pathname + url.search); + router.navigateByUrl(url.pathname + url.search + url.hash); }); }