apache / cordova-plugin-file

Apache Cordova File Plugin
https://cordova.apache.org/
Apache License 2.0
741 stars 756 forks source link

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

Open vitto32 opened 4 years ago

vitto32 commented 4 years ago

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:

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

Nashorn commented 4 years ago

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

vitto32 commented 4 years ago

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?

Nashorn commented 4 years ago

sorry that didnt work, not sure

breautek commented 4 years ago

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.

vitto32 commented 4 years ago

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).

breautek commented 4 years ago

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 commented 4 years ago

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.

vitto32 commented 4 years ago

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 commented 4 years ago

Finally I opted for the script embedding mechanism:

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.

breautek commented 4 years ago

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.

Lindsay-Needs-Sleep commented 4 years ago

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.

vitto32 commented 4 years ago

@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?

Lindsay-Needs-Sleep commented 4 years ago

@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

vitto32 commented 4 years ago

@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:

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.

ihershkovitzSF commented 3 years ago

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"];
weareu commented 2 years ago

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?