diff --git a/src/RESTController.js b/src/RESTController.js index 552c3d058..59090e76e 100644 --- a/src/RESTController.js +++ b/src/RESTController.js @@ -111,11 +111,14 @@ const RESTController = { let response; try { response = JSON.parse(xhr.responseText); + const availableHeaders = typeof xhr.getAllResponseHeaders === 'function' ? xhr.getAllResponseHeaders() : ""; headers = {}; - if (typeof xhr.getResponseHeader === 'function' && xhr.getResponseHeader('access-control-expose-headers')) { + if (typeof xhr.getResponseHeader === 'function' && availableHeaders?.indexOf('access-control-expose-headers') >= 0) { const responseHeaders = xhr.getResponseHeader('access-control-expose-headers').split(', '); responseHeaders.forEach(header => { - headers[header] = xhr.getResponseHeader(header.toLowerCase()); + if (availableHeaders.indexOf(header.toLowerCase()) >= 0) { + headers[header] = xhr.getResponseHeader(header.toLowerCase()); + } }); } } catch (e) { diff --git a/src/__tests__/RESTController-test.js b/src/__tests__/RESTController-test.js index 02d72e94d..640d83bc5 100644 --- a/src/__tests__/RESTController-test.js +++ b/src/__tests__/RESTController-test.js @@ -221,6 +221,9 @@ describe('RESTController', () => { getResponseHeader: function (header) { return headers[header]; }, + getAllResponseHeaders: function() { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); + }, send: function () { this.status = 200; this.responseText = '{}'; @@ -241,6 +244,9 @@ describe('RESTController', () => { getResponseHeader: function (header) { return headers[header]; }, + getAllResponseHeaders: function() { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\n'); + }, send: function () { this.status = 200; this.responseText = '{}'; @@ -253,6 +259,63 @@ describe('RESTController', () => { expect(response._headers['X-Parse-Push-Status-Id']).toBe('5678'); }); + it('does not call getRequestHeader with no headers or no getAllResponseHeaders', async () => { + const XHR = function () {}; + XHR.prototype = { + open: function () {}, + setRequestHeader: function () {}, + getResponseHeader: jest.fn(), + send: function () { + this.status = 200; + this.responseText = '{"result":"hello"}'; + this.readyState = 4; + this.onreadystatechange(); + }, + }; + RESTController._setXHR(XHR); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); + + XHR.prototype.getAllResponseHeaders = jest.fn(); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getAllResponseHeaders.mock.calls.length).toBe(1); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(0); + }); + + it('does not invoke Chrome browser console error on getResponseHeader', async () => { + const headers = { + 'access-control-expose-headers': 'a, b, c', + 'a' : 'value', + 'b' : 'value', + 'c' : 'value', + } + const XHR = function () {}; + XHR.prototype = { + open: function () {}, + setRequestHeader: function () {}, + getResponseHeader: jest.fn(key => { + if (Object.keys(headers).includes(key)) { + return headers[key]; + } + throw new Error("Chrome creates a console error here."); + }), + getAllResponseHeaders: jest.fn(() => { + return Object.keys(headers).map(key => `${key}: ${headers[key]}`).join('\r\n'); + }), + send: function () { + this.status = 200; + this.responseText = '{"result":"hello"}'; + this.readyState = 4; + this.onreadystatechange(); + }, + }; + RESTController._setXHR(XHR); + await RESTController.request('GET', 'classes/MyObject', {}, {}); + expect(XHR.prototype.getAllResponseHeaders.mock.calls.length).toBe(1); + expect(XHR.prototype.getResponseHeader.mock.calls.length).toBe(4); + }); + + it('handles invalid header', async () => { const XHR = function () {}; XHR.prototype = {