From 38aa441d8eccb8890f9fbacdb4836826f3b00300 Mon Sep 17 00:00:00 2001 From: Caitlin Potter Date: Tue, 14 Oct 2014 14:51:29 -0400 Subject: [PATCH 1/2] fix($templateRequest): ignore JSON Content-Type header and content Normally, if there is a Content-Type header with the string "application/json", or else the content looks sort of JSON-y, $http will attempt to deserialize the JSON into an object. $templateRequest is intended to request markup, and as such should never attempt to parse JSON, regardless of the headers or shape of the content. Closes #5756 --- src/ng/templateRequest.js | 7 ++++++- test/ng/templateRequestSpec.js | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/ng/templateRequest.js b/src/ng/templateRequest.js index 7155718e89a4..06dd070b9e58 100644 --- a/src/ng/templateRequest.js +++ b/src/ng/templateRequest.js @@ -21,11 +21,16 @@ var $compileMinErr = minErr('$compile'); */ function $TemplateRequestProvider() { this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { + var httpOptions = { + cache: $templateCache, + transformResponse: null + }; + function handleRequestFn(tpl, ignoreRequestError) { var self = handleRequestFn; self.totalPendingRequests++; - return $http.get(tpl, { cache : $templateCache }) + return $http.get(tpl, httpOptions) .then(function(response) { var html = response.data; if(!html || html.length === 0) { diff --git a/test/ng/templateRequestSpec.js b/test/ng/templateRequestSpec.js index efc6a182116c..d7c16ae8d212 100644 --- a/test/ng/templateRequestSpec.js +++ b/test/ng/templateRequestSpec.js @@ -86,4 +86,15 @@ describe('$templateRequest', function() { expect($templateRequest.totalPendingRequests).toBe(0); })); + it('should not try to parse a response as JSON', + inject(function($templateRequest, $httpBackend) { + var spy = jasmine.createSpy('success'); + $httpBackend.expectGET('a.html').respond('{{text}}', { + 'Content-Type': 'application/json' + }); + $templateRequest('a.html').then(spy); + $httpBackend.flush(); + expect(spy).toHaveBeenCalledOnce(); + expect(spy.argsForCall[0][0]).toBe('{{text}}'); + })); }); From a33a6936a18d5ca0730a3b55ac94bbe8e01bf506 Mon Sep 17 00:00:00 2001 From: Caitlin Potter Date: Tue, 14 Oct 2014 21:23:02 -0400 Subject: [PATCH 2/2] WIP: make change bigger, but avoid breaking change --- src/.jshintrc | 2 ++ src/ng/http.js | 38 ++++++++++++++++++---------------- src/ng/templateRequest.js | 21 ++++++++++++++----- test/ng/templateRequestSpec.js | 37 +++++++++++++++++++++++++++++++-- 4 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/.jshintrc b/src/.jshintrc index 9bf993719bf0..e833bcf065d2 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -100,6 +100,8 @@ "NODE_TYPE_DOCUMENT": false, "NODE_TYPE_DOCUMENT_FRAGMENT": false, + "httpResponseTransform": false, + /* filters.js */ "getFirstThursdayOfYear": false, diff --git a/src/ng/http.js b/src/ng/http.js index 0ae6d0ef1b81..a04d468bb3cc 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -1,5 +1,24 @@ 'use strict'; +var APPLICATION_JSON = 'application/json'; +var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; +var JSON_START = /^\s*(\[|\{[^\{])/; +var JSON_END = /[\}\]]\s*$/; +var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; + +function httpResponseTransform(data, headers) { + if (isString(data)) { + // strip json vulnerability protection prefix + data = data.replace(JSON_PROTECTION_PREFIX, ''); + var contentType = headers('Content-Type'); + if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) || + (JSON_START.test(data) && JSON_END.test(data))) { + data = fromJson(data); + } + } + return data; +} + /** * Parse headers into key value object * @@ -86,12 +105,6 @@ function isSuccess(status) { * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. * */ function $HttpProvider() { - var JSON_START = /^\s*(\[|\{[^\{])/, - JSON_END = /[\}\]]\s*$/, - PROTECTION_PREFIX = /^\)\]\}',?\n/, - APPLICATION_JSON = 'application/json', - CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; - /** * @ngdoc property * @name $httpProvider#defaults @@ -115,18 +128,7 @@ function $HttpProvider() { **/ var defaults = this.defaults = { // transform incoming response data - transformResponse: [function defaultHttpResponseTransform(data, headers) { - if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(PROTECTION_PREFIX, ''); - var contentType = headers('Content-Type'); - if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) || - (JSON_START.test(data) && JSON_END.test(data))) { - data = fromJson(data); - } - } - return data; - }], + transformResponse: [httpResponseTransform], // transform outgoing request data transformRequest: [function(d) { diff --git a/src/ng/templateRequest.js b/src/ng/templateRequest.js index 06dd070b9e58..475dae4b07f8 100644 --- a/src/ng/templateRequest.js +++ b/src/ng/templateRequest.js @@ -21,15 +21,26 @@ var $compileMinErr = minErr('$compile'); */ function $TemplateRequestProvider() { this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { - var httpOptions = { - cache: $templateCache, - transformResponse: null - }; - function handleRequestFn(tpl, ignoreRequestError) { var self = handleRequestFn; self.totalPendingRequests++; + var transformResponse = $http.defaults && $http.defaults.transformResponse; + var idx; + + if (isArray(transformResponse) && + (idx = transformResponse.indexOf(httpResponseTransform)) >= 0) { + transformResponse = copy(transformResponse, []); + transformResponse.splice(idx, 1); + } else if (transformResponse === httpResponseTransform) { + transformResponse = null; + } + + var httpOptions = { + cache: $templateCache, + transformResponse: transformResponse + }; + return $http.get(tpl, httpOptions) .then(function(response) { var html = response.data; diff --git a/test/ng/templateRequestSpec.js b/test/ng/templateRequestSpec.js index d7c16ae8d212..dbb36a4161bc 100644 --- a/test/ng/templateRequestSpec.js +++ b/test/ng/templateRequestSpec.js @@ -94,7 +94,40 @@ describe('$templateRequest', function() { }); $templateRequest('a.html').then(spy); $httpBackend.flush(); - expect(spy).toHaveBeenCalledOnce(); - expect(spy.argsForCall[0][0]).toBe('{{text}}'); + expect(spy).toHaveBeenCalledOnceWith('{{text}}'); })); + + it('should use custom response transformers (array)', function() { + module(function($httpProvider) { + $httpProvider.defaults.transformResponse.push(function(data) { + return data + '!!'; + }); + }); + inject(function($templateRequest, $httpBackend) { + var spy = jasmine.createSpy('success'); + $httpBackend.expectGET('a.html').respond('{{text}}', { + 'Content-Type': 'application/json' + }); + $templateRequest('a.html').then(spy); + $httpBackend.flush(); + expect(spy).toHaveBeenCalledOnceWith('{{text}}!!'); + }); + }); + + it('should use custom response transformers (function)', function() { + module(function($httpProvider) { + $httpProvider.defaults.transformResponse = function(data) { + return data + '!!'; + }; + }); + inject(function($templateRequest, $httpBackend) { + var spy = jasmine.createSpy('success'); + $httpBackend.expectGET('a.html').respond('{{text}}', { + 'Content-Type': 'application/json' + }); + $templateRequest('a.html').then(spy); + $httpBackend.flush(); + expect(spy).toHaveBeenCalledOnceWith('{{text}}!!'); + }); + }); });