From 9122213aa22c87040debd776e7c5143ad46c30b0 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 29 Sep 2020 22:10:16 -0400 Subject: [PATCH 1/3] return Access-Control-Expose-Headers only if content request -Access-Control-Expose-Headers is only for what content response headers can be visible inside Javascript, nobody will do XHR preflight OPTIONS verb by hand, saves bytes on wire and undefined behaviour -dont deref request.corsAnywhereRequestState.corsMaxAge unless value used source https://web.archive.org/web/20200110204952/https://www.html5rocks.com/static/images/cors_server_flowchart.png --- lib/cors-anywhere.js | 13 +++++++++---- test/test.js | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/cors-anywhere.js b/lib/cors-anywhere.js index 90026448..7a3d34dd 100644 --- a/lib/cors-anywhere.js +++ b/lib/cors-anywhere.js @@ -52,9 +52,12 @@ function isValidHostName(hostname) { */ function withCORS(headers, request) { headers['access-control-allow-origin'] = '*'; - var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; - if (request.method === 'OPTIONS' && corsMaxAge) { - headers['access-control-max-age'] = corsMaxAge; + + if (request.method === 'OPTIONS') { + var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; + if (corsMaxAge) { + headers['access-control-max-age'] = corsMaxAge; + } } if (request.headers['access-control-request-method']) { headers['access-control-allow-methods'] = request.headers['access-control-request-method']; @@ -65,7 +68,9 @@ function withCORS(headers, request) { delete request.headers['access-control-request-headers']; } - headers['access-control-expose-headers'] = Object.keys(headers).join(','); + if (request.method !== 'OPTIONS') { + headers['access-control-expose-headers'] = Object.keys(headers).join(','); + } return headers; } diff --git a/test/test.js b/test/test.js index 3795a506..c1bba5f6 100644 --- a/test/test.js +++ b/test/test.js @@ -277,6 +277,7 @@ describe('Basic functionality', function() { request(cors_anywhere) .options('/') .expect('Access-Control-Allow-Origin', '*') + .expectNoHeader('access-control-expose-headers') .expect(200, '', done); }); @@ -288,6 +289,7 @@ describe('Basic functionality', function() { .expect('Access-Control-Allow-Origin', '*') .expect('Access-Control-Allow-Methods', 'DELETE') .expect('Access-Control-Allow-Headers', 'X-Tralala') + .expectNoHeader('access-control-expose-headers') .expect(200, '', done); }); @@ -297,6 +299,7 @@ describe('Basic functionality', function() { request(cors_anywhere) .options('//bogus') .expect('Access-Control-Allow-Origin', '*') + .expectNoHeader('access-control-expose-headers') .expect(200, '', done); }); From b453f56eaa3ac6d61a35530beb6f4ecfe9acaea3 Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 29 Sep 2020 22:56:53 -0400 Subject: [PATCH 2/3] "Access-Control-Expose-Headers" shouldnt contain access-control-* values -XHR code will not inspect the A-C-* values, it can't even get them with getResponseHeader() if their is a fault with A-C-* headers moving "headers['access-control-allow-origin'] = '*';" to after Expose-Headers computation keeps "access-control-allow-origin" out of Expose-Headers value --- lib/cors-anywhere.js | 12 +++++++----- test/test.js | 12 ++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/cors-anywhere.js b/lib/cors-anywhere.js index 7a3d34dd..70b5f767 100644 --- a/lib/cors-anywhere.js +++ b/lib/cors-anywhere.js @@ -51,14 +51,18 @@ function isValidHostName(hostname) { * @param request {ServerRequest} */ function withCORS(headers, request) { - headers['access-control-allow-origin'] = '*'; - if (request.method === 'OPTIONS') { var corsMaxAge = request.corsAnywhereRequestState.corsMaxAge; if (corsMaxAge) { headers['access-control-max-age'] = corsMaxAge; } } + else { + headers['access-control-expose-headers'] = Object.keys(headers).join(','); + } + + headers['access-control-allow-origin'] = '*'; + if (request.headers['access-control-request-method']) { headers['access-control-allow-methods'] = request.headers['access-control-request-method']; delete request.headers['access-control-request-method']; @@ -68,9 +72,7 @@ function withCORS(headers, request) { delete request.headers['access-control-request-headers']; } - if (request.method !== 'OPTIONS') { - headers['access-control-expose-headers'] = Object.keys(headers).join(','); - } + return headers; } diff --git a/test/test.js b/test/test.js index c1bba5f6..15b795d2 100644 --- a/test/test.js +++ b/test/test.js @@ -29,6 +29,17 @@ request.Test.prototype.expectNoHeader = function(header, done) { return done ? this.end(done) : this; }; +request.Test.prototype.expectHeaderNotMatch = function(header, regexp, done) { + this.expect(function(res) { + var headerVal = res.headers[header.toLowerCase()]; + if (regexp.test(headerVal)) { + return new Error('Header "' + header + '" should not contain "' + + String(regexp) + '" got "' + headerVal + '"'); + } + }); + return done ? this.end(done) : this; +}; + var cors_anywhere; var cors_anywhere_port; function stopServer(done) { @@ -92,6 +103,7 @@ describe('Basic functionality', function() { .get('/example.com') .expect('Access-Control-Allow-Origin', '*') .expect('x-request-url', 'http://example.com/') + .expectHeaderNotMatch('access-control-expose-headers', /access-control-allow-origin/) .expect(200, 'Response from example.com', done); }); From 455cdb672272a0742f0c5e391ca05cab08c85b7f Mon Sep 17 00:00:00 2001 From: bulk88 Date: Tue, 29 Sep 2020 21:37:48 -0400 Subject: [PATCH 3/3] A-C-Expose-Headers: dont include the 7 CORS-safelisted response-headers https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name saves bytes over wire --- lib/cors-anywhere.js | 12 +++++++++--- test/setup.js | 10 +++++++++- test/test.js | 3 +++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/cors-anywhere.js b/lib/cors-anywhere.js index 70b5f767..42001a68 100644 --- a/lib/cors-anywhere.js +++ b/lib/cors-anywhere.js @@ -56,9 +56,15 @@ function withCORS(headers, request) { if (corsMaxAge) { headers['access-control-max-age'] = corsMaxAge; } - } - else { - headers['access-control-expose-headers'] = Object.keys(headers).join(','); + } else { + headers['access-control-expose-headers'] = Object.keys(headers).filter( + function(header){ + if (header.match(/^cache-control|content-language|content-length|content-type|expires|last-modified|pragma$/)) { + return false; + } else { + return header; + } + }).join(','); } headers['access-control-allow-origin'] = '*'; diff --git a/test/setup.js b/test/setup.js index 61a1d019..76940434 100644 --- a/test/setup.js +++ b/test/setup.js @@ -54,8 +54,16 @@ function echoheaders(origin) { nock('http://example.com') .persist() +// replyContentLength() has no effect unless response body is JSON and will get +// stringified, use defaultReplyHeaders() instead +// https://github.com/nock/nock/blob/626021770b9b2fa52860c19f6b7a6033d64125e3/lib/interceptor.js#L176 + .defaultReplyHeaders({ + 'Content-Length': function (req, res, body) {return body.length;}, + }) .get('/') - .reply(200, 'Response from example.com') + .reply(200, 'Response from example.com', { + 'Content-Type': 'text/plain', + }) .post('/echopost') .reply(200, function(uri, requestBody) { diff --git a/test/test.js b/test/test.js index 15b795d2..8995aa29 100644 --- a/test/test.js +++ b/test/test.js @@ -103,6 +103,9 @@ describe('Basic functionality', function() { .get('/example.com') .expect('Access-Control-Allow-Origin', '*') .expect('x-request-url', 'http://example.com/') + .expect('Content-Length', '25') + .expectHeaderNotMatch('access-control-expose-headers', /content-length/) + .expectHeaderNotMatch('access-control-expose-headers', /content-type/) .expectHeaderNotMatch('access-control-expose-headers', /access-control-allow-origin/) .expect(200, 'Response from example.com', done); });