diff --git a/CHANGELOG.md b/CHANGELOG.md index 134ced9b4..37e396624 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ version, see the upgrade guides in `docs/`. - **router:** The text after `**` is matched literally, e.g. `/f/**/x.json` doesn't match `/f/a/xajson` anymore, and params after `**` can have any name, e.g. `:my-id`, which threw an `Invalid capture group name` error (#333). - **router:** Params starting with `$` keep their names next to wildcards, e.g. `$x` in `/a/*/:$x`, instead of being renamed to `$` or `$$` and replacing the wildcard's value (#334). - **admin:** Everything behind the topmost dialog is inert: the page, the navigation, the sidebar and other dialogs. Select All in a dialog no longer selects the values behind it, and assistive technology can't reach them either. +- **utils:** `isUrl()` returns `false` for invalid URLs with long paths right away, where the time doubled with each path segment, taking seconds for 28 segments. URLs ending in a lone `?` or `#` are valid too (#348). ## 4.7.2 (2026-10-16) diff --git a/packages/utils/src/string/isUrl.js b/packages/utils/src/string/isUrl.js index 94112309c..e9668c3f4 100644 --- a/packages/utils/src/string/isUrl.js +++ b/packages/utils/src/string/isUrl.js @@ -1,6 +1,3 @@ -// TODO: There's a contradiction between isUrl() and isAbsoluteUrl(), where -// isUrl() returns `true` for 'google.com', and isAbsoluteUrl() returns `false`! - const isUrlRegExp = new RegExp( '^((https?|ftps?|mailto|rtsp|mms)?://)?' + // user:pass@ `(([0-9a-z_!~*'().&=+$%-]+:)?[0-9a-z_!~*'().&=+$%-]*@)?` + @@ -13,8 +10,7 @@ const isUrlRegExp = new RegExp( '([a-z]{2,61}|xn--[0-9a-z][0-9a-z-]{0,61}[0-9a-z])' + ')' + // top level domain. '(:[0-9]{1,5})?' + // port - '((/?)|' + // allow ending in a slash - `([/?#][0-9a-z_!~*'().;:@&=+$,%/?#-]+)+/?)$`, // path + `([/?#][0-9a-z_!~*'().;:@&=+$,%/?#-]*)?$`, // path 'i' ) diff --git a/packages/utils/src/string/isUrl.test.js b/packages/utils/src/string/isUrl.test.js index a6948ed96..488353414 100644 --- a/packages/utils/src/string/isUrl.test.js +++ b/packages/utils/src/string/isUrl.test.js @@ -87,6 +87,11 @@ describe('isUrl()', () => { }) } ) + + it('returns false for long invalid paths without backtracking', () => { + // Exponential backtracking in the path takes seconds here: + expect(isUrl('http://a.com' + '/a'.repeat(27) + ' ')).toBe(false) + }, 1000) }) // https://data.iana.org/TLD/tlds-alpha-by-domain.txt