From df1b1f695768cf31e86e71f3755eae99ed8f1b60 Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Thu, 5 May 2016 14:25:44 -0700 Subject: [PATCH] feat(security): strip XSSI prefix from XHR responses. --- .../@angular/http/src/backends/xhr_backend.ts | 10 ++++++-- .../http/test/backends/xhr_backend_spec.ts | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/modules/@angular/http/src/backends/xhr_backend.ts b/modules/@angular/http/src/backends/xhr_backend.ts index 3e57f04d0c..db6a87e4db 100644 --- a/modules/@angular/http/src/backends/xhr_backend.ts +++ b/modules/@angular/http/src/backends/xhr_backend.ts @@ -6,11 +6,13 @@ import {Headers} from '../headers'; import {ResponseOptions} from '../base_response_options'; import {Injectable} from '@angular/core'; import {BrowserXhr} from './browser_xhr'; -import {isPresent} from '../../src/facade/lang'; +import {isPresent, isString} from '../../src/facade/lang'; import {Observable} from 'rxjs/Observable'; import {Observer} from 'rxjs/Observer'; import {isSuccess, getResponseURL} from '../http_utils'; +const XSSI_PREFIX = ')]}\',\n'; + /** * Creates connections using `XMLHttpRequest`. Given a fully-qualified * request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the @@ -32,13 +34,17 @@ export class XHRConnection implements Connection { this.response = new Observable((responseObserver: Observer) => { let _xhr: XMLHttpRequest = browserXHR.build(); _xhr.open(RequestMethod[req.method].toUpperCase(), req.url); + // load event handler let onLoad = () => { // responseText is the old-school way of retrieving response (supported by IE8 & 9) // response/responseType properties were introduced in XHR Level2 spec (supported by // IE10) let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText; - + // Implicitly strip a potential XSSI prefix. + if (isString(body) && body.startsWith(XSSI_PREFIX)) { + body = body.substring(XSSI_PREFIX.length); + } let headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders()); let url = getResponseURL(_xhr); diff --git a/modules/@angular/http/test/backends/xhr_backend_spec.ts b/modules/@angular/http/test/backends/xhr_backend_spec.ts index b93fe60fbd..bbc5b5ced9 100644 --- a/modules/@angular/http/test/backends/xhr_backend_spec.ts +++ b/modules/@angular/http/test/backends/xhr_backend_spec.ts @@ -273,6 +273,29 @@ export function main() { existingXHRs[0].dispatchEvent('load'); })); + it('should strip XSSI prefixes', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { + var conn = new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions()); + conn.response.subscribe((res: Response) => { + expect(res.text()).toBe('{json: "object"}'); + async.done(); + }); + existingXHRs[0].setStatusCode(200); + existingXHRs[0].setResponseText(')]}\',\n{json: "object"}'); + existingXHRs[0].dispatchEvent('load'); + })); + + it('should strip XSSI prefix from errors', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { + var conn = + new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions()); + conn.response.subscribe(null, (res: Response) => { + expect(res.text()).toBe('{json: "object"}'); + async.done(); + }); + existingXHRs[0].setStatusCode(404); + existingXHRs[0].setResponseText(')]}\',\n{json: "object"}'); + existingXHRs[0].dispatchEvent('load'); + })); + it('should parse response headers and add them to the response', inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { var statusCode = 200;