pillarjs / path-to-regexp

Turn a path string such as `/user/:name` into a regular expression

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

incorrect group value for `:name*` pattern

wanderview opened this issue · comments

If you run:

const { pathToRegexp } = require('path-to-regexp');
pathToRegexp(':name*')

You will get this regexp:

/^([^\/#\?]+?)*[\/#\?]?$/i

This regexp does not produce a proper matched group value, though. From devtools:

var r = /^([^\/#\?]+?)*[\/#\?]?$/
undefined
r.exec('foobar')
(2) ['foobar', 'r', index: 0, input: 'foobar', groups: undefined]

The matched value is "r" instead of "foobar".

I think path-to-regexp should instead make the current grouping in the regexp non-matching and add a matching group around the repeating * modifier. Like this:

/^((?:[^\/#\?]+?)*)[\/#\?]?$/i

This is my plan for fixing this in URLPattern.

I'd like to open a pull request to tackle this. Will add the respective tests to catch any regressions.

It looks like the suggested fix cannot be generally applied. When this expression is changed:

route += `(${token.pattern})${token.modifier}`;

to:

route += `((?:${token.pattern})${token.modifier})`;

It will result in arbitrary characters being caught in the non-matching group:

  • Path: /user(s)?/:userId
  • RegExp: ^\/user((?:s)?)\/([^\\/#\\?]+?)
  • Match:
/^\/user((?:s)?)\/([^\\/#\\?]+?)/.exec('/user/123')
(3) ["/user/1", "", "1", index: 0, input: "/user/123", groups: undefined]

This is due to the (s)? token getting wrapped in the non-matching group, becoming user((?:s)?) instead of user(s)?.

We can apply the suggested fix only in the case token.modifier === '*'. This retains the previous behavior and fixes the issue above.

I think the same can be said about the + modifier: it has the same issue of not matching the entire path parameter and should also be taken into account within this fix.

const { pathToRegexp } = require('path-to-regexp');
const exp = pathToRegexp(':name*')

exp.exec('foobar')
(2) ['foobar', 'r', index: 0, input: 'foobar', groups: undefined]

The fix for both :name* and :name+ paths has been provided and respective tests added in #261.

I think the fix should also be applied for :name?. Basically if there is a modifier present at all. When there is no modifier present then the old logic is correct. Does that agree with what you are seeing?

I added an end anchor $ to your regexp above and I get:

/^\/user((?:s)?)\/([^\\/#\\?]+?)$/.exec('/user/123')
(3) ['/user/123', '', '123', index: 0, input: '/user/123', groups: undefined]
/^\/user((?:s)?)\/([^\\/#\\?]+?)$/.exec('/users/123')
(3) ['/users/123', 's', '123', index: 0, input: '/users/123', groups: undefined]

I’ll add “+” scenario later next week as well.

I think the fix should also be applied for :name?

You can ignore this feedback from me above. You are indeed correct we don't need the extra non-capturing group for the optional modifier.

The fix implemented in #261 is ready for review.

FWIW, I'd love a fix for this as well. Just stumbled on the problem. At least for me it seems to only happen with no leading '/'. So you can work around the problem by prefixing the pattern and target with a '/'. Bit of a hassle but seems to work.
Otherwise, awesome library! Thanks.