cordova-plugin-file icon indicating copy to clipboard operation
cordova-plugin-file copied to clipboard

Accessing cdvfile:// from http://[network-url] in ajax throws CORS policy error on Android

Open vitto32 opened this issue 5 years ago • 16 comments

While developing I run my app on an Android device. The app is served from a network url using cordova-plugin-webpack LiveReload (HMR) feature.

I've successfully implemented a download mechanism of JSON files and Audio files. I can embed the downloaded audio file using cdvfile protocol (obtained through .toInternalURL) but I can not get the JSON files using Ajax requests because of this error:

Access to XMLHttpRequest at 'cdvfile://localhost/files/test/data.json' from origin 'http://10.123.123.123:8080' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, https.

Recap:

  • [x] I can access APIs on the web via ajax.
  • [x] I can download files using XHR and store them on the device.
  • [x] I can embed the downloaded files through src='cdvfile://'
  • [ ] I can't ajax 'cdvfile://'

I have <access origin="*" /> and also tried <access origin="cdvfile://*" /> in config.xml I have <allow-navigation href="cdvfile:*" /> CSP is set as follow:

default-src * cdvfile: data: blob: ws: wss: gap://ready file: http: https: 'unsafe-eval'; media-src * cdvfile:; style-src * 'unsafe-inline'; script-src * cdvfile: 'unsafe-inline' 'unsafe-eval'; connect-src * ws: wss:;"

Any help would be appreciated

vitto32 avatar Oct 22 '19 11:10 vitto32

Have you tried sending the cdvfile path into a toNativeURL() call to get back a valid url you can then use in the ajax call? Think it'll convert from a cdvfile path to a file:/// path

Nashorn avatar Oct 22 '19 12:10 Nashorn

I've tried toUrl and toNativeURL (deprecated) but i get the same error as long as the allowed schemes are "http, data, chrome, https". Is there any way to change those?

vitto32 avatar Oct 22 '19 12:10 vitto32

sorry that didnt work, not sure

Nashorn avatar Oct 22 '19 14:10 Nashorn

Why don't you use the actual file reader APIs instead of XHR? XHR is intended to make requests to remote servers, not to "download" local files.

https://github.com/apache/cordova-plugin-file#read-a-file-

Not 100% confident, but I don't believe there is a way in the android sdk to disable cors in the webview.

breautek avatar Oct 22 '19 14:10 breautek

I do download the file from remote API (this part works) and I store it for offline usage. But then I have to load it.

I've tried using a file reader, but reading files is very slow (the files are huge).

The workaround (ugly) I'm thinking about is to edit the JSON, prepend a function call and load it through jsonp. Being able to disable CORS for cdvfile or file protocol on the webview would be a lot nicer and I can reuse code (I'm porting an Electron app).

vitto32 avatar Oct 22 '19 14:10 vitto32

I briefly did some research and it looks like disabling CORS from the webview is not possible... but looks like it is possible to get around that by intercepting requests as shown here

And in case the link goes away, I'll post the example:

public class OptionsAllowResponse {
    static final SimpleDateFormat formatter = new SimpleDateFormat("E, dd MMM yyyy kk:mm:ss", Locale.US);

    @TargetApi(21)
    static WebResourceResponse build() {
        Date date = new Date();
        final String dateString = formatter.format(date);

        Map<String, String> headers = new HashMap<String, String>() {{
            put("Connection", "close");
            put("Content-Type", "text/plain");
            put("Date", dateString + " GMT");
            put("Access-Control-Allow-Origin", /* your domain here */);
            put("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
            put("Access-Control-Max-Age", "600");
            put("Access-Control-Allow-Credentials", "true");
            put("Access-Control-Allow-Headers", "accept, authorization, Content-Type");
            put("Via", "1.1 vegur");
        }};

        return new WebResourceResponse("text/plain", "UTF-8", 200, "OK", headers, null);
    }
}
// WebViewClient
@Override
@TargetApi(21)
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
    if (request.getMethod().equalsIgnoreCase("OPTIONS")) {
        return OptionsAllowResponse.build();
    }

    return null;
}

Not sure if this will work at all, and I can't promise when I'll have time to experiment with this.

breautek avatar Oct 22 '19 17:10 breautek

How large of a file are we talking about? In one of my apps, I read/write JSON files of approximately 50mb, but sometimes upwards of 100mb with no significant slowdown. I do use the deprecated file transfer plugin to avoid reading the data in the javascript environment though. But the data is recorded and JSON stringified in javascript for the initial write.

breautek avatar Oct 22 '19 18:10 breautek

My files are smaller, 10-15MB but the reading takes up to 4 seconds on my (old) test device using just the file plugin.

If I don’t find any alternative I’ll conform with that.

PS: thanks for investigating further!

vitto32 avatar Oct 22 '19 18:10 vitto32

Finally I opted for the script embedding mechanism:

  • while writing the json during the download operation, I wrap it in a function call window.__loadJSON(filePath, [json-content]);
  • When I need it I register a callback bound to filePath and I load it using a script tag
  • __loadJSON triggers the callback

it's still slow (I assume it's my device) and ugly. But it's a little faster then the read implementation and probably more solid.

It's silly I can load and run a fully functional script but I can't load a simple json object from the same source / protocol.

vitto32 avatar Oct 23 '19 08:10 vitto32

Glad you found a workaround.

I'm going to add the help wanted label to this ticket. I think it's still worth investigating on request intercepts to see if it can be used to allow cdvfile and other custom protocols through CORS.

breautek avatar Oct 23 '19 13:10 breautek

It appaers the "cdvfile://" protocol causes a lot of trouble.
Related issues: Issue #295 > PR #296 + PR #322 Issue #329 Issue #347 Issue #349

It seems like a solution similar to the one proposed in pull request #296, might also work for this situation.

Lindsay-Needs-Sleep avatar Oct 24 '19 12:10 Lindsay-Needs-Sleep

@Lindsay-Needs-Sleep I'm facing a lot of troubles. I've seen you've done some PR, do you managed to load local files (using XHR and/or regular request) in both iOS and Android even if current page is on a remote host?

vitto32 avatar Feb 21 '20 12:02 vitto32

@vitto32 I don't think I have any particularily good solutions for you. Defeating CORS as suggested by @breautek seems like it would be the nicest solution.

Here are a couple ways that could work:

  1. You can use cordova-plugin-ionic-webview
    This runs/(fakes?) a local server on the device which hosts your files. I had to use this to play locally saved videos.
    ++ You should definitely be able to xhr/ajax request whatever you want -- It maybe runs a local server (depends/not sure). If you have to support ios less than 11, you have to use the 2.x version which definitely runs an actual server (on ios at least). Otherwise, 4.x on ios, does not actually run a server. I'm not entirely sure how the android implementation does it.

  2. Have your app inject code (exec I believe) directly into the webview, onto your remotely hosted web page using cordova-plugin-hostedwebapp. It is not maintained but it works, (basically), you just need to select a more a active fork to use. ++ definitely no local server ++ nice way to load all the cordova files + plugin files onto your remote webpage without having to rely on cdvfile -- you would have to rewrite your json as a js script and load it as a script I believe (so that it can be injected) -- You have to know which scripts should be injected on which pages at build time

Lindsay-Needs-Sleep avatar Feb 24 '20 00:02 Lindsay-Needs-Sleep

@Lindsay-Needs-Sleep thanks! My project evolved, and now I need real XHR support to load not only json files but also binary content and possibly chunks of data (mainly encoded audio files).

Android I solved it using a slightly different version of PR #322 (i used the url format proposed in #296). It seems to work smoothly even if my www folder is on a remote host. I haven't tested it yet in prod mode (www folder under file://) but I'm pretty confident it will work.

iOS I'm using cordova-plugin-wkwebview-engine but at the moment I have no access to xCode so I just have a few possible roads in mind:

  • try cordova-plugin-wkwebview-file-xhr that sounds pretty interesting
  • try WKURLSchemeHandler / setURLSchemeHandler approach as you suggested in your PR (targeting iOS >= 11)

everything started from your precious links to PR and Issues dealing with this. Thanks! The cordova-plugin-ionic-webview also sounds as a feasible plan B.

vitto32 avatar Feb 24 '20 11:02 vitto32

Hi, Try this solution:

Search the code for the following line: (might be in several files, so do it for all of them) WKWebViewConfiguration* configuration = [[WKWebViewConfiguration alloc] init];

And add the following two lines after it:

[configuration.preferences setValue:@TRUE forKey:@"allowFileAccessFromFileURLs"];
[configuration setValue:@"TRUE" forKey:@"allowUniversalAccessFromFileURLs"];

ihershkovitzSF avatar Aug 12 '20 12:08 ihershkovitzSF

Since Cordova 10 Android template does all requests via https how is cvdfile:// access via browser supposed to work now. We needed to upgrade and have found ourselves stuck on this issue. All imgs from webview that were cached can no longer be rendered. I've tried so many hacks at this but setting a host in config.xml and using android intended behaviour just means we are stuck at accessing the cvdfile protocol. Is this right, or are we doing something wrong. ionic cordova webview is not compattible with our other plugins for encryption and I don't understand how IOS allows access while android doesnt. Is this plugin just broken in Cordova Andoid 10?

weareu avatar Feb 04 '22 14:02 weareu