mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 12:57:46 +00:00
[now-routing-utils] Fix error handling on path-to-regexp error (#3523)
This PR fixes an issue where certain patterns caused `path-to-regexp` to throw when it should be returning an error object. The fix is to make sure all inputs work properly with `path-to-regexp` before continuing.
This commit is contained in:
33
errors/invalid-route-source.md
Normal file
33
errors/invalid-route-source.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Invalid Custom Route `source`
|
||||||
|
|
||||||
|
#### Why This Error Occurred
|
||||||
|
|
||||||
|
When defining custom routes a route was added that causes an error during parsing. This can be due to trying to use normal `RegExp` syntax like negative lookaheads (`?!exclude`) without following `path-to-regexp`'s syntax for it.
|
||||||
|
|
||||||
|
#### Possible Ways to Fix It
|
||||||
|
|
||||||
|
Wrap the `RegExp` part of your `source` as an un-named parameter.
|
||||||
|
|
||||||
|
**Before**
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
source: '/feedback/(?!general)',
|
||||||
|
destination: '/feedback/general'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After**
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
source: '/feedback/((?!general).*)',
|
||||||
|
destination: '/feedback/general'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Useful Links
|
||||||
|
|
||||||
|
- [path-to-regexp](https://github.com/pillarjs/path-to-regexp/tree/v6.1.0)
|
||||||
|
- [named parameters](https://github.com/pillarjs/path-to-regexp/blob/v6.1.0/Readme.md#named-parameters)
|
||||||
|
- [un-named paramters](https://github.com/pillarjs/path-to-regexp/blob/v6.1.0/Readme.md#unnamed-parameters)
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
convertRedirects,
|
convertRedirects,
|
||||||
convertHeaders,
|
convertHeaders,
|
||||||
convertTrailingSlash,
|
convertTrailingSlash,
|
||||||
|
sourceToRegex,
|
||||||
} from './superstatic';
|
} from './superstatic';
|
||||||
|
|
||||||
export { getCleanUrls } from './superstatic';
|
export { getCleanUrls } from './superstatic';
|
||||||
@@ -142,6 +143,18 @@ function checkRegexSyntax(src: string): NowErrorNested | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkPatternSyntax(src: string): NowErrorNested | null {
|
||||||
|
try {
|
||||||
|
sourceToRegex(src);
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
message: `Invalid pattern: "${src}"`,
|
||||||
|
src,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function createNowError(
|
function createNowError(
|
||||||
code: string,
|
code: string,
|
||||||
msg: string,
|
msg: string,
|
||||||
@@ -231,16 +244,29 @@ export function getTransformedRoutes({
|
|||||||
|
|
||||||
if (typeof redirects !== 'undefined') {
|
if (typeof redirects !== 'undefined') {
|
||||||
const code = 'invalid_redirects';
|
const code = 'invalid_redirects';
|
||||||
const errors = redirects
|
const errorsRegex = redirects
|
||||||
.map(r => checkRegexSyntax(r.source))
|
.map(r => checkRegexSyntax(r.source))
|
||||||
.filter(notEmpty);
|
.filter(notEmpty);
|
||||||
if (errors.length > 0) {
|
if (errorsRegex.length > 0) {
|
||||||
return {
|
return {
|
||||||
routes,
|
routes,
|
||||||
error: createNowError(
|
error: createNowError(
|
||||||
code,
|
code,
|
||||||
'Redirect `source` contains invalid regex',
|
'Redirect `source` contains invalid regex. Read more: https://err.sh/now/invalid-route-source',
|
||||||
errors
|
errorsRegex
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const errorsPattern = redirects
|
||||||
|
.map(r => checkPatternSyntax(r.source))
|
||||||
|
.filter(notEmpty);
|
||||||
|
if (errorsPattern.length > 0) {
|
||||||
|
return {
|
||||||
|
routes,
|
||||||
|
error: createNowError(
|
||||||
|
code,
|
||||||
|
'Redirect `source` contains invalid pattern. Read more: https://err.sh/now/invalid-route-source',
|
||||||
|
errorsPattern
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -265,16 +291,29 @@ export function getTransformedRoutes({
|
|||||||
|
|
||||||
if (typeof rewrites !== 'undefined') {
|
if (typeof rewrites !== 'undefined') {
|
||||||
const code = 'invalid_rewrites';
|
const code = 'invalid_rewrites';
|
||||||
const errors = rewrites
|
const errorsRegex = rewrites
|
||||||
.map(r => checkRegexSyntax(r.source))
|
.map(r => checkRegexSyntax(r.source))
|
||||||
.filter(notEmpty);
|
.filter(notEmpty);
|
||||||
if (errors.length > 0) {
|
if (errorsRegex.length > 0) {
|
||||||
return {
|
return {
|
||||||
routes,
|
routes,
|
||||||
error: createNowError(
|
error: createNowError(
|
||||||
code,
|
code,
|
||||||
'Rewrites `source` contains invalid regex',
|
'Rewrites `source` contains invalid regex. Read more: https://err.sh/now/invalid-route-source',
|
||||||
errors
|
errorsRegex
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const errorsPattern = rewrites
|
||||||
|
.map(r => checkPatternSyntax(r.source))
|
||||||
|
.filter(notEmpty);
|
||||||
|
if (errorsPattern.length > 0) {
|
||||||
|
return {
|
||||||
|
routes,
|
||||||
|
error: createNowError(
|
||||||
|
code,
|
||||||
|
'Rewrites `source` contains invalid pattern. Read more: https://err.sh/now/invalid-route-source',
|
||||||
|
errorsPattern
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ export function convertTrailingSlash(enable: boolean, status = 308): Route[] {
|
|||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sourceToRegex(source: string): { src: string; segments: string[] } {
|
export function sourceToRegex(
|
||||||
|
source: string
|
||||||
|
): { src: string; segments: string[] } {
|
||||||
const keys: Key[] = [];
|
const keys: Key[] = [];
|
||||||
const r = pathToRegexp(source, keys, { strict: true });
|
const r = pathToRegexp(source, keys, { strict: true });
|
||||||
const segments = keys.map(k => k.name).filter(isString);
|
const segments = keys.map(k => k.name).filter(isString);
|
||||||
|
|||||||
18
packages/now-routing-utils/test/index.spec.js
vendored
18
packages/now-routing-utils/test/index.spec.js
vendored
@@ -568,6 +568,15 @@ describe('getTransformedRoutes', () => {
|
|||||||
assert.equal(actual.error.code, 'invalid_redirects');
|
assert.equal(actual.error.code, 'invalid_redirects');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should error when redirects is invalid pattern', () => {
|
||||||
|
const nowConfig = {
|
||||||
|
redirects: [{ source: '/:?', destination: '/file.html' }],
|
||||||
|
};
|
||||||
|
const actual = getTransformedRoutes({ nowConfig });
|
||||||
|
assert.notEqual(actual.error, null);
|
||||||
|
assert.equal(actual.error.code, 'invalid_redirects');
|
||||||
|
});
|
||||||
|
|
||||||
test('should error when rewrites is invalid regex', () => {
|
test('should error when rewrites is invalid regex', () => {
|
||||||
const nowConfig = {
|
const nowConfig = {
|
||||||
rewrites: [{ source: '^/(*.)\\.html$', destination: '/file.html' }],
|
rewrites: [{ source: '^/(*.)\\.html$', destination: '/file.html' }],
|
||||||
@@ -577,6 +586,15 @@ describe('getTransformedRoutes', () => {
|
|||||||
assert.equal(actual.error.code, 'invalid_rewrites');
|
assert.equal(actual.error.code, 'invalid_rewrites');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should error when rewrites is invalid pattern', () => {
|
||||||
|
const nowConfig = {
|
||||||
|
rewrites: [{ source: '/:?', destination: '/file.html' }],
|
||||||
|
};
|
||||||
|
const actual = getTransformedRoutes({ nowConfig });
|
||||||
|
assert.notEqual(actual.error, null);
|
||||||
|
assert.equal(actual.error.code, 'invalid_rewrites');
|
||||||
|
});
|
||||||
|
|
||||||
test('should normalize all redirects before rewrites', () => {
|
test('should normalize all redirects before rewrites', () => {
|
||||||
const nowConfig = {
|
const nowConfig = {
|
||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user