From b2415c48669391ee1ab7c6450748c4d91097a666 Mon Sep 17 00:00:00 2001 From: Andrew Scherkus Date: Mon, 25 Oct 2021 19:44:28 -0700 Subject: [PATCH] Update XMLHttpRequest.getAllResponseHeaders() implementation (#32353) (#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: https://github.com/facebook/react-native/pull/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 --- Libraries/Network/XMLHttpRequest.js | 49 ++++++++++++++++--- .../Network/__tests__/XMLHttpRequest-test.js | 20 +++++++- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/Libraries/Network/XMLHttpRequest.js b/Libraries/Network/XMLHttpRequest.js index c12888f087a146..083e0abadfe675 100644 --- a/Libraries/Network/XMLHttpRequest.js +++ b/Libraries/Network/XMLHttpRequest.js @@ -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 { diff --git a/Libraries/Network/__tests__/XMLHttpRequest-test.js b/Libraries/Network/__tests__/XMLHttpRequest-test.js index 91d2ce1e252c98..f769d356f78881 100644 --- a/Libraries/Network/__tests__/XMLHttpRequest-test.js +++ b/Libraries/Network/__tests__/XMLHttpRequest-test.js @@ -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', ); }); @@ -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', + ); + }); });