From 7f1dec008e98ae206c53d67268c330846c4d227d Mon Sep 17 00:00:00 2001 From: Raphael Jamet Date: Thu, 15 Dec 2016 15:55:44 +0100 Subject: [PATCH] feat($templateFactory): refactor to a Provider to have a $http/$templateRequest switch This creates a $TemplateFactoryProvider to be able to set a global config option that forces $templateFactory to use $http (which bypasses the security checks, hence the scary name). Added tests and doc for that too. This still defaults to $http on older Angulars as expected. --- src/templateFactory.js | 60 +++++++++++++++++++++++++++++++++---- test/templateFactorySpec.js | 23 ++++++++++++++ 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/templateFactory.js b/src/templateFactory.js index 6d23ff758..acc0336dd 100644 --- a/src/templateFactory.js +++ b/src/templateFactory.js @@ -1,3 +1,54 @@ + +/** + * @ngdoc object + * @name ui.router.util.$templateFactoryProvider + * + * @description + * Provider for $templateFactory. Manages which template-loading mechanism to + * use, and will default to the most recent one ($templateRequest on Angular + * versions starting from 1.3, $http otherwise). + */ +function $TemplateFactoryProvider() { + var shouldUnsafelyUseHttp = angular.version.minor < 3; + + /** + * @ngdoc function + * @name ui.router.util.$templateFactoryProvider#shouldUnsafelyUseHttp + * @methodOf ui.router.util.$templateFactoryProvider + * + * @description + * Forces $templateFactory to use $http instead of $templateRequest. This + * might cause XSS, as $http doesn't enforce the regular security checks for + * templates that have been introduced in Angular 1.3. Note that setting this + * to false on Angular older than 1.3.x will crash, as the $templateRequest + * service (and the security checks) are not implemented on these versions. + * + * See the $sce documentation, section + * + * Impact on loading templates for more details about this mechanism. + * + * @param {boolean} value + */ + this.shouldUnsafelyUseHttp = function(value) { + shouldUnsafelyUseHttp = !!value; + }; + + /** + * @ngdoc object + * @name ui.router.util.$templateFactory + * + * @requires $http + * @requires $templateCache + * @requires $injector + * + * @description + * Service. Manages loading of templates. + */ + this.$get = ['$http', '$templateCache', '$injector', function($http, $templateCache, $injector){ + return new $TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp);}]; +} + + /** * @ngdoc object * @name ui.router.util.$templateFactory @@ -9,8 +60,7 @@ * @description * Service. Manages loading of templates. */ -$TemplateFactory.$inject = ['$http', '$templateCache', '$injector']; -function $TemplateFactory( $http, $templateCache, $injector) { +function $TemplateFactory($http, $templateCache, $injector, shouldUnsafelyUseHttp) { /** * @ngdoc function @@ -83,7 +133,7 @@ function $TemplateFactory( $http, $templateCache, $injector) { if (isFunction(url)) url = url(params); if (url == null) return null; else { - if($injector.has && $injector.has('$templateRequest')) { + if(!shouldUnsafelyUseHttp) { return $injector.get('$templateRequest')(url); } else { return $http @@ -111,6 +161,6 @@ function $TemplateFactory( $http, $templateCache, $injector) { this.fromProvider = function (provider, params, locals) { return $injector.invoke(provider, null, locals || { params: params }); }; -} +}; -angular.module('ui.router.util').service('$templateFactory', $TemplateFactory); +angular.module('ui.router.util').provider('$templateFactory', $TemplateFactoryProvider); diff --git a/test/templateFactorySpec.js b/test/templateFactorySpec.js index 0b5368412..5ff0a568f 100644 --- a/test/templateFactorySpec.js +++ b/test/templateFactorySpec.js @@ -55,3 +55,26 @@ describe('templateFactory', function () { })); } }); + +describe('templateFactory with $http use forced', function () { + + beforeEach(function() { + angular + .module('forceHttpInTemplateFactory', []) + .config(function($templateFactoryProvider) { + $templateFactoryProvider.shouldUnsafelyUseHttp(true); + }); + module('ui.router.util'); + module('forceHttpInTemplateFactory'); + }); + + it('does not restrict URL loading', inject(function($templateFactory, $httpBackend) { + $httpBackend.expectGET('http://evil.com/views/view.html').respond(200, 'template!'); + $templateFactory.fromUrl('http://evil.com/views/view.html'); + $httpBackend.flush(); + + $httpBackend.expectGET('data:text/html,foo').respond(200, 'template!'); + $templateFactory.fromUrl('data:text/html,foo'); + $httpBackend.flush(); + })); +});