6

I'm writing a Chrome App using AngularJS. I know that when accessing external images you have to do a cross-origin XMLHttpRequest and serve them as blobs.

I have a bunch of internal images (local app resources) that follow a pattern that I want to display in an ngRepeat.

I can get the images to load statically just fine with something like:

<img src="images/image1.png"/>

But when I try using them in a repeat like this:

<div ng-repeat="item in myList">
    <img ng-src="{{item.imageUrl}}"/>
</div>

I get an error for each image (though the image from the error does exist) like this:

Refused to load the image 'unsafe:chrome-extension://hcdb...flhk/images/image1.png' because it violates the following Content Security Policy directive: "img-src 'self' data: chrome-extension-resource:".

I have been able to load external resources successfully using ng-src and XHR. Do I have to follow the same pattern for internal resources loaded dynamically?

Update - Another Simple Example

Starting with the simplest Chrome App (https://github.com/GoogleChrome/chrome-app-samples/tree/master/hello-world), the following will work outside a chrome app (in the browser) but not within the chrome app:

index.html

<!DOCTYPE html>
<html ng-app ng-csp>
<head>
    <title>Hello World</title>
    <script src="js/angular.min.js"></script>
    <script src="js/test.js"></script>
</head>
<body ng-controller="Ctrl">
    <img ng-src="{{imageUrl}}"/>
</body>
</html>

test.js

function Ctrl($scope) {
    $scope.imageUrl = 'hello_world.png';
}
Shaun
  • 1,485
  • 2
  • 15
  • 21

5 Answers5

15

I just found the answer in another stack overflow question:

Angular changes urls to "unsafe:" in extension page

Angular has a whitelist regular expression that an image src url must match before angular will change the src. A chrome-extension:// url doesn't match it by default so you have to change it. See here for more information:

http://docs.angularjs.org/api/ng/provider/$compileProvider

I added the following code to allow chrome-extension// urls to the whitelist (using the code from the other stackoverflow question's answer):

angular.module('myApp', [])
.config( [
    '$compileProvider',
    function( $compileProvider ) {
        var currentImgSrcSanitizationWhitelist = $compileProvider.imgSrcSanitizationWhitelist();
        var newImgSrcSanitizationWhiteList = currentImgSrcSanitizationWhitelist.toString().slice(0,-1)
        + '|chrome-extension:'
        +currentImgSrcSanitizationWhitelist.toString().slice(-1);

        console.log("Changing imgSrcSanitizationWhiteList from "+currentImgSrcSanitizationWhitelist+" to "+newImgSrcSanitizationWhiteList);
        $compileProvider.imgSrcSanitizationWhitelist(newImgSrcSanitizationWhiteList);
    }
]);
Community
  • 1
  • 1
Shaun
  • 1,485
  • 2
  • 15
  • 21
12

Similar to an answer already given, below is a function that overwrites AngluarJs's regular expression for whitelisting the img src urls.

Basically, what happens is that angular js won't trust the url's included in the ng-src tag and will try to validate/sanitise them. As a result of this process, the url's provided in that tag will have an "unsafe:" string prepended. This in turn determines the chrome runtime to not display the image as it can't find the image located at 'unsafe:/path/to/image/img.jpg'.

So, what we have to do is overwrite the regex that whitelists the img src urls. There are similar regexes for a href urls. You have to read the angular js documentation for this.

appModule is the main application module in the angular js application.

appModule.config([
  '$compileProvider',
  function ($compileProvider) {
      //  Default imgSrcSanitizationWhitelist: /^\s*((https?|ftp|file|blob):|data:image\/)/
      //  chrome-extension: will be added to the end of the expression
      $compileProvider.imgSrcSanitizationWhitelist(/^\s*((https?|ftp|file|blob|chrome-extension):|data:image\/)/);
  }
]);
Hengjie
  • 4,598
  • 2
  • 30
  • 35
Dan Borza
  • 3,419
  • 3
  • 22
  • 16
1

Try to run each image src through a function like

<div ng-repeat="item in myList">
    <img ng-src="{{trustAsResourcUrl(item.imageUrl)}}"/>
</div>

And inside your controller

$scope.trustAsResourcUrl = function(url) {
    return $sce.trustAsResourceUrl(url);
} 

Don't forget to include the $sce service. You can also try to disable SCE completly to eliminate it

angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
    // Completely disable SCE.  For demonstration purposes only!
    // Do not use in new projects.
    $sceProvider.enabled(false);

});
Nenad
  • 3,438
  • 3
  • 28
  • 36
  • I just tried your suggestion. After initially forgetting to include the $sce service (despite the reminder), I'm back to getting the same error I had before (though I double checked it was making it through the $sce.trustAsResourceUrl). – Shaun Mar 31 '14 at 21:57
  • Try to disable SCE completely, to see if this is the issue or something else. P.S I updated my answer – Nenad Mar 31 '14 at 22:50
  • I tried your second recommendation as well, but still had the same error. I just found the answer here: http://stackoverflow.com/questions/15606751/angular-changes-urls-to-unsafe-in-extension-page – Shaun Apr 01 '14 at 22:45
1

Get it to work based on Eric Chen response, creating a directive like this:

angular.module('myAppInChromeApp',[]).directive('cspSrc', function() {
    return {
        restrict: 'A',
        replace: false,
        priority: 99, //after all build-in directive are compiled
        link: function(scope, element, attrs) {
            attrs.$observe('cspSrc', function(value) {
                if (!value)
                    return;

                if (element[0].nodeName.toLowerCase() === 'img' && value.indexOf('blob') !== 0) {
                    //if it is img tag then use XHR to load image.
                    var localSrc = null;
                    var xhr = new XMLHttpRequest();
                    xhr.open('GET', value, true);
                    xhr.responseType = 'blob';
                    xhr.onload = function(e) {
                        localSrc = URL.createObjectURL(this.response);
                        element[0].src = localSrc;

                    };
                    xhr.send();
                    scope.$on("$destroy", function() {
                        if (localSrc) {
                            URL.revokeObjectURL(localSrc);
                        }
                    });


                } else {
                    attrs.$set('src', value);
                }
            });
        }
    };
})

;

andremp
  • 11
  • 1
0

My approach is not use ng-src.

I wrote my onw csp-src for Chrome Security Policy.

angular.module('myAppInChromeApp',[]).directive('cspSrc', function () {
    return {
        restrict: 'A',
        replace: false,
        priority: 99, //after all build-in directive are compiled
        link: function(scope, element, attrs) {
            attrs.$observe('ngSrc', function(value) {
                if (!value)
                    return;

                if(element[0].nodeName.toLowerCase() === 'img' && value.indexOf('blob') != 0){
                    //if it is img tag then use XHR to load image.
                    var localSrc = null;
                    var xhr = new XMLHttpRequest();
                    xhr.open('GET', value, true);
                    xhr.responseType = 'blob';
                    xhr.onload = function (e) {
                        localSrc = URL.createObjectURL(this.response);
                    };
                    xhr.send();
                    scope.$on("$destroy", function() {
                        localSrc && URL.revokeObjectURL(localSrc);
                    });
                    attrs.$set('src', localSrc);

                }else{
                    attrs.$set('src', value);
                }
            });
        }
    };
})

and change ng-src to csp-src

<img csp-src="http://yourimage.jpg"/>

of course you can add more condition make csp-src work both for website and chromeapp

Eric Chen
  • 3,708
  • 2
  • 18
  • 15