Skip to content
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

Reduce cases in which browsers would trigger a CORS preflight request #1955

Merged
merged 13 commits into from
Sep 15, 2023

Conversation

lonelyteapot
Copy link
Contributor

@lonelyteapot lonelyteapot commented Sep 7, 2023

Certain requests are considered "simple requests" by the CORS spec - they do not require a preflight request. The old logic unconditionally registered XMLHttpRequest.upload progress event listener, which made it impossible for requests to be considered "simple". This mostly affected Firefox, because it correctly followed the spec.

This PR adds more conditions around registering the handler, reducing dio's impact on CORS handling. Now, a request is NOT preflighted if:

  • It is a HEAD / GET / POST request
  • Its Content-Type is one of text/plain, multipart/form-data, application/x-www-form-urlencoded
  • It doesn't contain any headers which are not safelisted
  • connectTimeout is not specified
  • sendTimeout is not specified
  • onSendProgress is not specified
  • It otherwise satisfies the spec as determined by the browser

Resolves #1954.

New Pull Request Checklist

  • I have read the Documentation
  • I have searched for a similar pull request in the project and found none
  • I have updated this branch with the latest main branch to avoid conflicts (via merge from master or rebase)
  • I have added the required tests to prove the fix/feature I'm adding
  • I have updated the documentation (if necessary)
  • I have run the tests without failures
  • I have updated the CHANGELOG.md in the corresponding package

Additional context and info (if any)

This is my first time contributing to dio. I don't really know what I'm doing. I have no idea of the global implications of this change. Any assistance and scrutiny would be appreciated.

This fix has worked for my original use case.

@lonelyteapot lonelyteapot changed the title Prevent listening to XMLHttpRequest.upload when request body is empty Fix CORS issue caused by listening to XMLHttpRequest.upload events when request body is empty Sep 8, 2023
@lonelyteapot lonelyteapot marked this pull request as ready for review September 8, 2023 00:36
@lonelyteapot lonelyteapot requested a review from a team as a code owner September 8, 2023 00:36
@kuhnroyal
Copy link
Member

Interesting, I didn't know this about CORS. I think this makes sense in any case.

Just wondering about the options.onSendProgress, what is the current behavior and how does the change effect people passing this without body data.

@AlexV525
Copy link
Member

Can we run tests only for Firefox? I'm not aware of any corresponding flags.

Copy link
Member

@AlexV525 AlexV525 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From an overall look, this change affects some timeout behavior which might introduce regression. And the change itself requires tests.

@lonelyteapot
Copy link
Contributor Author

lonelyteapot commented Sep 11, 2023

@AlexV525

Can we run tests only for Firefox? I'm not aware of any corresponding flags.

I think we can with @TestOn('firefox') and by adding Firefox to dart test target platforms (right now no tests are run on Firefox as I understand). But I think this behavior should be tested for all browsers, not just Firefox.

I'll start adding a browser test which checks if OPTIONS request gets sent to the server for requests that should've been "simple".

@lonelyteapot
Copy link
Contributor Author

@kuhnroyal @AlexV525

Just wondering about the options.onSendProgress, what is the current behavior and how does the change effect people passing this without body data.

From an overall look, this change affects some timeout behavior which might introduce regression.

Can you elaborate on that? As I understand from the code comment, the onProgress event only triggers if the request body is not empty. All I did is added the same logical check above registering the event listener, so logic should remain identical. However, I don't know if this is actually true, I haven't found any proof and didn't test it manually (yet). Can you share something that can help?

uploadStopwatch.start();
if (requestStream != null) {
xhr.upload.onProgress.listen((event) {
// This event will only be triggered if a request body exists.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlexV525 Regarding the timeouts, I think this comment is the key here. If there is no body, the following code never runs. But we should add a general test for that.

@kuhnroyal
Copy link
Member

@lonelyteapot It would be great if you can setup firefox handling. I think this should be possible on ubuntu in CI.

As I understand from the code comment, the onProgress event only triggers if the request body is not empty

Yea, after looking at the code again, this became more obvious to me :)
Can you check if we have a test that verifies this behavior and if not, add one?

@lonelyteapot
Copy link
Contributor Author

I've run into a blocker while enabling tests on Firefox. Basic tests fail with CORS errors. This is due to a problem with httpbun.com which is used in tests. I've filed an issue there: sharat87/httpbun#10.

This error happens in Chrome too, but Dio tests are not affected because they launch Chrome with --disable-web-security argument. There is no such possibility in Firefox, CORS cannot be disabled in any way.

And I think web security shouldn't be disabled, so not to hide issues like this.

@kuhnroyal
Copy link
Member

Thanks for creating the httpbun issue. My last tickets there were resolved and deployed quickly.

@kuhnroyal
Copy link
Member

I have created #1966 to update test configuration for firefox.

@lonelyteapot
Copy link
Contributor Author

@kuhnroyal, thanks, but I've already done it locally for this PR. Sorry for not pushing for so long. Right now I'm debugging some stuff and making sure new changes aren't breaking things.

I think you may also want to add --headless argument to Firefox, since chrome runs in headless mode by default, while Firefox does not (ref)

@kuhnroyal
Copy link
Member

No worries. I think it would be good to have a baseline from the other PR so we don't mingle changes for the CORS problem with other fixes for Firefox tests.

@lonelyteapot
Copy link
Contributor Author

I've extended the scope of this PR. Previously there was only a check for empty request body to handle the majority of use cases, but now there are more tighter conditions to register an upload listener. The logic itself should remain unchanged, but a thorough review is needed.

I've manually verified that progress events don't get triggered when the body is empty. I don't think there's a need for an automated test.

I've also noticed that there was a bug which caused receiveTimeout not to work.

Please review

@lonelyteapot lonelyteapot changed the title Fix CORS issue caused by listening to XMLHttpRequest.upload events when request body is empty Reduce cases in which browsers would trigger a CORS preflight request Sep 13, 2023
Copy link
Member

@AlexV525 AlexV525 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the above comments are resolved to me gracefully. Thanks!

@kuhnroyal
Copy link
Member

This is interesting. Without your changes, there are failing tests on firefox in #1966.
I don't know why exactly these requests fail, but the XMLHttpRequest.onError callback is called. I would assume it is for the same reason, that we attach upload listeners without uploading anything.

Copy link
Member

@kuhnroyal kuhnroyal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great work, thanks!

@kuhnroyal kuhnroyal mentioned this pull request Sep 14, 2023
7 tasks
@kuhnroyal kuhnroyal added p: dio Targeting `dio` package platform: web labels Sep 14, 2023
@lonelyteapot
Copy link
Contributor Author

@kuhnroyal interested indeed. I don't think lack of my changes could be the cause here. I haven't encountered any issues while running tests locally. That might have been a random failure.

@kuhnroyal
Copy link
Member

kuhnroyal commented Sep 14, 2023

No, it is not random. All status code tests (4xx/5xx) fail with firefox on develop.
As soon as I add the if (requestStream != null) check, everything works.

@lonelyteapot
Copy link
Contributor Author

@kuhnroyal oh, yes, you're right. Since in these tests the server expectedly responds with non-successful HTTP status codes, preflight requests fail. Turns out there wasn't a single test for a request that should both be preflighted and fail.

@kuhnroyal kuhnroyal added this pull request to the merge queue Sep 15, 2023
Merged via the queue into cfug:main with commit 5076208 Sep 15, 2023
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p: dio Targeting `dio` package platform: web
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dio prevents avoiding CORS preflight requests in Firefox
3 participants