diff --git a/HISTORY.md b/HISTORY.md
index ea2210b..d5e662a 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,3 +1,7 @@
+# [2.0.2](https://github.com/EventSource/eventsource/compare/v2.0.1...v2.0.2)
+
+* Do not include authorization and cookie headers on redirect to different origin ([#273](https://github.com/EventSource/eventsource/pull/273) Espen Hovlandsdal)
+
# [2.0.1](https://github.com/EventSource/eventsource/compare/v2.0.0...v2.0.1)
* Fix `URL is not a constructor` error for browser ([#268](https://github.com/EventSource/eventsource/pull/268) Ajinkya Rajput)
@@ -7,6 +11,10 @@
* BREAKING: Node >= 12 now required ([#152](https://github.com/EventSource/eventsource/pull/152) @HonkingGoose)
* Preallocate buffer size when reading data for increased performance with large messages ([#239](https://github.com/EventSource/eventsource/pull/239) Pau Freixes)
* Removed dependency on url-parser. Fixes [CVE-2022-0512](https://www.whitesourcesoftware.com/vulnerability-database/CVE-2022-0512) & [CVE-2022-0691](https://nvd.nist.gov/vuln/detail/CVE-2022-0691) ([#249](https://github.com/EventSource/eventsource/pull/249) Alex Hladin)
+
+# [1.1.1](https://github.com/EventSource/eventsource/compare/v1.1.0...v1.1.1)
+
+* Do not include authorization and cookie headers on redirect to different origin ([#273](https://github.com/EventSource/eventsource/pull/273) Espen Hovlandsdal)
# [1.1.0](https://github.com/EventSource/eventsource/compare/v1.0.7...v1.1.0)
diff --git a/example/eventsource-polyfill.js b/example/eventsource-polyfill.js
index 163280b..50fda2c 100644
--- a/example/eventsource-polyfill.js
+++ b/example/eventsource-polyfill.js
@@ -2141,7 +2141,7 @@
/**/
-var pna = __webpack_require__(7);
+var pna = __webpack_require__(6);
/**/
/**/
@@ -2359,6 +2359,127 @@
/***/ }),
/* 6 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+/* WEBPACK VAR INJECTION */(function(process) {
+
+if (typeof process === 'undefined' ||
+ !process.version ||
+ process.version.indexOf('v0.') === 0 ||
+ process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) {
+ module.exports = { nextTick: nextTick };
+} else {
+ module.exports = process
+}
+
+function nextTick(fn, arg1, arg2, arg3) {
+ if (typeof fn !== 'function') {
+ throw new TypeError('"callback" argument must be a function');
+ }
+ var len = arguments.length;
+ var args, i;
+ switch (len) {
+ case 0:
+ case 1:
+ return process.nextTick(fn);
+ case 2:
+ return process.nextTick(function afterTickOne() {
+ fn.call(null, arg1);
+ });
+ case 3:
+ return process.nextTick(function afterTickTwo() {
+ fn.call(null, arg1, arg2);
+ });
+ case 4:
+ return process.nextTick(function afterTickThree() {
+ fn.call(null, arg1, arg2, arg3);
+ });
+ default:
+ args = new Array(len - 1);
+ i = 0;
+ while (i < args.length) {
+ args[i++] = arguments[i];
+ }
+ return process.nextTick(function afterTick() {
+ fn.apply(null, args);
+ });
+ }
+}
+
+
+/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))
+
+/***/ }),
+/* 7 */
+/***/ (function(module, exports, __webpack_require__) {
+
+/* eslint-disable node/no-deprecated-api */
+var buffer = __webpack_require__(3)
+var Buffer = buffer.Buffer
+
+// alternative to using Object.keys for old browsers
+function copyProps (src, dst) {
+ for (var key in src) {
+ dst[key] = src[key]
+ }
+}
+if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) {
+ module.exports = buffer
+} else {
+ // Copy properties from require('buffer')
+ copyProps(buffer, exports)
+ exports.Buffer = SafeBuffer
+}
+
+function SafeBuffer (arg, encodingOrOffset, length) {
+ return Buffer(arg, encodingOrOffset, length)
+}
+
+// Copy static methods from Buffer
+copyProps(Buffer, SafeBuffer)
+
+SafeBuffer.from = function (arg, encodingOrOffset, length) {
+ if (typeof arg === 'number') {
+ throw new TypeError('Argument must not be a number')
+ }
+ return Buffer(arg, encodingOrOffset, length)
+}
+
+SafeBuffer.alloc = function (size, fill, encoding) {
+ if (typeof size !== 'number') {
+ throw new TypeError('Argument must be a number')
+ }
+ var buf = Buffer(size)
+ if (fill !== undefined) {
+ if (typeof encoding === 'string') {
+ buf.fill(fill, encoding)
+ } else {
+ buf.fill(fill)
+ }
+ } else {
+ buf.fill(0)
+ }
+ return buf
+}
+
+SafeBuffer.allocUnsafe = function (size) {
+ if (typeof size !== 'number') {
+ throw new TypeError('Argument must be a number')
+ }
+ return Buffer(size)
+}
+
+SafeBuffer.allocUnsafeSlow = function (size) {
+ if (typeof size !== 'number') {
+ throw new TypeError('Argument must be a number')
+ }
+ return buffer.SlowBuffer(size)
+}
+
+
+/***/ }),
+/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
@@ -3097,127 +3218,6 @@
/***/ }),
-/* 7 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-/* WEBPACK VAR INJECTION */(function(process) {
-
-if (typeof process === 'undefined' ||
- !process.version ||
- process.version.indexOf('v0.') === 0 ||
- process.version.indexOf('v1.') === 0 && process.version.indexOf('v1.8.') !== 0) {
- module.exports = { nextTick: nextTick };
-} else {
- module.exports = process
-}
-
-function nextTick(fn, arg1, arg2, arg3) {
- if (typeof fn !== 'function') {
- throw new TypeError('"callback" argument must be a function');
- }
- var len = arguments.length;
- var args, i;
- switch (len) {
- case 0:
- case 1:
- return process.nextTick(fn);
- case 2:
- return process.nextTick(function afterTickOne() {
- fn.call(null, arg1);
- });
- case 3:
- return process.nextTick(function afterTickTwo() {
- fn.call(null, arg1, arg2);
- });
- case 4:
- return process.nextTick(function afterTickThree() {
- fn.call(null, arg1, arg2, arg3);
- });
- default:
- args = new Array(len - 1);
- i = 0;
- while (i < args.length) {
- args[i++] = arguments[i];
- }
- return process.nextTick(function afterTick() {
- fn.apply(null, args);
- });
- }
-}
-
-
-/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1)))
-
-/***/ }),
-/* 8 */
-/***/ (function(module, exports, __webpack_require__) {
-
-/* eslint-disable node/no-deprecated-api */
-var buffer = __webpack_require__(3)
-var Buffer = buffer.Buffer
-
-// alternative to using Object.keys for old browsers
-function copyProps (src, dst) {
- for (var key in src) {
- dst[key] = src[key]
- }
-}
-if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) {
- module.exports = buffer
-} else {
- // Copy properties from require('buffer')
- copyProps(buffer, exports)
- exports.Buffer = SafeBuffer
-}
-
-function SafeBuffer (arg, encodingOrOffset, length) {
- return Buffer(arg, encodingOrOffset, length)
-}
-
-// Copy static methods from Buffer
-copyProps(Buffer, SafeBuffer)
-
-SafeBuffer.from = function (arg, encodingOrOffset, length) {
- if (typeof arg === 'number') {
- throw new TypeError('Argument must not be a number')
- }
- return Buffer(arg, encodingOrOffset, length)
-}
-
-SafeBuffer.alloc = function (size, fill, encoding) {
- if (typeof size !== 'number') {
- throw new TypeError('Argument must be a number')
- }
- var buf = Buffer(size)
- if (fill !== undefined) {
- if (typeof encoding === 'string') {
- buf.fill(fill, encoding)
- } else {
- buf.fill(fill)
- }
- } else {
- buf.fill(0)
- }
- return buf
-}
-
-SafeBuffer.allocUnsafe = function (size) {
- if (typeof size !== 'number') {
- throw new TypeError('Argument must be a number')
- }
- return Buffer(size)
-}
-
-SafeBuffer.allocUnsafeSlow = function (size) {
- if (typeof size !== 'number') {
- throw new TypeError('Argument must be a number')
- }
- return buffer.SlowBuffer(size)
-}
-
-
-/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
@@ -3740,7 +3740,7 @@
var response = __webpack_require__(13)
var extend = __webpack_require__(41)
var statusCodes = __webpack_require__(42)
-var url = __webpack_require__(6)
+var url = __webpack_require__(8)
var http = exports
@@ -4177,7 +4177,7 @@
/**/
-var pna = __webpack_require__(7);
+var pna = __webpack_require__(6);
/**/
module.exports = Readable;
@@ -4206,7 +4206,7 @@
/**/
-var Buffer = __webpack_require__(8).Buffer;
+var Buffer = __webpack_require__(7).Buffer;
var OurUint8Array = global.Uint8Array || function () {};
function _uint8ArrayToBuffer(chunk) {
return Buffer.from(chunk);
@@ -5189,7 +5189,7 @@
/**/
-var pna = __webpack_require__(7);
+var pna = __webpack_require__(6);
/**/
// undocumented cb() API, needed for core, not for public API
@@ -5294,7 +5294,7 @@
/**/
-var pna = __webpack_require__(7);
+var pna = __webpack_require__(6);
/**/
module.exports = Writable;
@@ -5347,7 +5347,7 @@
/**/
-var Buffer = __webpack_require__(8).Buffer;
+var Buffer = __webpack_require__(7).Buffer;
var OurUint8Array = global.Uint8Array || function () {};
function _uint8ArrayToBuffer(chunk) {
return Buffer.from(chunk);
@@ -5984,7 +5984,7 @@
/**/
-var Buffer = __webpack_require__(8).Buffer;
+var Buffer = __webpack_require__(7).Buffer;
/**/
var isEncoding = Buffer.isEncoding || function (encoding) {
@@ -6495,8 +6495,7 @@
/* 22 */
/***/ (function(module, exports, __webpack_require__) {
-/* WEBPACK VAR INJECTION */(function(process, Buffer) {var parse = __webpack_require__(6).parse
-var URL = __webpack_require__(6).URL
+/* WEBPACK VAR INJECTION */(function(process, Buffer) {var parse = __webpack_require__(8).parse
var events = __webpack_require__(9)
var https = __webpack_require__(31)
var http = __webpack_require__(11)
@@ -6514,6 +6513,8 @@
var carriageReturn = 13
// Beyond 256KB we could not observe any gain in performance
var maxBufferAheadAllocation = 1024 * 256
+// Headers matching the pattern should be removed when redirecting to different origin
+var reUnsafeHeader = /^(cookie|authorization)$/i
function hasBom (buf) {
return bom.every(function (charCode, index) {
@@ -6530,6 +6531,8 @@
**/
function EventSource (url, eventSourceInitDict) {
var readyState = EventSource.CONNECTING
+ var headers = eventSourceInitDict && eventSourceInitDict.headers
+ var hasNewOrigin = false
Object.defineProperty(this, 'readyState', {
get: function () {
return readyState
@@ -6551,11 +6554,12 @@
readyState = EventSource.CONNECTING
_emit('error', new Event('error', {message: message}))
- // The url may have been changed by a temporary
- // redirect. If that's the case, revert it now.
+ // The url may have been changed by a temporary redirect. If that's the case,
+ // revert it now, and flag that we are no longer pointing to a new origin
if (reconnectUrl) {
url = reconnectUrl
reconnectUrl = null
+ hasNewOrigin = false
}
setTimeout(function () {
if (readyState !== EventSource.CONNECTING || self.connectionInProgress) {
@@ -6568,9 +6572,9 @@
var req
var lastEventId = ''
- if (eventSourceInitDict && eventSourceInitDict.headers && eventSourceInitDict.headers['Last-Event-ID']) {
- lastEventId = eventSourceInitDict.headers['Last-Event-ID']
- delete eventSourceInitDict.headers['Last-Event-ID']
+ if (headers && headers['Last-Event-ID']) {
+ lastEventId = headers['Last-Event-ID']
+ delete headers['Last-Event-ID']
}
var discardTrailingNewline = false
@@ -6584,9 +6588,10 @@
var isSecure = options.protocol === 'https:'
options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' }
if (lastEventId) options.headers['Last-Event-ID'] = lastEventId
- if (eventSourceInitDict && eventSourceInitDict.headers) {
- for (var i in eventSourceInitDict.headers) {
- var header = eventSourceInitDict.headers[i]
+ if (headers) {
+ var reqHeaders = hasNewOrigin ? removeUnsafeHeaders(headers) : headers
+ for (var i in reqHeaders) {
+ var header = reqHeaders[i]
if (header) {
options.headers[i] = header
}
@@ -6646,13 +6651,17 @@
// Handle HTTP redirects
if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
- if (!res.headers.location) {
+ var location = res.headers.location
+ if (!location) {
// Server sent redirect response without Location header.
_emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage}))
return
}
+ var prevOrigin = new URL(url).origin
+ var nextOrigin = new URL(location).origin
+ hasNewOrigin = prevOrigin !== nextOrigin
if (res.statusCode === 307) reconnectUrl = url
- url = res.headers.location
+ url = location
process.nextTick(connect)
return
}
@@ -6960,6 +6969,26 @@
Object.defineProperty(this, f, { writable: false, value: eventInitDict[f], enumerable: true })
}
}
+}
+
+/**
+ * Returns a new object of headers that does not include any authorization and cookie headers
+ *
+ * @param {Object} headers An object of headers ({[headerName]: headerValue})
+ * @return {Object} a new object of headers
+ * @api private
+ */
+function removeUnsafeHeaders (headers) {
+ var safe = {}
+ for (var key in headers) {
+ if (reUnsafeHeader.test(key)) {
+ continue
+ }
+
+ safe[key] = headers[key]
+ }
+
+ return safe
}
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(1), __webpack_require__(3).Buffer))
@@ -8001,7 +8030,7 @@
/***/ (function(module, exports, __webpack_require__) {
var http = __webpack_require__(11)
-var url = __webpack_require__(6)
+var url = __webpack_require__(8)
var https = module.exports
@@ -8382,7 +8411,7 @@
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
-var Buffer = __webpack_require__(8).Buffer;
+var Buffer = __webpack_require__(7).Buffer;
var util = __webpack_require__(35);
function copyBuffer(src, target, offset) {
diff --git a/lib/eventsource.js b/lib/eventsource.js
index ffcbe37..bd401a1 100644
--- a/lib/eventsource.js
+++ b/lib/eventsource.js
@@ -16,6 +16,8 @@
var carriageReturn = 13
// Beyond 256KB we could not observe any gain in performance
var maxBufferAheadAllocation = 1024 * 256
+// Headers matching the pattern should be removed when redirecting to different origin
+var reUnsafeHeader = /^(cookie|authorization)$/i
function hasBom (buf) {
return bom.every(function (charCode, index) {
@@ -32,6 +34,8 @@
**/
function EventSource (url, eventSourceInitDict) {
var readyState = EventSource.CONNECTING
+ var headers = eventSourceInitDict && eventSourceInitDict.headers
+ var hasNewOrigin = false
Object.defineProperty(this, 'readyState', {
get: function () {
return readyState
@@ -53,11 +57,12 @@
readyState = EventSource.CONNECTING
_emit('error', new Event('error', {message: message}))
- // The url may have been changed by a temporary
- // redirect. If that's the case, revert it now.
+ // The url may have been changed by a temporary redirect. If that's the case,
+ // revert it now, and flag that we are no longer pointing to a new origin
if (reconnectUrl) {
url = reconnectUrl
reconnectUrl = null
+ hasNewOrigin = false
}
setTimeout(function () {
if (readyState !== EventSource.CONNECTING || self.connectionInProgress) {
@@ -70,9 +75,9 @@
var req
var lastEventId = ''
- if (eventSourceInitDict && eventSourceInitDict.headers && eventSourceInitDict.headers['Last-Event-ID']) {
- lastEventId = eventSourceInitDict.headers['Last-Event-ID']
- delete eventSourceInitDict.headers['Last-Event-ID']
+ if (headers && headers['Last-Event-ID']) {
+ lastEventId = headers['Last-Event-ID']
+ delete headers['Last-Event-ID']
}
var discardTrailingNewline = false
@@ -86,9 +91,10 @@
var isSecure = options.protocol === 'https:'
options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' }
if (lastEventId) options.headers['Last-Event-ID'] = lastEventId
- if (eventSourceInitDict && eventSourceInitDict.headers) {
- for (var i in eventSourceInitDict.headers) {
- var header = eventSourceInitDict.headers[i]
+ if (headers) {
+ var reqHeaders = hasNewOrigin ? removeUnsafeHeaders(headers) : headers
+ for (var i in reqHeaders) {
+ var header = reqHeaders[i]
if (header) {
options.headers[i] = header
}
@@ -148,13 +154,17 @@
// Handle HTTP redirects
if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307) {
- if (!res.headers.location) {
+ var location = res.headers.location
+ if (!location) {
// Server sent redirect response without Location header.
_emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage}))
return
}
+ var prevOrigin = new URL(url).origin
+ var nextOrigin = new URL(location).origin
+ hasNewOrigin = prevOrigin !== nextOrigin
if (res.statusCode === 307) reconnectUrl = url
- url = res.headers.location
+ url = location
process.nextTick(connect)
return
}
@@ -463,3 +473,23 @@
}
}
}
+
+/**
+ * Returns a new object of headers that does not include any authorization and cookie headers
+ *
+ * @param {Object} headers An object of headers ({[headerName]: headerValue})
+ * @return {Object} a new object of headers
+ * @api private
+ */
+function removeUnsafeHeaders (headers) {
+ var safe = {}
+ for (var key in headers) {
+ if (reUnsafeHeader.test(key)) {
+ continue
+ }
+
+ safe[key] = headers[key]
+ }
+
+ return safe
+}
diff --git a/package-lock.json b/package-lock.json
index 54bd1dc..fd57dca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "eventsource",
- "version": "2.0.1",
+ "version": "2.0.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "eventsource",
- "version": "2.0.1",
+ "version": "2.0.2",
"license": "MIT",
"devDependencies": {
"buffer-from": "^1.1.1",
diff --git a/package.json b/package.json
index 9f362e3..ad90321 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eventsource",
- "version": "2.0.1",
+ "version": "2.0.2",
"description": "W3C compliant EventSource client for Node.js and browser (polyfill)",
"keywords": [
"eventsource",
diff --git a/test/eventsource_test.js b/test/eventsource_test.js
index 31f6eea..8900042 100644
--- a/test/eventsource_test.js
+++ b/test/eventsource_test.js
@@ -576,6 +576,49 @@
es.onopen = function () {
assert.ok(clientRequestedRedirectUrl)
assert.equal(server.url + redirectSuffix, es.url)
+ server.close(done)
+ }
+ })
+ })
+
+ it('follows http ' + status + ' redirects, drops sensitive headers on origin change', function (done) {
+ var redirectSuffix = '/foobar'
+ var clientRequestedRedirectUrl = false
+ var receivedHeaders = {}
+ createServer(function (err, server) {
+ if (err) return done(err)
+
+ var newServerUrl = server.url.replace('http://localhost', 'http://127.0.0.1')
+
+ server.on('request', function (req, res) {
+ if (req.url === '/') {
+ res.writeHead(status, {
+ 'Connection': 'Close',
+ 'Location': newServerUrl + redirectSuffix
+ })
+ res.end()
+ } else if (req.url === redirectSuffix) {
+ clientRequestedRedirectUrl = true
+ receivedHeaders = req.headers
+ res.writeHead(200, {'Content-Type': 'text/event-stream'})
+ res.end()
+ }
+ })
+
+ var es = new EventSource(server.url, {
+ headers: {
+ keep: 'me',
+ authorization: 'Bearer someToken',
+ cookie: 'some-cookie=yep'
+ }
+ })
+
+ es.onopen = function () {
+ assert.ok(clientRequestedRedirectUrl)
+ assert.equal(newServerUrl + redirectSuffix, es.url)
+ assert.equal(receivedHeaders.keep, 'me', 'safe header no longer present')
+ assert.equal(typeof receivedHeaders.authorization, 'undefined', 'authorization header still present')
+ assert.equal(typeof receivedHeaders.cookie, 'undefined', 'cookie header still present')
server.close(done)
}
})