diff --git a/packages/router/src/url_tree.ts b/packages/router/src/url_tree.ts index 255ed74db9d..6feb42b6d6d 100644 --- a/packages/router/src/url_tree.ts +++ b/packages/router/src/url_tree.ts @@ -612,7 +612,13 @@ class UrlParser { return this.consumeOptional('#') ? decodeURIComponent(this.remaining) : null; } - private parseChildren(): {[outlet: string]: UrlSegmentGroup} { + private parseChildren(depth = 0): {[outlet: string]: UrlSegmentGroup} { + if (depth > 50) { + throw new RuntimeError( + RuntimeErrorCode.UNPARSABLE_URL, + (typeof ngDevMode === 'undefined' || ngDevMode) && 'URL is too deep', + ); + } if (this.remaining === '') { return {}; } @@ -632,12 +638,12 @@ class UrlParser { let children: {[outlet: string]: UrlSegmentGroup} = {}; if (this.peekStartsWith('/(')) { this.capture('/'); - children = this.parseParens(true); + children = this.parseParens(true, depth); } let res: {[outlet: string]: UrlSegmentGroup} = {}; if (this.peekStartsWith('(')) { - res = this.parseParens(false); + res = this.parseParens(false, depth); } if (segments.length > 0 || Object.keys(children).length > 0) { @@ -723,7 +729,7 @@ class UrlParser { } // parse `(a/b//outlet_name:c/d)` - private parseParens(allowPrimary: boolean): {[outlet: string]: UrlSegmentGroup} { + private parseParens(allowPrimary: boolean, depth: number): {[outlet: string]: UrlSegmentGroup} { const segments: {[key: string]: UrlSegmentGroup} = {}; this.capture('('); @@ -750,7 +756,7 @@ class UrlParser { outletName = PRIMARY_OUTLET; } - const children = this.parseChildren(); + const children = this.parseChildren(depth + 1); segments[outletName ?? PRIMARY_OUTLET] = Object.keys(children).length === 1 && children[PRIMARY_OUTLET] ? children[PRIMARY_OUTLET] diff --git a/packages/router/test/url_serializer.spec.ts b/packages/router/test/url_serializer.spec.ts index 8fc7a432b18..5f467d00bee 100644 --- a/packages/router/test/url_serializer.spec.ts +++ b/packages/router/test/url_serializer.spec.ts @@ -425,6 +425,22 @@ describe('url serializer', () => { it('should throw when missing closing )', () => { expect(() => url.parse('/one/(left')).toThrowError(); }); + + it('should throw when the URL is too deeply nested', () => { + let urlStr = 'a'; + for (let i = 0; i < 60; i++) { + urlStr = `p/(${urlStr})`; + } + expect(() => url.parse(`/${urlStr}`)).toThrowError(/URL is too deep/); + }); + + it('should not throw when the URL is nested within the limit', () => { + let urlStr = 'a'; + for (let i = 0; i < 40; i++) { + urlStr = `p/(${urlStr})`; + } + expect(() => url.parse(`/${urlStr}`)).not.toThrow(); + }); }); });