-
Notifications
You must be signed in to change notification settings - Fork 57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix 'isRequestOriginAllowed' returns mixed results when regex is used #152
Fix 'isRequestOriginAllowed' returns mixed results when regex is used #152
Conversation
Pull Request Test Coverage Report for Build 1278185507
💛 - Coveralls |
Pull Request Test Coverage Report for Build 1882066914
💛 - Coveralls |
index.js
Outdated
@@ -210,7 +210,7 @@ function isRequestOriginAllowed (reqOrigin, allowedOrigin) { | |||
} else if (typeof allowedOrigin === 'string') { | |||
return reqOrigin === allowedOrigin | |||
} else if (allowedOrigin instanceof RegExp) { | |||
return allowedOrigin.test(reqOrigin) | |||
return Boolean(reqOrigin.match(allowedOrigin)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't we just do?
return Boolean(reqOrigin.match(allowedOrigin)) | |
allowedOrigin.lastIndex=0 | |
return allowedOrigin.test(reqOrigin) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zekth I don't know, I'm not sure about the exact implementation of Node.js async/thread behaviour. E.g. if two requests are triggered simultaneously, can we ensure that the following does not occur?:
- First request is running .test function
- Second request sets 'lastIndex' to 0
- First requests finds match and updates lastIndex again
- Second requests then uses the updated lastIndex
If we are sure the above cannot happen, and these lines are always executed synchronously without interruption, we can also use your solution since it's less invasive.
Please let me know what you think, then I'll update the commit...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cannot happen as we are not working with multithreading but asynchronization. As those operations happen in the same scope there would not be any collision regarding the change of the regex.
On the other hand this shows a problem that might appear, which is passing a reference of the regex in the plugin like:
const reg = /(example|other)\.com/gi
fastify.register(cors, { origin: reg })
In this case, if reg
is mutated anywhere else in the app we'll have a collision. So in this case the only way to avoid this is to do something like
if (allowedOrigin instanceof RegExp) {
const testReg = RegExp(allowedOrigin.source, allowedOrigin.flags)
return testReg.test(reqOrigin)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct. I just tested the performance implications of three variants:
const sourceRegex = new RegExp('example.com', 'gi');
const testString = 'https://www.example.com/';
function timeRegular() { // Takes about 33ms
start = performance.now();
for (let i=0; i < 1000000; i++) {
sourceRegex.lastIndex = 0;
sourceRegex.test(testString);
}
end = performance.now()
return end - start;
}
function timeCopy() { // Takes ~ 160 ms
start = performance.now();
for (let i=0; i < 1000000; i++) {
const testRegex = new RegExp(sourceRegex.source, sourceRegex.flags);
testRegex.test(testString);
}
end = performance.now()
return end - start;
}
function timeMatch() { // Takes ~100ms
start = performance.now();
for (let i=0; i < 1000000; i++) {
testString.match(sourceRegex);
}
end = performance.now()
return end - start;
}
The copying seems to add some overhead to the process (factor 5 roughly), but for a million checks it's only about 130ms extra.
I also checked here, and it seems like 'lastIndex' is the only editable attribute for a RegExp object.
If you think this additional overhead compares positively to being sure that 'lastIndex' is zero at the start, please let me know, I'll implement your last suggestion.
Personally I would go with your first suggestion (Which is also the fastest from the above tests), since I wouldn't even want to promote being able to count on the 'lastIndex' staying zero anywhere in the plugin, but that may be just my personal preference...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@climba03003 I would like to have your opinion on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will go for editing the lastIndex
. If you think it may alter by someone in the middle of run. You can clone it once before passing down to the check. It should be in https://github.com/fastify/fastify-cors/pull/152/files#R101
@Tom-Brouwer how's this going? |
The 'isRequestOriginAllowed' function returned random results for global regexes, since the .test function was used, and the output of this function depends on previous invocations of the function. By resetting the 'lastIndex', every invocation of the function should now return the same result. This also updates the corresponding test to use a global regex, and do the same validation twice, in order to check consistency fixes #151
Hi @Fdawgs , Sorry, totally forgot about this. I've pushed an update in-line with the last comments (So implementing the 'lastIndex' solution). Please review. Best regards, Tom Brouwer Test Output
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✌️ lgtm
Fixes #151
This pull-request contains the code update (Regex.test replaced by String.match function), as well as the update to the tests (The tests now use a global regex, and repeats the test two times to test consistency)
Test Output
Checklist
npm run test
and-> Note: There is no 'benchmark' script in package.jsonnpm run benchmark
and the Code of conduct