Jesse Skrivseth
Jesse Skrivseth

Reputation: 491

ngCordova cdvfile URLs and ng-src not rendering image

I've been struggling with this for days now. I have an Ionic 1.3.0 application using ngCordova#0.1.26-alpha

I am using $cordovaFile.writeFile() to store images in cordova.file.applicationStorageDirectory. I've tried every storage location with the same result. The file is good, I can view it in a file manager app. I would like to use the file as an ng-src for an <img>. On Android, this might look something like: cdvfile://localhost/sdcard/Android/data/com.example.mobile/img-49444.jpg

So in my page, I bind the URL to the <img> and stick a sanity check <pre> in there to see that record.image_url has a valid URL:

<img ng-src="{{record.image_url}}">
<pre>{{record.image_url}}</pre>

While the URL looks good to me, the image simply doesn't render and it seems no amount of effort will convince it. I've tried file:///, cdvfile://, and http:// URLs, using entry.toURL(), entry.toInternalURL(), etc., from the response objects from $cordovaFile.writeFile(). All approaches fail with either a broken image icon or an empty <img> element rendering.

I've tried adding this CSP tag to index.html (and dozens of other attempts)

<meta http-equiv="Content-Security-Policy" content="img-src 'self' data: blob: filesystem: cdvfile://*;">

And I've tried messing around with $compileProvider, like:

$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|cdvfile):|data:image\//);

And throwing everything I can at config.xml Cordova whitelists

<plugin name="cordova-plugin-whitelist" spec="~1.2.1"> <access origin="*"/> <access origin="cdvfile://*"/> <access origin="file://*"/> <access origin="file:///*"/> <allow-intent href="cdvfile://*"/> <allow-intent href="file://*"/> <allow-intent href="file:///*"/> <allow-navigation href="cdvfile://*"/> <allow-navigation href="file://*"/> <allow-navigation href="file:///*"/> </plugin>

Nothing works. I don't think this is a white list plugin issue - my assumption is that when the image is rejected due to the white list, the image is displayed as a broken link, whereas the <img> element is instead empty and takes up no space when the white list is not rejecting it. This behavior is consistent on Android 4.4.4 and 6.0.1.

Any help would be greatly appreciated.

Upvotes: 0

Views: 1859

Answers (2)

Jesse Skrivseth
Jesse Skrivseth

Reputation: 491

So as to not mislead others that come across this answer, I managed to get cdvfile:// URLs to work even in livereload mode without issue in a new app. Simply:

config.xml

  <access origin="*"/>
  <access origin="tel:*" launch-external="yes"/>
  <access origin="geo:*" launch-external="yes"/>
  <access origin="mailto:*" launch-external="yes"/>
  <access origin="sms:*" launch-external="yes"/>
  <access origin="market:*" launch-external="yes"/>
  <access origin="cdvfile:*"/>
  <plugin name="cordova-plugin-whitelist" spec="~1.2.2"/>

Note that <access> rules are NOT supposed to be nested under <plugin>. The documentation doesn't explicitly call that out.

app.js

.config(function ($stateProvider, $urlRouterProvider, $compileProvider) {
     $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|file|blob|cdvfile|content|tel|geo|mailto|sms|market):|data:image\//);

I do not have a Content-Security-Policy meta tag declared in index.html

And all is well.

Upvotes: 3

Jesse Skrivseth
Jesse Skrivseth

Reputation: 491

So it turns out I can use file:///* paths for ng-src if I deploy the built .apk to the device instead of using the Ionic livereload server (ionic run android -cls). It should be obvious that hardware-level functions won't work through something like the browser-based ionic serve, but it was less obvious that testing on actual hardware would fail like this based on the livereload server. I incorrectly assumed that the livereload server was just a file monitoring approach where it would stream assets onto the device as they change and maybe notify the app to reload. Instead it seems they are just hosting a web server directly on your host. $cordovaFile correctly reads/write files on the device even in livereload, but the web views in the app don't have native access to the files on the device in that mode. I could read the file contents with $cordovaFile.readAsText() and that would still work even on livereload, but when it comes to the web views in the app reaching files, well, it just doesn't have any access to native device URLs.

If anyone else runs into this issue, here's how I'm writing the file and capturing the URL so that it works in a view.

$ionicPlatform.ready(function () {
     var targetDir = cordova.file.externalApplicationStorageDirectory + "files/";
    $cordovaFile.writeFile(targetDir, name, imageData, true)
      .then(function (info) {
        console.log("File created", info.target.localURL, JSON.stringify(info));
        $cordovaFile.checkFile(targetDir, name).then(function (entry) {
          console.log("Got file entry", entry.toURL());
          q.resolve(entry.toURL());
        });

      }, function (e) {
        q.reject(e);
      });
  });
  return q.promise;
}

On android this would return a URL like: file:///storage/emulated/0/Android/data/com.example/files/<name>

I've found that cordova-plugin-whitelist can be empty in config.xml - we don't need any whitelist directives for file:///. Also, no $compileProvider.imgSrcSanitizationWhitelist() changes are necessary - the default whitelist should work.

I never did get cdvfile:// URLs to work, regardless of whitelist settings, Content-Security-Policy directives, or app.config tweaks.

Upvotes: 1

Related Questions