apache / cordova-ios

Apache Cordova iOS
https://cordova.apache.org/
Apache License 2.0
2.15k stars 987 forks source link

Origin header not passed with HTML img src calls #1297

Closed rolinger closed 1 year ago

rolinger commented 1 year ago

Bug Report

on iOS, <img ng-src="https://example.com/file/someImage.png">, the call to the server is not passing the Origin header at all.

Problem

Stated above. This issue is different but is quite similar to this one: https://github.com/apache/cordova-ios/issues/897 - the difference between these two issues is in that issue it appears Origin is passed with a null value, whereas this issue is about Origin is not passed at all.

What is expected to happen?

The Origin as defined in config.xml preference is set and passed in <img ng-src> url calls

What does actually happen?

In an html template: <img ng-src="https://example.com/image.png"> - the url call does not include any headers such as Origin and Referrer. On Android, both are passed in the headers.

However <ng-include src="https://example.com/thisFile.txt"> does pass the Origin header. Android passes both Origin and Referrer

Because the Origin header is required on our server, all server images being loaded in our app on iOS are being blocked forcing us to rewrite all standard <img ng-src="{{imageUrl}}"> calls to retrieve the image via $http.get() like:

<img data-ng-src="{{someImage}}" ng-init="getImage(imageUrl)">

$scope.getImage = function(url) {
   $http.get(url).then(function(response) {
      $scope.someImage = response.data ;
  }) ;
}

We don't have to add any additional headers because on iOS $http.get natively adds the Origin header. On Android, it adds Origin and Referrer. This is not ideal; besides having to rewrite tons of <img> tags the file loads are slower. We are also coming through the rest of our code to to look for any other type of src call and having to test what that call looks like. If others are doing the same as the <img ng-src> then we will have to rewrite those too.

<img ng-src> headers:

GET /files/C1001/images/map_C1001_R1001_S1001.png HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Accept: image/webp,image/avif,video/*;q=0.8,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Accept-Encoding: gzip, deflate, br
Host: portal.example.com

$http.get() and ng-include headers:

GET /files/C1001/images/map_C1001_R1001_S1001.png HTTP/1.1
Accept: application/json, text/plain, */*
Origin: ionic://myapp
Host: portal.example.com
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Environment, Platform, Device

Cordova 11, cordova-ios @6.2

Checklist

dpogue commented 1 year ago

Typically the Origin header is only sent in cases where Cross-Origin Resource Sharing (CORS) applies. This applies to non-GET requests made from JavaScript, not to resource loads within the page.

rolinger commented 1 year ago

@dpogue - In Safari on a Mac or on iPhone, Referrer is passed on standard <img src> calls. But not on cordova-ios . For cordova (iOS), origin and/or referrer being passed, like both on Android are, would be of big value to many apps.

breautek commented 1 year ago

A note on standard browser features... Cordova has limited control over browser features. If webview fetch or XMLHttpRequest is behaving a certain way, it's unlikely that Cordova can do anything to influence whatever behaviour is implemented by the webview.

While Safari is based off of WKWebView, they are two different products and may have slight differences of behaviour.

Like dpogue said, a client generally only sends the Origin header if it's making a CORS request. The Origin header is required on CORS request, but is optional otherwise. Resource and page navigations are generally not CORs requests and thus is up to the user agent if they want to still include the Origin header.

Images for example are suppose to have a no-cors default fetch mode, according to MDN. Safari might be breaking this rule because the spec also discourages the no-cors defaults.

Anyway, you can try using crossorigin="anonymous" attribute on your image tags, which should explicitly state that the resource should be a CORS request, and thus the Origin header should be sent.

anonymous means the image will be fetched using a CORS request but without credentials (e.g. no cookies, or Authorization header. See MDN for more information.

crossorigin attribute is also valid for audio, link, script, and video tags.

rolinger commented 1 year ago

@breautek - as always, you provide very thorough explanations and quite often a working solution. As is the case with your response above.

Sent headers for: <img ng-src="https://example.com/pics/someImage.png">

GET /files/C1001/images/map_C1001_R1001_S1001.png HTTP/1.1
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Accept: image/webp,image/avif,video/*;q=0.8,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Accept-Encoding: gzip, deflate, br
Host: portal.example.com

Sent headers for: <img crossorigin="anonymous" ng-src="https://example.com/pics/someImage.png">

GET /files/C1001/images/map_C1001_R1001_S1109.png HTTP/1.1
Accept: image/webp,image/avif,video/*;q=0.8,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5
Origin: ionic://myapp
Host: portal.example.com
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

The Origin is added natively now. This is a far more preferred method than my alternative solutions. I never knew crossorigin could be applied specifically to certain HTML tags like IMG and SCRIPT. What other tags can crossorigin be applied to - I was looking for some list but couldn't find any.

breautek commented 1 year ago

The list I provided came from MDN.

But glad to know that the crossorigin attribute is working for you. It's sometihng I never actually used myself.

img, video, audio, link, and script are the tags that supports the crossorigin attribute. Things like fetch or XMLHttpRequest will send the Origin header in my experience virtually all the time, at least in the context of the Cordova app (where our origin is always cross-origin to our server). It may also send a "CORS" request, which by Fetch spec, is when it sends an OPTIONS preflight request just before it sends the actual request.

Like I mentioned before, Cordova itself has very limited control over browser/webview features. Those features are implemented by the webview, something that is completely abstracted from what Cordova can manipulate. E.g. Cordova cannot change the defaults of the img tag to make crossorigin="anonymous" the default. Thecrossorigin` attribute is also part of those browser features that Cordova can't really control.

What I'm saying is... I doubt that Cordova can do anything to automate this process. It will be up to the app developers to craft their DOM/web app respectfully according using the webview features provided to them. So with that being said, may I close this issue?

rolinger commented 1 year ago

Yes, please close.