feat(security): strip XSSI prefix from XHR responses.

This commit is contained in:
Martin Probst 2016-05-05 14:25:44 -07:00
parent 9099160038
commit df1b1f6957
2 changed files with 31 additions and 2 deletions

View File

@ -6,11 +6,13 @@ import {Headers} from '../headers';
import {ResponseOptions} from '../base_response_options'; import {ResponseOptions} from '../base_response_options';
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {BrowserXhr} from './browser_xhr'; import {BrowserXhr} from './browser_xhr';
import {isPresent} from '../../src/facade/lang'; import {isPresent, isString} from '../../src/facade/lang';
import {Observable} from 'rxjs/Observable'; import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer'; import {Observer} from 'rxjs/Observer';
import {isSuccess, getResponseURL} from '../http_utils'; import {isSuccess, getResponseURL} from '../http_utils';
const XSSI_PREFIX = ')]}\',\n';
/** /**
* Creates connections using `XMLHttpRequest`. Given a fully-qualified * Creates connections using `XMLHttpRequest`. Given a fully-qualified
* request, an `XHRConnection` will immediately create an `XMLHttpRequest` object and send the * 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<Response>((responseObserver: Observer<Response>) => { this.response = new Observable<Response>((responseObserver: Observer<Response>) => {
let _xhr: XMLHttpRequest = browserXHR.build(); let _xhr: XMLHttpRequest = browserXHR.build();
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url); _xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
// load event handler // load event handler
let onLoad = () => { let onLoad = () => {
// responseText is the old-school way of retrieving response (supported by IE8 & 9) // responseText is the old-school way of retrieving response (supported by IE8 & 9)
// response/responseType properties were introduced in XHR Level2 spec (supported by // response/responseType properties were introduced in XHR Level2 spec (supported by
// IE10) // IE10)
let body = isPresent(_xhr.response) ? _xhr.response : _xhr.responseText; 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 headers = Headers.fromResponseHeaderString(_xhr.getAllResponseHeaders());
let url = getResponseURL(_xhr); let url = getResponseURL(_xhr);

View File

@ -273,6 +273,29 @@ export function main() {
existingXHRs[0].dispatchEvent('load'); 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', it('should parse response headers and add them to the response',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => { inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var statusCode = 200; var statusCode = 200;