Skip to content

Commit ed9c2b6

Browse files
vicbchuckjaz
authored andcommitted
fix(Header): preserve case of the first init, set() or append() (angular#12023)
fixes angular#11624
1 parent 1cf5f5f commit ed9c2b6

File tree

3 files changed

+178
-134
lines changed

3 files changed

+178
-134
lines changed

modules/@angular/http/src/headers.ts

Lines changed: 71 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ListWrapper, MapWrapper, StringMapWrapper, isListLikeIterable, iterateListLike} from '../src/facade/collection';
9+
import {MapWrapper} from '../src/facade/collection';
1010

1111
/**
1212
* Polyfill for [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers/Headers), as
@@ -15,7 +15,7 @@ import {ListWrapper, MapWrapper, StringMapWrapper, isListLikeIterable, iterateLi
1515
* The only known difference between this `Headers` implementation and the spec is the
1616
* lack of an `entries` method.
1717
*
18-
* ### Example ([live demo](http://plnkr.co/edit/MTdwT6?p=preview))
18+
* ### Example
1919
*
2020
* ```
2121
* import {Headers} from '@angular/http';
@@ -37,38 +37,46 @@ import {ListWrapper, MapWrapper, StringMapWrapper, isListLikeIterable, iterateLi
3737
* @experimental
3838
*/
3939
export class Headers {
40-
/** @internal */
41-
_headersMap: Map<string, string[]>;
42-
constructor(headers?: Headers|{[key: string]: any}) {
43-
if (headers instanceof Headers) {
44-
this._headersMap = new Map<string, string[]>((<Headers>headers)._headersMap);
40+
/** @internal header names are lower case */
41+
_headers: Map<string, string[]> = new Map();
42+
/** @internal map lower case names to actual names */
43+
_normalizedNames: Map<string, string> = new Map();
44+
45+
// TODO(vicb): any -> string|string[]
46+
constructor(headers?: Headers|{[name: string]: any}) {
47+
if (!headers) {
4548
return;
4649
}
4750

48-
this._headersMap = new Map<string, string[]>();
49-
50-
if (!headers) {
51+
if (headers instanceof Headers) {
52+
headers._headers.forEach((value: string[], name: string) => {
53+
const lcName = name.toLowerCase();
54+
this._headers.set(lcName, value);
55+
this.mayBeSetNormalizedName(name);
56+
});
5157
return;
5258
}
5359

54-
// headers instanceof StringMap
55-
StringMapWrapper.forEach(headers, (v: any, k: string) => {
56-
this._headersMap.set(normalize(k), isListLikeIterable(v) ? v : [v]);
60+
Object.keys(headers).forEach((name: string) => {
61+
const value = headers[name];
62+
const lcName = name.toLowerCase();
63+
this._headers.set(lcName, Array.isArray(value) ? value : [value]);
64+
this.mayBeSetNormalizedName(name);
5765
});
5866
}
5967

6068
/**
6169
* Returns a new Headers instance from the given DOMString of Response Headers
6270
*/
6371
static fromResponseHeaderString(headersString: string): Headers {
64-
let headers = new Headers();
72+
const headers = new Headers();
6573

6674
headersString.split('\n').forEach(line => {
6775
const index = line.indexOf(':');
6876
if (index > 0) {
69-
const key = line.substring(0, index);
70-
const value = line.substring(index + 1).trim();
71-
headers.set(key, value);
77+
const name = line.slice(0, index);
78+
const value = line.slice(index + 1).trim();
79+
headers.set(name, value);
7280
}
7381
});
7482

@@ -79,92 +87,94 @@ export class Headers {
7987
* Appends a header to existing list of header values for a given header name.
8088
*/
8189
append(name: string, value: string): void {
82-
name = normalize(name);
83-
var mapName = this._headersMap.get(name);
84-
var list = isListLikeIterable(mapName) ? mapName : [];
85-
list.push(value);
86-
this._headersMap.set(name, list);
90+
const values = this.getAll(name);
91+
this.set(name, values === null ? [value] : [...values, value]);
8792
}
8893

8994
/**
9095
* Deletes all header values for the given name.
9196
*/
92-
delete (name: string): void { this._headersMap.delete(normalize(name)); }
97+
delete (name: string): void {
98+
const lcName = name.toLowerCase();
99+
this._normalizedNames.delete(lcName);
100+
this._headers.delete(lcName);
101+
}
93102

94103
forEach(fn: (values: string[], name: string, headers: Map<string, string[]>) => void): void {
95-
this._headersMap.forEach(fn);
104+
this._headers.forEach(
105+
(values, lcName) => fn(values, this._normalizedNames.get(lcName), this._headers));
96106
}
97107

98108
/**
99109
* Returns first header that matches given name.
100110
*/
101-
get(header: string): string { return ListWrapper.first(this._headersMap.get(normalize(header))); }
111+
get(name: string): string {
112+
const values = this.getAll(name);
113+
114+
if (values === null) {
115+
return null;
116+
}
117+
118+
return values.length > 0 ? values[0] : null;
119+
}
102120

103121
/**
104-
* Check for existence of header by given name.
122+
* Checks for existence of header by given name.
105123
*/
106-
has(header: string): boolean { return this._headersMap.has(normalize(header)); }
124+
has(name: string): boolean { return this._headers.has(name.toLowerCase()); }
107125

108126
/**
109-
* Provides names of set headers
127+
* Returns the names of the headers
110128
*/
111-
keys(): string[] { return MapWrapper.keys(this._headersMap); }
129+
keys(): string[] { return MapWrapper.values(this._normalizedNames); }
112130

113131
/**
114132
* Sets or overrides header value for given name.
115133
*/
116-
set(header: string, value: string|string[]): void {
117-
var list: string[] = [];
118-
119-
if (isListLikeIterable(value)) {
120-
var pushValue = (<string[]>value).join(',');
121-
list.push(pushValue);
122-
} else {
123-
list.push(<string>value);
124-
}
125-
126-
this._headersMap.set(normalize(header), list);
134+
set(name: string, value: string|string[]): void {
135+
const strValue = Array.isArray(value) ? value.join(',') : value;
136+
this._headers.set(name.toLowerCase(), [strValue]);
137+
this.mayBeSetNormalizedName(name);
127138
}
128139

129140
/**
130141
* Returns values of all headers.
131142
*/
132-
values(): string[][] { return MapWrapper.values(this._headersMap); }
143+
values(): string[][] { return MapWrapper.values(this._headers); }
133144

134145
/**
135146
* Returns string of all headers.
136147
*/
137-
toJSON(): {[key: string]: any} {
138-
let serializableHeaders = {};
139-
this._headersMap.forEach((values: string[], name: string) => {
140-
let list: any[] /** TODO #9100 */ = [];
141-
142-
iterateListLike(
143-
values, (val: any /** TODO #9100 */) => list = ListWrapper.concat(list, val.split(',')));
144-
145-
(serializableHeaders as any /** TODO #9100 */)[normalize(name)] = list;
148+
// TODO(vicb): returns {[name: string]: string[]}
149+
toJSON(): {[name: string]: any} {
150+
const serialized: {[name: string]: string[]} = {};
151+
152+
this._headers.forEach((values: string[], name: string) => {
153+
const split: string[] = [];
154+
values.forEach(v => split.push(...v.split(',')));
155+
serialized[this._normalizedNames.get(name)] = split;
146156
});
147-
return serializableHeaders;
157+
158+
return serialized;
148159
}
149160

150161
/**
151162
* Returns list of header values for a given name.
152163
*/
153-
getAll(header: string): string[] {
154-
var headers = this._headersMap.get(normalize(header));
155-
return isListLikeIterable(headers) ? headers : [];
164+
getAll(name: string): string[] {
165+
return this.has(name) ? this._headers.get(name.toLowerCase()) : null;
156166
}
157167

158168
/**
159169
* This method is not implemented.
160170
*/
161171
entries() { throw new Error('"entries" method is not implemented on Headers class'); }
162-
}
163172

164-
// "HTTP character sets are identified by case-insensitive tokens"
165-
// Spec at https://tools.ietf.org/html/rfc2616
166-
// This implementation is same as NodeJS.
167-
// see https://nodejs.org/dist/latest-v6.x/docs/api/http.html#http_message_headers
168-
function normalize(name: string): string {
169-
return name.toLowerCase();
173+
private mayBeSetNormalizedName(name: string): void {
174+
const lcName = name.toLowerCase();
175+
176+
if (!this._normalizedNames.has(lcName)) {
177+
this._normalizedNames.set(lcName, name);
178+
}
179+
}
170180
}

0 commit comments

Comments
 (0)