diff --git a/lib/grunt/utils.js b/lib/grunt/utils.js index 764f481fff00..c7d4431b9be5 100644 --- a/lib/grunt/utils.js +++ b/lib/grunt/utils.js @@ -91,7 +91,7 @@ module.exports = { .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/\r?\n/g, '\\n'); - return "angular.element(document).find('head').prepend('');"; + return "!angular.$$csp() && angular.element(document).find('head').prepend('');"; } }, diff --git a/src/Angular.js b/src/Angular.js index 305f132176e4..085e062de28e 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -760,6 +760,13 @@ function equals(o1, o2) { } +function csp() { + return (document.securityPolicy && document.securityPolicy.isActive) || + (document.querySelector && + !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); +} + + function concat(array1, array2, index) { return array1.concat(slice.call(array2, index)); } diff --git a/src/AngularPublic.js b/src/AngularPublic.js index f7f78ae5c736..09361079f166 100755 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -44,12 +44,13 @@ function publishExternalAPI(angular){ 'isNumber': isNumber, 'isElement': isElement, 'isArray': isArray, - '$$minErr': minErr, 'version': version, 'isDate': isDate, 'lowercase': lowercase, 'uppercase': uppercase, - 'callbacks': {counter: 0} + 'callbacks': {counter: 0}, + '$$minErr': minErr, + '$$csp': csp }); angularModule = setupModuleLoader(window); @@ -77,7 +78,6 @@ function publishExternalAPI(angular){ ngClass: ngClassDirective, ngClassEven: ngClassEvenDirective, ngClassOdd: ngClassOddDirective, - ngCsp: ngCspDirective, ngCloak: ngCloakDirective, ngController: ngControllerDirective, ngForm: ngFormDirective, diff --git a/src/ng/directive/ngCsp.js b/src/ng/directive/ngCsp.js index 174e4c58f927..1a099f592607 100644 --- a/src/ng/directive/ngCsp.js +++ b/src/ng/directive/ngCsp.js @@ -3,25 +3,26 @@ /** * @ngdoc directive * @name ng.directive:ngCsp - * @priority 1000 * * @element html * @description * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. - * + * * This is necessary when developing things like Google Chrome Extensions. - * + * * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). * For us to be compatible, we just need to implement the "getterFn" in $parse without violating * any of these restrictions. - * + * * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will * be raised. - * + * * In order to use this feature put the `ngCsp` directive on the root element of the application. - * + * + * *Note: This directive is only available in the ng-csp and data-ng-csp attribute form.* + * * @example * This example shows how to apply the `ngCsp` directive to the `html` tag.
@@ -33,11 +34,6 @@
    
*/ -var ngCspDirective = ['$sniffer', function($sniffer) { - return { - priority: 1000, - compile: function() { - $sniffer.csp = true; - } - }; -}]; +// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap +// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute +// anywhere in the current doc diff --git a/src/ng/sniffer.js b/src/ng/sniffer.js index 7f26e31226e5..9a7447cb2d4f 100644 --- a/src/ng/sniffer.js +++ b/src/ng/sniffer.js @@ -76,7 +76,7 @@ function $SnifferProvider() { return eventSupport[event]; }, - csp: document.securityPolicy ? document.securityPolicy.isActive : false, + csp: csp(), vendorPrefix: vendorPrefix, transitions : transitions, animations : animations diff --git a/test/AngularSpec.js b/test/AngularSpec.js index c1914947415e..1b08a18e078d 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -348,6 +348,46 @@ describe('angular', function() { }); + describe('csp', function() { + var originalSecurityPolicy; + + beforeEach(function() { + originalSecurityPolicy = document.securityPolicy; + }); + + afterEach(function() { + document.securityPolicy = originalSecurityPolicy; + }); + + + it('should return the false when CSP is not enabled (the default)', function() { + expect(csp()).toBe(false); + }); + + + it('should return true if CSP is autodetected via CSP v1.1 securityPolicy.isActive property', function() { + document.securityPolicy = {isActive: true}; + expect(csp()).toBe(true); + }); + + it('should return the true when CSP is enabled manually via [ng-csp]', function() { + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector == '[ng-csp]') return {}; + }); + expect(csp()).toBe(true); + }); + + + it('should return the true when CSP is enabled manually via [data-ng-csp]', function() { + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector == '[data-ng-csp]') return {}; + }); + expect(csp()).toBe(true); + expect(document.querySelector).toHaveBeenCalledWith('[data-ng-csp]'); + }); + }); + + describe('parseKeyValue', function() { it('should parse a string into key-value pairs', function() { expect(parseKeyValue('')).toEqual({}); diff --git a/test/ng/directive/ngCspSpec.js b/test/ng/directive/ngCspSpec.js deleted file mode 100644 index 7a21b587c6c3..000000000000 --- a/test/ng/directive/ngCspSpec.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -describe('ngCsp', function() { - - it('it should turn on CSP mode in $sniffer', inject(function($sniffer, $compile) { - expect($sniffer.csp).toBe(false); - $compile('
'); - expect($sniffer.csp).toBe(true); - })); -}); diff --git a/test/ng/snifferSpec.js b/test/ng/snifferSpec.js index 6edf9f61d740..6e9dc830439a 100644 --- a/test/ng/snifferSpec.js +++ b/test/ng/snifferSpec.js @@ -85,21 +85,12 @@ describe('$sniffer', function() { describe('csp', function() { - it('should be false if document.securityPolicy.isActive not available', function() { + it('should be false by default', function() { expect(sniffer({}).csp).toBe(false); }); - - - it('should use document.securityPolicy.isActive if available', function() { - var createDocumentWithCSP = function(csp) { - return {securityPolicy: {isActive: csp}}; - }; - - expect(sniffer({}, createDocumentWithCSP(false)).csp).toBe(false); - expect(sniffer({}, createDocumentWithCSP(true)).csp).toBe(true); - }); }); + describe('vendorPrefix', function() { it('should return the correct vendor prefix based on the browser', function() {