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

[Android] Allow non-ascii header values & add utf-8 filename fallback #35060

Closed

Conversation

robertying
Copy link
Contributor

@robertying robertying commented Oct 24, 2022

Summary

Fix #31537: [Android] React Native strips non-ASCII characters from HTTP headers

Changelog

[Android] [Changed] - Allow non-ascii header values on Android and add utf-8 filename fallback in FormData

Test Plan

  1. Clone the react-native repo.
  2. Build the rn-tester app.
  3. Prepare tests
    1. Add android:usesCleartextTraffic="true" to AndroidManifest.xml
    2. Use the following code as a server:
       const http = require('http');
       
       const requestListener = function (req, res) {
           // raw header value
           console.log(req.headers['content-disposition']);
           // nodejs assumes the header value is ISO-8859-1 encoded
           console.log(Buffer.from(req.headers['content-disposition'], 'latin1').toString('utf-8'));
           // decode encoded header value if it's sent as UTF-8
           console.log(decodeURI(req.headers['content-disposition']));
           res.writeHead(200);
           res.end();
       };
       
       const server = http.createServer(requestListener);
       server.listen(3000);
    3. Run adb reverse tcp:3000 tcp:3000 to connect the 3000 port on the emulator if necessary.
  4. Edit RNTesterAppShared.js to include test code:
      useEffect(() => {
        fetch('http://localhost:3000/', {
          headers: {
            'Content-Type': 'multipart/form-data; charset=utf-8',
            'Content-Disposition': `attachment; filename*=utf-8''${encodeURI(
              'filename测试abc.jpg',
            )}`,
          },
        }).then(res => {
          console.log(res.ok);
        });
        fetch('http://localhost:3000/', {
          headers: {
            'Content-Type': 'multipart/form-data; charset=utf-8',
            'Content-Disposition': `attachment; filename="filename测试abc.jpg"`,
          },
        }).then(res => {
          console.log(res.ok);
        });
      }, []);
  5. Both requests should succeed; without the fix, the second request received by the server will not have the utf-8 characters "测试" in the header value.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 24, 2022
@react-native-bot react-native-bot added the Platform: Android Android applications. label Oct 24, 2022
@facebook-github-bot
Copy link
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@cipolleschi
Copy link
Contributor

Hi @robertying, Thank you for doing this.
The filtering has been introduced by @dryganets in this PR #21231 4 years ago.

We are wondering whether this change could break some webservers that don’t understand the utf8- marker. @dryganets, could you help out with the review?

@robertying
Copy link
Contributor Author

robertying commented Oct 25, 2022

@cipolleschi Thanks for taking a look. The detail was discussed in this issue #31537 when I first opened it:

As I investigate, I find lines of code that should be responsible ebc89bf/ReactAndroid/src/main/java/com/facebook/react/modules/network/HeaderUtil.java.

These methods are to weaken okhttp's too strict rules on headers, but also bring this unwanted behavior.

FYI, okhttp has supported non-ASCII headers from square/okhttp#4296.

These two stripping methods may need to be looked into and synced with the upstream to reflect okhttp's changes.

This change was brought by #21231 in response to the issue square/okhttp#2802. The issue has been discussed in another one square/okhttp#3876, which was fixed by square/okhttp#4296.

The filtering was done before okhttp fixed the utf-8 thing. Since okhttp has already fixed the utf-8 header value handling, we should remove the workaround that is not invalid anymore.


As for whether it will break web servers, this usage is actually included in RFC 5987:

This specification suggests that a
parameter using the extended syntax takes precedence. This would
allow producers to use both formats without breaking recipients that
do not understand the extended syntax yet.

Example:

 foo: bar; title="EURO exchange rates";
           title*=utf-8''%e2%82%ac%20exchange%20rates

There is also a note:

Note: at the time of this writing, many implementations failed to
ignore the form they do not understand, or prioritize the ASCII
form although the extended syntax was present.

However, the RFC was written in 2010 and I believe utf-8 support on the server side should be adequate now; and even if the server doesn't understand the extended syntax, it should be able to pick up the original filename as it used to.

Copy link
Contributor

@dryganets dryganets left a comment

Choose a reason for hiding this comment

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

The change looks good to me.
The behavior hasn't been handled by okhttp at the moment.

@robertying
Copy link
Contributor Author

@cipolleschi it seems we could move forward since Sergei has commented. Please let me know if there's anything else that needs addressing!

@cipolleschi
Copy link
Contributor

@robertying Sorry for the delay, busy days.

Can I ask two favours, first:

  1. Could you go to https://app.circleci.com and log into their website? This should restart the CI pipelines. I know it is annoying, I'm working with them so that we can automate this step
  2. Could you rebase onto main?

After that, when the CI is green, I can reimport it!

Thank you so much and sorry again for the delay.

@analysis-bot
Copy link

analysis-bot commented Nov 8, 2022

Platform Engine Arch Size (bytes) Diff
ios - universal n/a --

Base commit: b7a85b5
Branch: main

@pull-bot
Copy link

pull-bot commented Nov 8, 2022

PR build artifact for 5d31b62 is ready.
To use, download tarball from "Artifacts" tab in this CircleCI job then run yarn add <path to tarball> in your React Native project.

@analysis-bot
Copy link

analysis-bot commented Nov 8, 2022

Platform Engine Arch Size (bytes) Diff
android hermes arm64-v8a 8,519,584 +172
android hermes armeabi-v7a 7,835,878 +171
android hermes x86 8,998,729 +178
android hermes x86_64 8,854,890 +175
android jsc arm64-v8a 9,140,777 +213
android jsc armeabi-v7a 8,332,995 +218
android jsc x86 9,194,447 +213
android jsc x86_64 9,453,529 +219

Base commit: 92b8981
Branch: main

@pull-bot
Copy link

pull-bot commented Nov 8, 2022

PR build artifact for 33711f2 is ready.
To use, download tarball from "Artifacts" tab in this CircleCI job then run yarn add <path to tarball> in your React Native project.

@robertying
Copy link
Contributor Author

@cipolleschi No problem! Appreciate the work you've been doing. I fixed some issue and all checks have passed.

@facebook-github-bot
Copy link
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

if (headerName == null || headerValue == null) {
return null;
}
headersBuilder.add(headerName, headerValue);
headersBuilder.addUnsafeNonAscii(headerName, headerValue);
Copy link
Contributor

Choose a reason for hiding this comment

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

We have internal failures due to this line. The internal system is not able to find it.
I'm not an Android expert and I couldn't find from which version of OkHTTP this is available.
Could you help me out a bit here? 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was added in 3.12.0: https://square.github.io/okhttp/changelogs/changelog_3x/#version-3120
RN is using v4 of okhttp now:

OKHTTP_VERSION=4.9.2

Copy link
Contributor Author

@robertying robertying Nov 28, 2022

Choose a reason for hiding this comment

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

Hi @cipolleschi hope you had a great week!

Is there any update on this?

All non-internal builds passed so I suppose the code should have built without problems.

Is the internal build using a different okhttp version than the one on GitHub? If you could attach logs (without sensitive info of course), it would be easier for me to help.

Also, I saw the Linter complained about a warning (not an error). Does this mean it is still mergeable?

Thank you.

Copy link
Contributor

Choose a reason for hiding this comment

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

Jumping on this.

The build still fails internally. For your reference with this error:

ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.java:754: error: cannot find symbol
[CONTEXT]       headersBuilder.addUnsafeNonAscii(headerName, headerValue);
[CONTEXT]                     ^
[CONTEXT]   symbol:   method addUnsafeNonAscii(java.lang.String,java.lang.String)
[CONTEXT]   location: variable headersBuilder of type okhttp3.Headers.Builder

Also, I saw the Linter complained about a warning (not an error). Does this mean it is still mergeable?

Currenty we can't merge this as it is, as our internal builds are red (not sure why they don't show up on Github though).

Copy link
Contributor Author

@robertying robertying Nov 28, 2022

Choose a reason for hiding this comment

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

Thank you @cortinico for looking into this!

So the method couldn't be found when compiled...

The code builds fine on CircleCI though. Is there any particularly special setup for internal builds?

For example, was the build that failed configured with a lower version of OkHttp?

Since I don't have access to the build logs and configurations, what's the best approach to the issue if the code builds fine in public but fails internally?

Thanks!

Edit: I synced with the main branch again to force rerun the jobs, hoping they all passes so it triggers the internal one again. I find the CI sometimes fails abruptly due to broken environments.

@pull-bot
Copy link

PR build artifact for b5749b6 is ready.
To use, download tarball from "Artifacts" tab in this CircleCI job then run yarn add <path to tarball> in your React Native project.

@pull-bot
Copy link

PR build artifact for 7ed0489 is ready.
To use, download tarball from "Artifacts" tab in this CircleCI job then run yarn add <path to tarball> in your React Native project.

@pull-bot
Copy link

PR build artifact for 03fb462 is ready.
To use, download tarball from "Artifacts" tab in this CircleCI job then run yarn add <path to tarball> in your React Native project.

@robertying
Copy link
Contributor Author

Want to bump this PR.
@cipolleschi @cortinico would you mind importing this again to see how it behaves in internal builds?

The fix passed all external checks but the internal ones, making it impossible for me to find the problem myself.

This utf-8 fix, however, is important to developers in lots of non-English speaking countries and I wish it could be handled soon.

Thank you!

@facebook-github-bot
Copy link
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@robertying robertying force-pushed the fix-non-ascii-header-value branch 4 times, most recently from dd10a95 to 0460aad Compare March 9, 2023 07:36
@robertying
Copy link
Contributor Author

@cipolleschi would you mind importing the diff again? Want to see if there's any internal update that might have solved the linter issue. Thank you!

@facebook-github-bot
Copy link
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@robertying
Copy link
Contributor Author

Ok so I'm convinced some Meta internal apps are still using an old version of okhttp that is not caught up with the open source version (v4.9.2).

To be honest, this feels so weird because it basically means we cannot use any newer features introduced in okhttp in open source React Native even if this repo has set its okhttp version to be v4.

I've done what I can, but only people at Meta can fix this issue.

@cipolleschi
Copy link
Contributor

Hi @robertying, sorry for the long silence. We were discussing this feature internally and, while we are looking to try and understand what needs to be done to make it work, we were exploring alternatives.

Do you think would it be possible to make this feature an opt-in? 🤔

So, for example, if we switch a flag in the gradle.properties people can start using this feature.

How do you feel about that? This would make much easier to land this as we will be preserving the base default behavior (and internal system won't break) but supporting the use case presented here.

@facebook-github-bot
Copy link
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@robertying
Copy link
Contributor Author

@cipolleschi I don't see any changes you requested. Did you mean to have dedicated functions to check?

Also, I'm not sure how internal tests are still failing. 😅 I tested all these GitHub checks with a lower version of okhttp v3.11.0 and they all passed.

@cipolleschi
Copy link
Contributor

My bad, I probably closed the page before submitting the suggestions... 🤦
I added the comment now. We should cache what we can to avoid expensive operation. There could be potential multi-threading issues, so it could make sense to synchronize access on the new variables... But I'll first try this out.

@robertying
Copy link
Contributor Author

@cipolleschi I see what you mean. In the meantime, can you check the linter error that was reported internally?

@cipolleschi
Copy link
Contributor

@robertying, there are some internal tests failing, but these are snapshot tests because we changed the layout of HTTP, so they will be easy to fix for me as soon as we have a final version of the diff.

If you can:

  • rebase and solve conflicts (we are making react-native a proper monorepo, so we moved a bunch of files. Now react-native Core codebase resides in react-native/packages/react-native. You can read more here)
  • add caching

I can fix the tests and try to land it.

Thank you so much for your patience.

@robertying robertying force-pushed the fix-non-ascii-header-value branch 2 times, most recently from 5cf6090 to e869398 Compare March 20, 2023 21:38
@robertying
Copy link
Contributor Author

@cipolleschi woohoo! All tests passed.

I used the static constructor to initialize the reflected method. Let me know if you think this is okay.

@cipolleschi
Copy link
Contributor

/rebase

@cipolleschi
Copy link
Contributor

I used the static constructor to initialize the reflected method. Let me know if you think this is okay.

Hello! Changes looks good to me, I'm not a Java expert really, but I think it make sense and it could work!

@facebook-github-bot
Copy link
Contributor

@cipolleschi has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@robertying
Copy link
Contributor Author

@cipolleschi Thanks! Also, usually the bot should have committed the code by now. Could you take a look when you have time?

@cipolleschi
Copy link
Contributor

cipolleschi commented Mar 29, 2023

We are trying to understand if we can actually merge this. There could be performance implications in using reflection and the situation with OkHTTP is blocking other team internally, so we are trying to understand the best path forward.

I'm sorry that this is taking so long to merge. :(

@robertying
Copy link
Contributor Author

We are trying to understand if we can actually merge this. There could be performance implications in using reflection and the situation with OkHTTP is blocking other team internally, so we are trying to understand the best path forward.

I'm sorry that this is taking so long to merge. :(

No worries. Please keep me posted.

@cortinico
Copy link
Contributor

@robertying sorry for the delay.
I'm looking into understanding how to handle the OkHTTP version internally so we can merge this. Just to set expectations, I don't believe we'll be able to merge this in the near future. I'll keep on investigating and get back to you once we have a plan forward.

@facebook-github-bot
Copy link
Contributor

@cortinico has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator.

@github-actions
Copy link

This pull request was successfully merged by @robertying in 7c7e9e6.

When will my fix make it into a release? | Upcoming Releases

@github-actions github-actions bot added the Merged This PR has been merged. label Sep 26, 2023
@facebook-github-bot
Copy link
Contributor

@cortinico merged this pull request in 7c7e9e6.

@cortinico
Copy link
Contributor

@robertying sorry for the delay, but we were finally able to ship this change.

@robertying
Copy link
Contributor Author

@robertying sorry for the delay, but we were finally able to ship this change.

@cortinico no worries! Appreciate that you revisited this change and had it merged!

Does this mean there's still an old okhttp version somewhere internal and we have to use reflections as a workaround?

@cortinico
Copy link
Contributor

Does this mean there's still an old okhttp version somewhere internal and we have to use reflections as a workaround?

Nope we were able to update the internal usages to OkHTTP 3.12.x

@robertying
Copy link
Contributor Author

Does this mean there's still an old okhttp version somewhere internal and we have to use reflections as a workaround?

Nope we were able to update the internal usages to OkHTTP 3.12.x

Ah I see! I just realized the commit imported was the original one without the reflection workaround, not the current changes listed in this PR. All good now. 👍

@robertying robertying deleted the fix-non-ascii-header-value branch October 7, 2023 12:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Platform: Android Android applications.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Android] React Native strips non-ASCII characters from HTTP headers
8 participants