diff --git a/packages/@ember/routing/history-location.ts b/packages/@ember/routing/history-location.ts index 42019c63b54..786a8fab83d 100644 --- a/packages/@ember/routing/history-location.ts +++ b/packages/@ember/routing/history-location.ts @@ -1,7 +1,7 @@ import EmberObject from '@ember/object'; import { assert } from '@ember/debug'; import type { default as EmberLocation, UpdateCallback } from '@ember/routing/location'; -import { getHash } from './lib/location-utils'; +import { escapeRegExp, getHash } from './lib/location-utils'; /** @module @ember/routing/history-location @@ -134,8 +134,8 @@ export default class HistoryLocation extends EmberObject implements EmberLocatio // remove baseURL and rootURL from start of path let url = path - .replace(new RegExp(`^${baseURL}(?=/|$)`), '') - .replace(new RegExp(`^${rootURL}(?=/|$)`), '') + .replace(new RegExp(`^${escapeRegExp(baseURL)}(?=/|$)`), '') + .replace(new RegExp(`^${escapeRegExp(rootURL)}(?=/|$)`), '') .replace(/\/\//g, '/'); // remove extra slashes let search = location.search || ''; diff --git a/packages/@ember/routing/lib/location-utils.ts b/packages/@ember/routing/lib/location-utils.ts index 0fe26430886..606f9f322b6 100644 --- a/packages/@ember/routing/lib/location-utils.ts +++ b/packages/@ember/routing/lib/location-utils.ts @@ -47,3 +47,14 @@ export function getFullPath(location: Location): string { export function replacePath(location: Location, path: string): void { location.replace(location.origin + path); } + +const REGEXP_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g; + +/** + @private + + Escapes RegExp special characters so a value can be matched literally. +*/ +export function escapeRegExp(string: string): string { + return string.replace(REGEXP_SPECIAL_CHARS, '\\$&'); +} diff --git a/packages/@ember/routing/none-location.ts b/packages/@ember/routing/none-location.ts index 1e43888207f..e396d8ef335 100644 --- a/packages/@ember/routing/none-location.ts +++ b/packages/@ember/routing/none-location.ts @@ -1,6 +1,7 @@ import EmberObject from '@ember/object'; import { assert } from '@ember/debug'; import type { default as EmberLocation, UpdateCallback } from '@ember/routing/location'; +import { escapeRegExp } from './lib/location-utils'; /** @module @ember/routing/none-location @@ -63,7 +64,7 @@ export default class NoneLocation extends EmberObject implements EmberLocation { rootURL = rootURL.replace(/\/$/, ''); // remove rootURL from url - return path.replace(new RegExp(`^${rootURL}(?=/|$)`), ''); + return path.replace(new RegExp(`^${escapeRegExp(rootURL)}(?=/|$)`), ''); } /** diff --git a/packages/@ember/routing/tests/location/history_location_test.js b/packages/@ember/routing/tests/location/history_location_test.js index 984dad9c778..8ca12389519 100644 --- a/packages/@ember/routing/tests/location/history_location_test.js +++ b/packages/@ember/routing/tests/location/history_location_test.js @@ -232,6 +232,22 @@ moduleFor( assert.equal(location.getURL(), '/bars/baz'); } + ['@test HistoryLocation.getURL() treats regex special characters in rootURL literally']( + assert + ) { + HistoryTestLocation.reopen({ + init() { + this._super(...arguments); + set(this, 'location', mockBrowserLocation('/appXv2/posts')); + set(this, 'rootURL', '/app.v2/'); + }, + }); + + createLocation(); + + assert.equal(location.getURL(), '/appXv2/posts'); + } + ['@test HistoryLocation.getURL() returns the current url, does not remove baseURL if its not at start of url']( assert ) {