Skip to content

Commit ab278c8

Browse files
HeroicEricbmac
authored andcommitted
[BUGFIX beta] Allow optional spaces when parsing response headers
Fixes #4122 - Allows header field-value without preceding whitespace - Removes leading/trailing whitespace from header field-names - Removes leading/trailing whitespace from header field-values - Moves `parseResponseHeaders` function out of `DS.RestAdapter` to its own module The main reason for moving to a module was to make it easier to unit test in order to document current behavior before making the fix. Let me know if this is undesirable. (cherry picked from commit dce22e3)
1 parent 091cf53 commit ab278c8

File tree

3 files changed

+94
-21
lines changed

3 files changed

+94
-21
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import EmptyObject from 'ember-data/-private/system/empty-object';
2+
3+
const CLRF = '\u000d\u000a';
4+
5+
export default function parseResponseHeaders(headersString) {
6+
let headers = new EmptyObject();
7+
8+
if (!headersString) {
9+
return headers;
10+
}
11+
12+
let headerPairs = headersString.split(CLRF);
13+
14+
headerPairs.forEach((header) => {
15+
let [field, ...value] = header.split(':');
16+
17+
field = field.trim();
18+
value = value.join(':').trim();
19+
20+
if (value) {
21+
headers[field] = value;
22+
}
23+
});
24+
25+
return headers;
26+
}

addon/adapters/rest.js

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {
1010
TimeoutError,
1111
AbortError
1212
} from 'ember-data/-private/adapters/errors';
13-
import EmptyObject from "ember-data/-private/system/empty-object";
1413
import BuildURLMixin from "ember-data/-private/adapters/build-url-mixin";
1514
import isEnabled from 'ember-data/-private/features';
15+
import parseResponseHeaders from 'ember-data/-private/utils/parse-response-headers';
1616

1717
const {
1818
MapWithDefault,
@@ -1006,26 +1006,6 @@ export default Adapter.extend(BuildURLMixin, {
10061006
}
10071007
});
10081008

1009-
function parseResponseHeaders(headerStr) {
1010-
var headers = new EmptyObject();
1011-
if (!headerStr) { return headers; }
1012-
1013-
var headerPairs = headerStr.split('\u000d\u000a');
1014-
for (var i = 0; i < headerPairs.length; i++) {
1015-
var headerPair = headerPairs[i];
1016-
// Can't use split() here because it does the wrong thing
1017-
// if the header value has the string ": " in it.
1018-
var index = headerPair.indexOf('\u003a\u0020');
1019-
if (index > 0) {
1020-
var key = headerPair.substring(0, index);
1021-
var val = headerPair.substring(index + 2);
1022-
headers[key] = val;
1023-
}
1024-
}
1025-
1026-
return headers;
1027-
}
1028-
10291009
//From http://stackoverflow.com/questions/280634/endswith-in-javascript
10301010
function endsWith(string, suffix) {
10311011
if (typeof String.prototype.endsWith !== 'function') {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import EmptyObject from 'ember-data/-private/system/empty-object';
2+
import parseResponseHeaders from 'ember-data/-private/utils/parse-response-headers';
3+
import { module, test } from 'qunit';
4+
5+
const CRLF = '\u000d\u000a';
6+
7+
module('unit/adapters/parse-response-headers');
8+
9+
test('returns an EmptyObject when headersString is undefined', function(assert) {
10+
let headers = parseResponseHeaders(undefined);
11+
12+
assert.deepEqual(headers, new EmptyObject(), 'EmptyObject is returned');
13+
});
14+
15+
test('header parsing', function(assert) {
16+
let headersString = [
17+
'Content-Encoding: gzip',
18+
'content-type: application/json; charset=utf-8',
19+
'date: Fri, 05 Feb 2016 21:47:56 GMT'
20+
].join(CRLF);
21+
22+
let headers = parseResponseHeaders(headersString);
23+
24+
assert.equal(headers['Content-Encoding'], 'gzip', 'parses basic header pair');
25+
assert.equal(headers['content-type'], 'application/json; charset=utf-8', 'parses header with complex value');
26+
assert.equal(headers['date'], 'Fri, 05 Feb 2016 21:47:56 GMT', 'parses header with date value');
27+
});
28+
29+
test('field-name parsing', function(assert) {
30+
let headersString = [
31+
' name-with-leading-whitespace: some value',
32+
'name-with-whitespace-before-colon : another value'
33+
].join(CRLF);
34+
35+
let headers = parseResponseHeaders(headersString);
36+
37+
assert.equal(headers['name-with-leading-whitespace'], 'some value', 'strips leading whitespace from field-name');
38+
assert.equal(headers['name-with-whitespace-before-colon'], 'another value', 'strips whitespace before colon from field-name');
39+
});
40+
41+
test('field-value parsing', function(assert) {
42+
let headersString = [
43+
'value-with-leading-space: value with leading whitespace',
44+
'value-without-leading-space:value without leading whitespace',
45+
'value-with-colon: value with: a colon',
46+
'value-with-trailing-whitespace: banana '
47+
].join(CRLF);
48+
49+
let headers = parseResponseHeaders(headersString);
50+
51+
assert.equal(headers['value-with-leading-space'], 'value with leading whitespace', 'strips leading whitespace in field-value');
52+
assert.equal(headers['value-without-leading-space'], 'value without leading whitespace', 'works without leaading whitespace in field-value');
53+
assert.equal(headers['value-with-colon'], 'value with: a colon', 'has correct value when value contains a colon');
54+
assert.equal(headers['value-with-trailing-whitespace'], 'banana', 'strips trailing whitespace from field-value');
55+
});
56+
57+
test('ignores headers that do not contain a colon', function(assert) {
58+
let headersString = [
59+
'Content-Encoding: gzip',
60+
'I am ignored because I do not contain a colon'
61+
].join(CRLF);
62+
63+
let headers = parseResponseHeaders(headersString);
64+
65+
assert.deepEqual(headers['Content-Encoding'], 'gzip', 'parses basic header pair');
66+
assert.equal(Object.keys(headers).length, 1, 'only has the one valid header');
67+
});

0 commit comments

Comments
 (0)