fix(http): headers should be case-insensitive.

fixes #9452

spec at https://tools.ietf.org/html/rfc2616
This commit is contained in:
Zhicheng Wang 2016-07-26 12:30:43 +08:00 committed by Victor Berchet
parent e34a04d2ad
commit 7f647822bd
4 changed files with 40 additions and 25 deletions

View File

@ -152,18 +152,18 @@ export class XHRConnection implements Connection {
case ContentType.NONE:
break;
case ContentType.JSON:
_xhr.setRequestHeader('Content-Type', 'application/json');
_xhr.setRequestHeader('content-type', 'application/json');
break;
case ContentType.FORM:
_xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
_xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
break;
case ContentType.TEXT:
_xhr.setRequestHeader('Content-Type', 'text/plain');
_xhr.setRequestHeader('content-type', 'text/plain');
break;
case ContentType.BLOB:
var blob = req.blob();
if (blob.type) {
_xhr.setRequestHeader('Content-Type', blob.type);
_xhr.setRequestHeader('content-type', blob.type);
}
break;
}

View File

@ -56,7 +56,7 @@ export class Headers {
// headers instanceof StringMap
StringMapWrapper.forEach(headers, (v: any, k: string) => {
this._headersMap.set(k, isListLikeIterable(v) ? v : [v]);
this._headersMap.set(normalize(k), isListLikeIterable(v) ? v : [v]);
});
}
@ -68,13 +68,16 @@ export class Headers {
.split('\n')
.map(val => val.split(':'))
.map(([key, ...parts]) => ([key.trim(), parts.join(':').trim()]))
.reduce((headers, [key, value]) => !headers.set(key, value) && headers, new Headers());
.reduce(
(headers, [key, value]) => !headers.set(normalize(key), value) && headers,
new Headers());
}
/**
* Appends a header to existing list of header values for a given header name.
*/
append(name: string, value: string): void {
name = normalize(name);
var mapName = this._headersMap.get(name);
var list = isListLikeIterable(mapName) ? mapName : [];
list.push(value);
@ -84,7 +87,7 @@ export class Headers {
/**
* Deletes all header values for the given name.
*/
delete (name: string): void { this._headersMap.delete(name); }
delete (name: string): void { this._headersMap.delete(normalize(name)); }
forEach(fn: (values: string[], name: string, headers: Map<string, string[]>) => void): void {
this._headersMap.forEach(fn);
@ -93,12 +96,12 @@ export class Headers {
/**
* Returns first header that matches given name.
*/
get(header: string): string { return ListWrapper.first(this._headersMap.get(header)); }
get(header: string): string { return ListWrapper.first(this._headersMap.get(normalize(header))); }
/**
* Check for existence of header by given name.
*/
has(header: string): boolean { return this._headersMap.has(header); }
has(header: string): boolean { return this._headersMap.has(normalize(header)); }
/**
* Provides names of set headers
@ -118,7 +121,7 @@ export class Headers {
list.push(<string>value);
}
this._headersMap.set(header, list);
this._headersMap.set(normalize(header), list);
}
/**
@ -137,7 +140,7 @@ export class Headers {
iterateListLike(
values, (val: any /** TODO #9100 */) => list = ListWrapper.concat(list, val.split(',')));
(serializableHeaders as any /** TODO #9100 */)[name] = list;
(serializableHeaders as any /** TODO #9100 */)[normalize(name)] = list;
});
return serializableHeaders;
}
@ -146,7 +149,7 @@ export class Headers {
* Returns list of header values for a given name.
*/
getAll(header: string): string[] {
var headers = this._headersMap.get(header);
var headers = this._headersMap.get(normalize(header));
return isListLikeIterable(headers) ? headers : [];
}
@ -155,3 +158,11 @@ export class Headers {
*/
entries() { throw new BaseException('"entries" method is not implemented on Headers class'); }
}
// "HTTP character sets are identified by case-insensitive tokens"
// Spec at https://tools.ietf.org/html/rfc2616
// This implementation is same as NodeJS.
// see https://nodejs.org/dist/latest-v6.x/docs/api/http.html#http_message_headers
function normalize(name: string): string {
return name.toLowerCase();
}

View File

@ -236,9 +236,9 @@ export function main() {
var connection = new XHRConnection(
new Request(base.merge(new RequestOptions({headers: headers}))), new MockBrowserXHR());
connection.response.subscribe();
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/xml');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Breaking-Bad', '<3');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('X-Multi', 'a,b');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/xml');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('breaking-bad', '<3');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('x-multi', 'a,b');
});
it('should skip content type detection if custom content type header is set', () => {
@ -249,8 +249,8 @@ export function main() {
new Request(base.merge(new RequestOptions({body: body, headers: headers}))),
new MockBrowserXHR());
connection.response.subscribe();
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/plain');
expect(setRequestHeaderSpy).not.toHaveBeenCalledWith('Content-Type', 'application/json');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/plain');
expect(setRequestHeaderSpy).not.toHaveBeenCalledWith('content-type', 'application/json');
});
it('should use object body and detect content type header to the request', () => {
@ -260,7 +260,7 @@ export function main() {
new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR());
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith(Json.stringify(body));
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'application/json');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'application/json');
});
it('should use number body and detect content type header to the request', () => {
@ -270,7 +270,7 @@ export function main() {
new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR());
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith('23');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/plain');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/plain');
});
it('should use string body and detect content type header to the request', () => {
@ -280,7 +280,7 @@ export function main() {
new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR());
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith(body);
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/plain');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/plain');
});
it('should use URLSearchParams body and detect content type header to the request', () => {
@ -294,7 +294,7 @@ export function main() {
expect(sendSpy).toHaveBeenCalledWith('test1=val1&test2=val2');
expect(setRequestHeaderSpy)
.toHaveBeenCalledWith(
'Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
'content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
});
if ((global as any /** TODO #9100 */)['Blob']) {
@ -335,7 +335,7 @@ export function main() {
new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR());
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith(body);
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/css');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/css');
});
it('should use blob body without type to the request', () => {
@ -358,7 +358,7 @@ export function main() {
new MockBrowserXHR());
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith(body);
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/css');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/css');
});
it('should use array buffer body to the request', () => {
@ -389,7 +389,7 @@ export function main() {
new MockBrowserXHR());
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith(body);
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', 'text/css');
expect(setRequestHeaderSpy).toHaveBeenCalledWith('content-type', 'text/css');
});
}

View File

@ -20,6 +20,10 @@ export function main() {
var firstHeaders = new Headers(); // Currently empty
firstHeaders.append('Content-Type', 'image/jpeg');
expect(firstHeaders.get('Content-Type')).toBe('image/jpeg');
// "HTTP character sets are identified by case-insensitive tokens"
// Spec at https://tools.ietf.org/html/rfc2616
expect(firstHeaders.get('content-type')).toBe('image/jpeg');
expect(firstHeaders.get('content-Type')).toBe('image/jpeg');
var httpHeaders = StringMapWrapper.create();
StringMapWrapper.set(httpHeaders, 'Content-Type', 'image/jpeg');
StringMapWrapper.set(httpHeaders, 'Accept-Charset', 'utf-8');
@ -72,7 +76,7 @@ export function main() {
beforeEach(() => {
headers = new Headers();
inputArr = ['application/jeisen', 'application/jason', 'application/patrickjs'];
obj = {'Accept': inputArr};
obj = {'accept': inputArr};
headers.set('Accept', inputArr);
});