Skip to content

Commit

Permalink
Update XMLHttpRequest.getAllResponseHeaders() implementation (#32353) (
Browse files Browse the repository at this point in the history
…#32363)

Summary:
As per the XMLHttpRequest specification [1], getAllResponseHeaders() should return a string of headers with lowercased names and sorted by their uppercase representation, with each header ending with '\r\n'.

[1] https://xhr.spec.whatwg.org/#the-getallresponseheaders()-method

## Changelog

[General] [Changed] XMLHttpRequest.getAllResponseHeaders() now returns headers with names lowercased and sorted in ascending order, as per specification

Pull Request resolved: #32363

Test Plan:
Test derived from Web Platform Test repository:
https://github.com/web-platform-tests/wpt/tree/master/xhr

Reviewed By: yungsters

Differential Revision: D31626217

Pulled By: sota000

fbshipit-source-id: 299d005facbe1c15b8cda5eed6750db75addca80
  • Loading branch information
ascherkus authored and facebook-github-bot committed Oct 26, 2021
1 parent 65af5eb commit b2415c4
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 7 deletions.
49 changes: 43 additions & 6 deletions Libraries/Network/XMLHttpRequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,49 @@ class XMLHttpRequest extends (EventTarget(...XHR_EVENTS): any) {
// according to the spec, return null if no response has been received
return null;
}
const headers = this.responseHeaders || {};
return Object.keys(headers)
.map(headerName => {
return headerName + ': ' + headers[headerName];
})
.join('\r\n');

// Assign to non-nullable local variable.
const responseHeaders = this.responseHeaders;

const unsortedHeaders: Map<
string,
{lowerHeaderName: string, upperHeaderName: string, headerValue: string},
> = new Map();
for (const rawHeaderName of Object.keys(responseHeaders)) {
const headerValue = responseHeaders[rawHeaderName];
const lowerHeaderName = rawHeaderName.toLowerCase();
const header = unsortedHeaders.get(lowerHeaderName);
if (header) {
header.headerValue += ', ' + headerValue;
unsortedHeaders.set(lowerHeaderName, header);
} else {
unsortedHeaders.set(lowerHeaderName, {
lowerHeaderName,
upperHeaderName: rawHeaderName.toUpperCase(),
headerValue,
});
}
}

// Sort in ascending order, with a being less than b if a's name is legacy-uppercased-byte less than b's name.
const sortedHeaders = [...unsortedHeaders.values()].sort((a, b) => {
if (a.upperHeaderName < b.upperHeaderName) {
return -1;
}
if (a.upperHeaderName > b.upperHeaderName) {
return 1;
}
return 0;
});

// Combine into single text response.
return (
sortedHeaders
.map(header => {
return header.lowerHeaderName + ': ' + header.headerValue;
})
.join('\r\n') + '\r\n'
);
}

getResponseHeader(header: string): ?string {
Expand Down
20 changes: 19 additions & 1 deletion Libraries/Network/__tests__/XMLHttpRequest-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ describe('XMLHttpRequest', function() {
});

expect(xhr.getAllResponseHeaders()).toBe(
'Content-Type: text/plain; charset=utf-8\r\n' + 'Content-Length: 32',
'content-length: 32\r\n' + 'content-type: text/plain; charset=utf-8\r\n',
);
});

Expand Down Expand Up @@ -292,4 +292,22 @@ describe('XMLHttpRequest', function() {
);
expect(GlobalPerformanceLogger.stopTimespan).not.toHaveBeenCalled();
});

it('should sort and lowercase response headers', function() {
// Derived from XHR Web Platform Test: https://github.com/web-platform-tests/wpt/blob/master/xhr/getallresponseheaders.htm
xhr.open('GET', 'blabla');
xhr.send();
setRequestId(10);
xhr.__didReceiveResponse(requestId, 200, {
'foo-TEST': '1',
'FOO-test': '2',
__Custom: 'token',
'ALSO-here': 'Mr. PB',
ewok: 'lego',
});

expect(xhr.getAllResponseHeaders()).toBe(
'also-here: Mr. PB\r\newok: lego\r\nfoo-test: 1, 2\r\n__custom: token\r\n',
);
});
});

0 comments on commit b2415c4

Please sign in to comment.