capacitor icon indicating copy to clipboard operation
capacitor copied to clipboard

[Bug]: Boundary is not extracted correctly from multipart/form-data requests when using CapacitorHttp

Open michaelwolz opened this issue 1 year ago • 11 comments

Capacitor Version

💊   Capacitor Doctor  💊

Latest Dependencies:

  @capacitor/cli: 6.1.2
  @capacitor/core: 6.1.2
  @capacitor/android: 6.1.2
  @capacitor/ios: 6.1.2

Installed Dependencies:

  @capacitor/android: 6.1.0
  @capacitor/ios: 6.1.0
  @capacitor/cli: 6.1.0
  @capacitor/core: 6.1.0

The extraction of the value of the boundary of multipart/form-data requests fails for some requests due to the value being surrounded by double quotes, which is allowed and happens occasionally (See MDN Reference which utilizes double quotes for the boundary in their example: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST#example). The current implementation only works with no surroundings of the boundary value and otherwise just includes them in the actual request body which will then fail on the server side where the serve will not be able to parse the request body.

In addition to that, the whole extraction currently just uses the last key of the Content-Type header which might not necessarily be the boundary but could also be something such as charset (see example implementation or MDN reference).

Other API Details

No response

Platforms Affected

  • [X] iOS
  • [X] Android
  • [ ] Web

Current Behavior

Let's consider the following request (assuming CapacitorHttp is active)

const formData = new FormData();
formData.append('key', 'value');

await fetch(url, { method: 'POST', body, 
  headers: { 'Content-Type': 'multipart/form-data; boundary="boundary"; charset=utf8' }
});

Because of CapacitorHttp is currently taking the last key of the Content-Type header as boundary the actual request body sent by CapacitorHttp will then look like:

--utf8
Content-Disposition: form-data; name="key"

value
--utf8--

The same request without the additional charset key but enclosed in double quotes: ( 'Content-Type': 'multipart/form-data; boundary="boundary"') will look like this:

--"boundary"
Content-Disposition: form-data; name="key"

value
--"boundary"--

The server will however read the boundary without quotes from the header and thus will be unable to parse the body.

Expected Behavior

For both cases the request body should look like this:

--boundary
Content-Disposition: form-data; name="key"

value
--boundary--

Project Reproduction

https://github.com/michaelwolz/multipart-form-data-not-working-on-ios

Additional Information

(Non-)working example can be found here: https://github.com/michaelwolz/multipart-form-data-not-working-on-ios/blob/master/src/js/capacitor-welcome.js#L76-L94

You install the current official version and it'll fail while working with the provided fix.

Pull-Request fixing the problem by accepting arbitrary position of the boundary and ignoring quotes: #7518

Fairly sure that the described behavior is also the origin of the following issues:

  • #7589
  • #7579
  • #7538

michaelwolz avatar Aug 14 '24 07:08 michaelwolz

Any updates on this? I'm facing the same issue with capacitor ^6.0.0

VashLT avatar Sep 02 '24 20:09 VashLT

When fix?

fobaz avatar Sep 10 '24 08:09 fobaz

Hello, when can we expect an update?

kapocius avatar Sep 10 '24 08:09 kapocius

Hey @jcesarmobile & team, I'm running into the same issue after upgrading from Capacitor 5 to 6. With the Google Play Store forcing the move to Android target version SDK 34, upgrading to Capacitor 6 seems to be a requirement. Unfortunately, this is blocking us us from submitting app updates, which has become an increasingly urgent matter for business and technical reasons.

I know we're not the only ones dealing with this, as I've seen other tickets about the issue but they were closed due to inactivity. We're really hoping for some guidance or a fix soon, as this has become a critical blocker for the community.

Thanks for all your hard work! Any update on this would be super helpful.

Relevant links: https://github.com/ionic-team/capacitor/issues/7498#issuecomment-2164974286 https://github.com/ionic-team/capacitor/pull/5498

peterrmah avatar Oct 04 '24 16:10 peterrmah

Hey, My team is waiting too for this update. Any information about when we can expect this update on production?

grzegorzCieslik95 avatar Oct 27 '24 13:10 grzegorzCieslik95

I'm also having the same issue.

koczka avatar Oct 29 '24 15:10 koczka

Hey @jcesarmobile & team, I'm running into the same issue after upgrading from Capacitor 5 to 6. With the Google Play Store forcing the move to Android target version SDK 34, upgrading to Capacitor 6 seems to be a requirement. Unfortunately, this is blocking us us from submitting app updates, which has become an increasingly urgent matter for business and technical reasons.

I know we're not the only ones dealing with this, as I've seen other tickets about the issue but they were closed due to inactivity. We're really hoping for some guidance or a fix soon, as this has become a critical blocker for the community.

Thanks for all your hard work! Any update on this would be super helpful.

Relevant links: #7498 (comment) #5498

It seems my previous comment was marked as "abuse" despite having chosen my wording very carefully and keeping an optimistic tone. A few other comments before me were also marked as "off topic" even by simply asking if there is an update on the issue.

There seems to be a growing number of users in the Capacitor community experiencing this blocker of a core issue. It would be very much appreciated for someone from the Capacitor team can give us an update in good faith. @markemer

peterrmah avatar Oct 29 '24 16:10 peterrmah

Hey @jcesarmobile & team, I'm running into the same issue after upgrading from Capacitor 5 to 6. With the Google Play Store forcing the move to Android target version SDK 34, upgrading to Capacitor 6 seems to be a requirement. Unfortunately, this is blocking us us from submitting app updates, which has become an increasingly urgent matter for business and technical reasons.

I know we're not the only ones dealing with this, as I've seen other tickets about the issue but they were closed due to inactivity. We're really hoping for some guidance or a fix soon, as this has become a critical blocker for the community.

Thanks for all your hard work! Any update on this would be super helpful.

Relevant links: #7498 (comment) #5498

It seems my previous comment was marked as "abuse" despite having chosen my wording very carefully and keeping an optimistic tone. A few other comments before me were also marked as "off topic" even by simply asking if there is an update on the issue.

There seems to be a growing number of users in the Capacitor community experiencing this blocker of a core issue. It would be very much appreciated for someone from the Capacitor team can give us an update in good faith. @markemer

That I think is the tagging, it may even be automated. Try to avoid tagging Julio and I, but this is on our radar for sure, but I cannot get a definitive date for you right now.

markemer avatar Oct 29 '24 16:10 markemer

For anyone who is really blocked by this, you might consider patching the plugin following this guide here, for example: https://capawesome.io/blog/how-to-patch-a-capacitor-plugin/.

You just need to adapt two files according to my PR #7518:

  • android/capacitor/src/main/java/com/getcapacitor/plugin/util/CapacitorHttpUrlConnection.java
  • ios/Capacitor/Capacitor/Plugins/CapacitorUrlRequest.swift

I know this is kind of a hacky solution, but on the other hand, we have been using the patched version in production code for a few months now with success. Still, it would be great to have this verified by the Capacitor team, as it's entirely possible that I missed something with my changes.

michaelwolz avatar Oct 30 '24 08:10 michaelwolz

Up

dyazincahya avatar Jan 22 '25 02:01 dyazincahya

I ended up using fetch API instead. One downside is that HTTP interceptor won't be able to add headers or execute when an HTTP request is made. However, it worked smoothly overall. For those that may be interested in how it works, here's an example:

import { from } from 'rxjs';

callEndpoint(body: FormData) {
    let headers: { [key: string]: string } = {};
    // here set some auth headers
    headers["Authorization"] = `Bearer ${localStorage.getItem(AUTH_TOKEN_KEY)}`;

    /**
     * Here instead of using CapacitorHttp or Angular HttpClient, use fetch and wrap it with "from"
     * so it returns an observable.
     */
    return from(
      fetch(
        `endpoint`,
        {
          method: 'PUT',
          headers,
          body,
        },
      ),
    );
  }

VashLT avatar Jan 22 '25 16:01 VashLT