From da74d2d50b76c3635ab8444bbaa38c62f9baedaf Mon Sep 17 00:00:00 2001 From: Peter Bacon Darwin Date: Tue, 20 Sep 2016 14:03:08 +0100 Subject: [PATCH] SQUASH ME: feat($httpBackend): JSONP requests now require trusted resource --- docs/content/error/$http/badreq.ngdoc | 6 ++++- src/ng/http.js | 34 +++++++++++++++++++-------- test/ng/httpSpec.js | 27 ++++++++++++++++----- 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/content/error/$http/badreq.ngdoc b/docs/content/error/$http/badreq.ngdoc index ea2f5006361c..81ea6349ecfe 100644 --- a/docs/content/error/$http/badreq.ngdoc +++ b/docs/content/error/$http/badreq.ngdoc @@ -3,7 +3,11 @@ @fullName Bad Request Configuration @description -This error occurs when the request configuration parameter passed to the {@link ng.$http `$http`} service is not an object.  `$http` expects a single parameter, the request configuration object, but received a parameter that was not an object.  The error message should provide additional context such as the actual value of the parameter that was received.  If you passed a string parameter, perhaps you meant to call one of the shorthand methods on `$http` such as `$http.get(…)`, etc. +This error occurs when the request configuration parameter passed to the {@link ng.$http `$http`} service is not a valid object. +`$http` expects a single parameter, the request configuration object, but received a parameter that was not an object or did not contain valid properties. + +The error message should provide additional context such as the actual value of the parameter that was received. +If you passed a string parameter, perhaps you meant to call one of the shorthand methods on `$http` such as `$http.get(…)`, etc. To resolve this error, make sure you pass a valid request configuration object to `$http`. diff --git a/src/ng/http.js b/src/ng/http.js index 2b9d372beca3..09673af5810f 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -802,7 +802,8 @@ function $HttpProvider() { * processed. The object has following properties: * * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) - * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. + * - **url** – `{string|TrustedObject}` – Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. * - **params** – `{Object.}` – Map of strings or objects which will be serialized * with the `paramSerializer` and appended as GET parameters. * - **data** – `{string|Object}` – Data to be sent as the request message data. @@ -882,6 +883,7 @@ function $HttpProvider() { angular.module('httpExample', []) .config(['$sceDelegateProvider', function($sceDelegateProvider) { + // We must whitelist the JSONP endpoint that we are using to show that we trust it $sceDelegateProvider.resourceUrlWhitelist([ 'self', 'https://angularjs.org/**' @@ -955,7 +957,7 @@ function $HttpProvider() { } if (!isString($sce.valueOf(requestConfig.url))) { - throw minErr('$http')('badreq', 'Http request configuration url must be a string. Received: {0}', requestConfig.url); + throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url); } var config = extend({ @@ -1095,11 +1097,10 @@ function $HttpProvider() { // send request try { - var promise = sendReq(config, reqData); + return sendReq(config, reqData).then(transformResponse, transformResponse); } catch (e) { return $q.reject(e); } - return promise.then(transformResponse, transformResponse); } function transformResponse(response) { @@ -1122,7 +1123,8 @@ function $HttpProvider() { * @description * Shortcut method to perform `GET` request. * - * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -1134,7 +1136,8 @@ function $HttpProvider() { * @description * Shortcut method to perform `DELETE` request. * - * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -1146,7 +1149,8 @@ function $HttpProvider() { * @description * Shortcut method to perform `HEAD` request. * - * @param {string} url Relative or absolute URL specifying the destination of the request + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -1157,11 +1161,18 @@ function $HttpProvider() { * * @description * Shortcut method to perform `JSONP` request. + * + * Note that, since JSONP requests are sensitive because the response is given full acces to the browser, + * the url must be declared, via {@link $sce} as a trusted resource URL. + * You can trust a URL by adding it to the whitelist via + * {@link $sceDelegateProvider#resourceUrlWhitelist `$sceDelegateProvider.resourceUrlWhitelist`} or + * by explicitly trusted the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}. + * * If you would like to customise where and how the callbacks are stored then try overriding * or decorating the {@link $jsonpCallbacks} service. * - * @param {string} url Relative or absolute URL specifying the destination of the request. - * The name of the callback should be the string `JSON_CALLBACK`. + * @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested; + * or an object created by a call to `$sce.trustAsResourceUrl(url)`. * @param {Object=} config Optional configuration object * @returns {HttpPromise} Future object */ @@ -1263,9 +1274,12 @@ function $HttpProvider() { url = config.url; if (lowercase(config.method) === 'jsonp') { - // This is a pretty sensitive operation where we're allowing a script to have full access to + // JSONP is a pretty sensitive operation where we're allowing a script to have full access to // our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL. url = $sce.getTrustedResourceUrl(url); + } else if (!isString(url)) { + // If it is not a string then the URL must be a $sce trusted object + url = $sce.valueOf(url); } url = buildUrl(url, config.paramSerializer(config.params)); diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index 9995451ed0a9..eed435692746 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -306,16 +306,31 @@ describe('$http', function() { it('should throw error if the request configuration is not an object', function() { expect(function() { - $http('/url'); + $http('/url'); }).toThrowMinErr('$http','badreq', 'Http request configuration must be an object. Received: /url'); }); - it('should throw error if the request configuration url is not a string', function() { + it('should throw error if the request configuration url is not a string nor a trusted object', function() { expect(function() { - $http({url: false}); - }).toThrowMinErr('$http','badreq', 'Http request configuration url must be a string. Received: false'); + $http({url: false}); + }).toThrowMinErr('$http','badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: false'); + expect(function() { + $http({url: null}); + }).toThrowMinErr('$http','badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: null'); + expect(function() { + $http({url: 42}); + }).toThrowMinErr('$http','badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: 42'); + expect(function() { + $http({}); + }).toThrowMinErr('$http','badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: undefined'); }); + it('should accept a $sce trusted object for the request configuration url', function() { + expect(function() { + $httpBackend.expect('GET', '/url').respond(''); + $http({url: $sce.trustAsResourceUrl('/url')}); + }).not.toThrowMinErr('$http','badreq', 'Http request configuration url must be a string. Received: false'); + }); it('should send GET requests if no method specified', function() { $httpBackend.expect('GET', '/url').respond(''); @@ -1033,7 +1048,7 @@ describe('$http', function() { function(e) { error = e; } ); $rootScope.$digest(); - expect(error.message).toContain("[$sce:insecurl]"); + expect(error.message).toContain('[$sce:insecurl]'); }); it('should not throw error if the url is an explicitly trusted resource', function() { @@ -1043,7 +1058,7 @@ describe('$http', function() { }).not.toThrow(); }); - it('jsonp() should allow trusted url', inject(['$sce', function($sce) { + it('jsonp() should accept explictly trusted urls', inject(['$sce', function($sce) { $httpBackend.expect('JSONP', '/url').respond(''); $http({method: 'JSONP', url: $sce.trustAsResourceUrl('/url')});