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

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

Open
3 of 4 tasks
vitto32 opened this issue Oct 22, 2019 · 16 comments
Open
3 of 4 tasks
Labels
help wanted Extra attention is needed platform: android

Comments

@vitto32
Copy link

vitto32 commented Oct 22, 2019

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 can access APIs on the web via ajax.
  • I can download files using XHR and store them on the device.
  • 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

@Nashorn
Copy link

Nashorn commented Oct 22, 2019

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
Copy link
Author

vitto32 commented Oct 22, 2019

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
Copy link

Nashorn commented Oct 22, 2019

sorry that didnt work, not sure

@breautek
Copy link
Contributor

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
Copy link
Author

vitto32 commented Oct 22, 2019

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
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Author

vitto32 commented Oct 22, 2019

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
Copy link
Author

vitto32 commented Oct 23, 2019

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.

@breautek
Copy link
Contributor

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 breautek added platform: android help wanted Extra attention is needed labels Oct 23, 2019
@Lindsay-Needs-Sleep
Copy link

Lindsay-Needs-Sleep commented Oct 24, 2019

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
Copy link
Author

vitto32 commented Feb 21, 2020

@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
Copy link

Lindsay-Needs-Sleep commented Feb 24, 2020

@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
Copy link
Author

vitto32 commented Feb 24, 2020

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

@ihershkovitzSF
Copy link

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
Copy link

weareu commented Feb 4, 2022

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed platform: android
Projects
None yet
Development

No branches or pull requests

6 participants