Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/router/src/apply_redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ export class ApplyRedirects {
): UrlSegmentGroup {
const updatedSegments = this.createSegments(redirectTo, group.segments, segments, posParams);

let children: {[n: string]: UrlSegmentGroup} = {};
// Keyed by outlet name, which can be `__proto__`, so use a null-prototype map.
let children: {[n: string]: UrlSegmentGroup} = Object.create(null);
Object.entries(group.children).forEach(([name, child]) => {
children[name] = this.createSegmentGroup(redirectTo, child, segments, posParams);
});
Expand Down
6 changes: 4 additions & 2 deletions packages/router/src/create_url_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,8 @@ function replaceSegment(
oldSegment: UrlSegmentGroup,
newSegment: UrlSegmentGroup,
): UrlSegmentGroup {
const children: {[key: string]: UrlSegmentGroup} = {};
// Keyed by outlet name, which can be `__proto__`, so use a null-prototype map.
const children: {[key: string]: UrlSegmentGroup} = Object.create(null);
Object.entries(current.children).forEach(([outletName, c]) => {
if (c === oldSegment) {
children[outletName] = newSegment;
Expand Down Expand Up @@ -419,7 +420,8 @@ function updateSegmentGroupChildren(
return new UrlSegmentGroup(segmentGroup.segments, {});
} else {
const outlets = getOutlets(commands);
const children: {[key: string]: UrlSegmentGroup} = {};
// Keyed by outlet name, which can be `__proto__`, so use a null-prototype map.
const children: {[key: string]: UrlSegmentGroup} = Object.create(null);
// If the set of commands applies to anything other than the primary outlet and the child
// segment is an empty path primary segment on its own, we want to apply the commands to the
// empty child path rather than here. The outcome is that the empty primary child is effectively
Expand Down
9 changes: 7 additions & 2 deletions packages/router/src/url_tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,11 @@ class UrlParser {

// parse `(a/b//outlet_name:c/d)`
private parseParens(allowPrimary: boolean, depth: number): {[outlet: string]: UrlSegmentGroup} {
const segments: {[key: string]: UrlSegmentGroup} = {};
// The outlet name is taken verbatim from the URL, so it can be `__proto__`. Indexing a plain
// object with that key assigns through the inherited `__proto__` setter instead of creating an
// outlet, which drops the outlet and mutates the map's prototype (and throws under Node's
// `--disable-proto=throw`). A null-prototype map makes `__proto__` an ordinary key.
const segments: {[key: string]: UrlSegmentGroup} = Object.create(null);
this.capture('(');

while (!this.consumeOptional(')') && this.remaining.length > 0) {
Expand Down Expand Up @@ -838,7 +842,8 @@ export function createRoot(rootCandidate: UrlSegmentGroup): UrlSegmentGroup {
* root but the `a` route lives under an empty path primary route.
*/
export function squashSegmentGroup(segmentGroup: UrlSegmentGroup): UrlSegmentGroup {
const newChildren: Record<string, UrlSegmentGroup> = {};
// Keyed by outlet name, which can be `__proto__`, so use a null-prototype map (see `parseParens`).
const newChildren: Record<string, UrlSegmentGroup> = Object.create(null);
for (const [childOutlet, child] of Object.entries(segmentGroup.children)) {
const childCandidate = squashSegmentGroup(child);
// moves named children in an empty path primary child into this group
Expand Down
11 changes: 11 additions & 0 deletions packages/router/test/url_serializer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ describe('url serializer', () => {
expect(url.serialize(tree)).toEqual('/one(left:two/three)');
});

it('should parse a secondary segment named "__proto__"', () => {
const tree = url.parse('/one(__proto__:two)');

expectSegment(tree.root.children[PRIMARY_OUTLET], 'one');
expectSegment(tree.root.children['__proto__'], 'two');
expect(tree.root.numberOfChildren).toEqual(2);
expect(Object.getPrototypeOf(tree.root.children)).toBeNull();

expect(url.serialize(tree)).toEqual('/one(__proto__:two)');
});

it('should parse an empty secondary segment group', () => {
const tree = url.parse('/one()');

Expand Down
Loading