18736 lines
702 KiB
JavaScript
18736 lines
702 KiB
JavaScript
'use strict';
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
var require$$2 = require('util');
|
|
var require$$0 = require('buffer');
|
|
var require$$1 = require('events');
|
|
var require$$0$1 = require('stream');
|
|
var require$$1$1 = require('crypto');
|
|
var require$$2$1 = require('url');
|
|
var require$$0$2 = require('assert');
|
|
var require$$1$2 = require('net');
|
|
var require$$2$2 = require('tls');
|
|
var require$$1$3 = require('@firebase/util');
|
|
var require$$2$3 = require('tslib');
|
|
var require$$3 = require('@firebase/logger');
|
|
var component = require('@firebase/component');
|
|
|
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
|
|
var require$$2__default = /*#__PURE__*/_interopDefaultLegacy(require$$2);
|
|
var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
|
|
var require$$1__default = /*#__PURE__*/_interopDefaultLegacy(require$$1);
|
|
var require$$0__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$0$1);
|
|
var require$$1__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$1$1);
|
|
var require$$2__default$1 = /*#__PURE__*/_interopDefaultLegacy(require$$2$1);
|
|
var require$$0__default$2 = /*#__PURE__*/_interopDefaultLegacy(require$$0$2);
|
|
var require$$1__default$2 = /*#__PURE__*/_interopDefaultLegacy(require$$1$2);
|
|
var require$$2__default$2 = /*#__PURE__*/_interopDefaultLegacy(require$$2$2);
|
|
var require$$1__default$3 = /*#__PURE__*/_interopDefaultLegacy(require$$1$3);
|
|
var require$$2__default$3 = /*#__PURE__*/_interopDefaultLegacy(require$$2$3);
|
|
var require$$3__default = /*#__PURE__*/_interopDefaultLegacy(require$$3);
|
|
|
|
var index_standalone = {};
|
|
|
|
var safeBuffer = {exports: {}};
|
|
|
|
/*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
|
|
|
(function (module, exports) {
|
|
/* eslint-disable node/no-deprecated-api */
|
|
var buffer = require$$0__default["default"];
|
|
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)
|
|
}
|
|
|
|
SafeBuffer.prototype = Object.create(Buffer.prototype);
|
|
|
|
// 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)
|
|
};
|
|
}(safeBuffer, safeBuffer.exports));
|
|
|
|
var streams$1 = {};
|
|
|
|
/**
|
|
|
|
Streams in a WebSocket connection
|
|
---------------------------------
|
|
|
|
We model a WebSocket as two duplex streams: one stream is for the wire protocol
|
|
over an I/O socket, and the other is for incoming/outgoing messages.
|
|
|
|
|
|
+----------+ +---------+ +----------+
|
|
[1] write(chunk) -->| ~~~~~~~~ +----->| parse() +----->| ~~~~~~~~ +--> emit('data') [2]
|
|
| | +----+----+ | |
|
|
| | | | |
|
|
| IO | | [5] | Messages |
|
|
| | V | |
|
|
| | +---------+ | |
|
|
[4] emit('data') <--+ ~~~~~~~~ |<-----+ frame() |<-----+ ~~~~~~~~ |<-- write(chunk) [3]
|
|
+----------+ +---------+ +----------+
|
|
|
|
|
|
Message transfer in each direction is simple: IO receives a byte stream [1] and
|
|
sends this stream for parsing. The parser will periodically emit a complete
|
|
message text on the Messages stream [2]. Similarly, when messages are written
|
|
to the Messages stream [3], they are framed using the WebSocket wire format and
|
|
emitted via IO [4].
|
|
|
|
There is a feedback loop via [5] since some input from [1] will be things like
|
|
ping, pong and close frames. In these cases the protocol responds by emitting
|
|
responses directly back to [4] rather than emitting messages via [2].
|
|
|
|
For the purposes of flow control, we consider the sources of each Readable
|
|
stream to be as follows:
|
|
|
|
* [2] receives input from [1]
|
|
* [4] receives input from [1] and [3]
|
|
|
|
The classes below express the relationships described above without prescribing
|
|
anything about how parse() and frame() work, other than assuming they emit
|
|
'data' events to the IO and Messages streams. They will work with any protocol
|
|
driver having these two methods.
|
|
**/
|
|
|
|
|
|
var Stream$3 = require$$0__default$1["default"].Stream,
|
|
util$c = require$$2__default["default"];
|
|
|
|
|
|
var IO = function(driver) {
|
|
this.readable = this.writable = true;
|
|
this._paused = false;
|
|
this._driver = driver;
|
|
};
|
|
util$c.inherits(IO, Stream$3);
|
|
|
|
// The IO pause() and resume() methods will be called when the socket we are
|
|
// piping to gets backed up and drains. Since IO output [4] comes from IO input
|
|
// [1] and Messages input [3], we need to tell both of those to return false
|
|
// from write() when this stream is paused.
|
|
|
|
IO.prototype.pause = function() {
|
|
this._paused = true;
|
|
this._driver.messages._paused = true;
|
|
};
|
|
|
|
IO.prototype.resume = function() {
|
|
this._paused = false;
|
|
this.emit('drain');
|
|
|
|
var messages = this._driver.messages;
|
|
messages._paused = false;
|
|
messages.emit('drain');
|
|
};
|
|
|
|
// When we receive input from a socket, send it to the parser and tell the
|
|
// source whether to back off.
|
|
IO.prototype.write = function(chunk) {
|
|
if (!this.writable) return false;
|
|
this._driver.parse(chunk);
|
|
return !this._paused;
|
|
};
|
|
|
|
// The IO end() method will be called when the socket piping into it emits
|
|
// 'close' or 'end', i.e. the socket is closed. In this situation the Messages
|
|
// stream will not emit any more data so we emit 'end'.
|
|
IO.prototype.end = function(chunk) {
|
|
if (!this.writable) return;
|
|
if (chunk !== undefined) this.write(chunk);
|
|
this.writable = false;
|
|
|
|
var messages = this._driver.messages;
|
|
if (messages.readable) {
|
|
messages.readable = messages.writable = false;
|
|
messages.emit('end');
|
|
}
|
|
};
|
|
|
|
IO.prototype.destroy = function() {
|
|
this.end();
|
|
};
|
|
|
|
|
|
var Messages = function(driver) {
|
|
this.readable = this.writable = true;
|
|
this._paused = false;
|
|
this._driver = driver;
|
|
};
|
|
util$c.inherits(Messages, Stream$3);
|
|
|
|
// The Messages pause() and resume() methods will be called when the app that's
|
|
// processing the messages gets backed up and drains. If we're emitting
|
|
// messages too fast we should tell the source to slow down. Message output [2]
|
|
// comes from IO input [1].
|
|
|
|
Messages.prototype.pause = function() {
|
|
this._driver.io._paused = true;
|
|
};
|
|
|
|
Messages.prototype.resume = function() {
|
|
this._driver.io._paused = false;
|
|
this._driver.io.emit('drain');
|
|
};
|
|
|
|
// When we receive messages from the user, send them to the formatter and tell
|
|
// the source whether to back off.
|
|
Messages.prototype.write = function(message) {
|
|
if (!this.writable) return false;
|
|
if (typeof message === 'string') this._driver.text(message);
|
|
else this._driver.binary(message);
|
|
return !this._paused;
|
|
};
|
|
|
|
// The Messages end() method will be called when a stream piping into it emits
|
|
// 'end'. Many streams may be piped into the WebSocket and one of them ending
|
|
// does not mean the whole socket is done, so just process the input and move
|
|
// on leaving the socket open.
|
|
Messages.prototype.end = function(message) {
|
|
if (message !== undefined) this.write(message);
|
|
};
|
|
|
|
Messages.prototype.destroy = function() {};
|
|
|
|
|
|
streams$1.IO = IO;
|
|
streams$1.Messages = Messages;
|
|
|
|
var Headers$3 = function() {
|
|
this.clear();
|
|
};
|
|
|
|
Headers$3.prototype.ALLOWED_DUPLICATES = ['set-cookie', 'set-cookie2', 'warning', 'www-authenticate'];
|
|
|
|
Headers$3.prototype.clear = function() {
|
|
this._sent = {};
|
|
this._lines = [];
|
|
};
|
|
|
|
Headers$3.prototype.set = function(name, value) {
|
|
if (value === undefined) return;
|
|
|
|
name = this._strip(name);
|
|
value = this._strip(value);
|
|
|
|
var key = name.toLowerCase();
|
|
if (!this._sent.hasOwnProperty(key) || this.ALLOWED_DUPLICATES.indexOf(key) >= 0) {
|
|
this._sent[key] = true;
|
|
this._lines.push(name + ': ' + value + '\r\n');
|
|
}
|
|
};
|
|
|
|
Headers$3.prototype.toString = function() {
|
|
return this._lines.join('');
|
|
};
|
|
|
|
Headers$3.prototype._strip = function(string) {
|
|
return string.toString().replace(/^ */, '').replace(/ *$/, '');
|
|
};
|
|
|
|
var headers = Headers$3;
|
|
|
|
var Buffer$9 = safeBuffer.exports.Buffer;
|
|
|
|
var StreamReader = function() {
|
|
this._queue = [];
|
|
this._queueSize = 0;
|
|
this._offset = 0;
|
|
};
|
|
|
|
StreamReader.prototype.put = function(buffer) {
|
|
if (!buffer || buffer.length === 0) return;
|
|
if (!Buffer$9.isBuffer(buffer)) buffer = Buffer$9.from(buffer);
|
|
this._queue.push(buffer);
|
|
this._queueSize += buffer.length;
|
|
};
|
|
|
|
StreamReader.prototype.read = function(length) {
|
|
if (length > this._queueSize) return null;
|
|
if (length === 0) return Buffer$9.alloc(0);
|
|
|
|
this._queueSize -= length;
|
|
|
|
var queue = this._queue,
|
|
remain = length,
|
|
first = queue[0],
|
|
buffers, buffer;
|
|
|
|
if (first.length >= length) {
|
|
if (first.length === length) {
|
|
return queue.shift();
|
|
} else {
|
|
buffer = first.slice(0, length);
|
|
queue[0] = first.slice(length);
|
|
return buffer;
|
|
}
|
|
}
|
|
|
|
for (var i = 0, n = queue.length; i < n; i++) {
|
|
if (remain < queue[i].length) break;
|
|
remain -= queue[i].length;
|
|
}
|
|
buffers = queue.splice(0, i);
|
|
|
|
if (remain > 0 && queue.length > 0) {
|
|
buffers.push(queue[0].slice(0, remain));
|
|
queue[0] = queue[0].slice(remain);
|
|
}
|
|
return Buffer$9.concat(buffers, length);
|
|
};
|
|
|
|
StreamReader.prototype.eachByte = function(callback, context) {
|
|
var buffer, n, index;
|
|
|
|
while (this._queue.length > 0) {
|
|
buffer = this._queue[0];
|
|
n = buffer.length;
|
|
|
|
while (this._offset < n) {
|
|
index = this._offset;
|
|
this._offset += 1;
|
|
callback.call(context, buffer[index]);
|
|
}
|
|
this._offset = 0;
|
|
this._queue.shift();
|
|
}
|
|
};
|
|
|
|
var stream_reader = StreamReader;
|
|
|
|
var Buffer$8 = safeBuffer.exports.Buffer,
|
|
Emitter = require$$1__default["default"].EventEmitter,
|
|
util$b = require$$2__default["default"],
|
|
streams = streams$1,
|
|
Headers$2 = headers,
|
|
Reader = stream_reader;
|
|
|
|
var Base$7 = function(request, url, options) {
|
|
Emitter.call(this);
|
|
Base$7.validateOptions(options || {}, ['maxLength', 'masking', 'requireMasking', 'protocols']);
|
|
|
|
this._request = request;
|
|
this._reader = new Reader();
|
|
this._options = options || {};
|
|
this._maxLength = this._options.maxLength || this.MAX_LENGTH;
|
|
this._headers = new Headers$2();
|
|
this.__queue = [];
|
|
this.readyState = 0;
|
|
this.url = url;
|
|
|
|
this.io = new streams.IO(this);
|
|
this.messages = new streams.Messages(this);
|
|
this._bindEventListeners();
|
|
};
|
|
util$b.inherits(Base$7, Emitter);
|
|
|
|
Base$7.isWebSocket = function(request) {
|
|
var connection = request.headers.connection || '',
|
|
upgrade = request.headers.upgrade || '';
|
|
|
|
return request.method === 'GET' &&
|
|
connection.toLowerCase().split(/ *, */).indexOf('upgrade') >= 0 &&
|
|
upgrade.toLowerCase() === 'websocket';
|
|
};
|
|
|
|
Base$7.validateOptions = function(options, validKeys) {
|
|
for (var key in options) {
|
|
if (validKeys.indexOf(key) < 0)
|
|
throw new Error('Unrecognized option: ' + key);
|
|
}
|
|
};
|
|
|
|
var instance$b = {
|
|
// This is 64MB, small enough for an average VPS to handle without
|
|
// crashing from process out of memory
|
|
MAX_LENGTH: 0x3ffffff,
|
|
|
|
STATES: ['connecting', 'open', 'closing', 'closed'],
|
|
|
|
_bindEventListeners: function() {
|
|
var self = this;
|
|
|
|
// Protocol errors are informational and do not have to be handled
|
|
this.messages.on('error', function() {});
|
|
|
|
this.on('message', function(event) {
|
|
var messages = self.messages;
|
|
if (messages.readable) messages.emit('data', event.data);
|
|
});
|
|
|
|
this.on('error', function(error) {
|
|
var messages = self.messages;
|
|
if (messages.readable) messages.emit('error', error);
|
|
});
|
|
|
|
this.on('close', function() {
|
|
var messages = self.messages;
|
|
if (!messages.readable) return;
|
|
messages.readable = messages.writable = false;
|
|
messages.emit('end');
|
|
});
|
|
},
|
|
|
|
getState: function() {
|
|
return this.STATES[this.readyState] || null;
|
|
},
|
|
|
|
addExtension: function(extension) {
|
|
return false;
|
|
},
|
|
|
|
setHeader: function(name, value) {
|
|
if (this.readyState > 0) return false;
|
|
this._headers.set(name, value);
|
|
return true;
|
|
},
|
|
|
|
start: function() {
|
|
if (this.readyState !== 0) return false;
|
|
|
|
if (!Base$7.isWebSocket(this._request))
|
|
return this._failHandshake(new Error('Not a WebSocket request'));
|
|
|
|
var response;
|
|
|
|
try {
|
|
response = this._handshakeResponse();
|
|
} catch (error) {
|
|
return this._failHandshake(error);
|
|
}
|
|
|
|
this._write(response);
|
|
if (this._stage !== -1) this._open();
|
|
return true;
|
|
},
|
|
|
|
_failHandshake: function(error) {
|
|
var headers = new Headers$2();
|
|
headers.set('Content-Type', 'text/plain');
|
|
headers.set('Content-Length', Buffer$8.byteLength(error.message, 'utf8'));
|
|
|
|
headers = ['HTTP/1.1 400 Bad Request', headers.toString(), error.message];
|
|
this._write(Buffer$8.from(headers.join('\r\n'), 'utf8'));
|
|
this._fail('protocol_error', error.message);
|
|
|
|
return false;
|
|
},
|
|
|
|
text: function(message) {
|
|
return this.frame(message);
|
|
},
|
|
|
|
binary: function(message) {
|
|
return false;
|
|
},
|
|
|
|
ping: function() {
|
|
return false;
|
|
},
|
|
|
|
pong: function() {
|
|
return false;
|
|
},
|
|
|
|
close: function(reason, code) {
|
|
if (this.readyState !== 1) return false;
|
|
this.readyState = 3;
|
|
this.emit('close', new Base$7.CloseEvent(null, null));
|
|
return true;
|
|
},
|
|
|
|
_open: function() {
|
|
this.readyState = 1;
|
|
this.__queue.forEach(function(args) { this.frame.apply(this, args); }, this);
|
|
this.__queue = [];
|
|
this.emit('open', new Base$7.OpenEvent());
|
|
},
|
|
|
|
_queue: function(message) {
|
|
this.__queue.push(message);
|
|
return true;
|
|
},
|
|
|
|
_write: function(chunk) {
|
|
var io = this.io;
|
|
if (io.readable) io.emit('data', chunk);
|
|
},
|
|
|
|
_fail: function(type, message) {
|
|
this.readyState = 2;
|
|
this.emit('error', new Error(message));
|
|
this.close();
|
|
}
|
|
};
|
|
|
|
for (var key$b in instance$b)
|
|
Base$7.prototype[key$b] = instance$b[key$b];
|
|
|
|
|
|
Base$7.ConnectEvent = function() {};
|
|
|
|
Base$7.OpenEvent = function() {};
|
|
|
|
Base$7.CloseEvent = function(code, reason) {
|
|
this.code = code;
|
|
this.reason = reason;
|
|
};
|
|
|
|
Base$7.MessageEvent = function(data) {
|
|
this.data = data;
|
|
};
|
|
|
|
Base$7.PingEvent = function(data) {
|
|
this.data = data;
|
|
};
|
|
|
|
Base$7.PongEvent = function(data) {
|
|
this.data = data;
|
|
};
|
|
|
|
var base = Base$7;
|
|
|
|
var httpParser = {};
|
|
|
|
/*jshint node:true */
|
|
var assert = require$$0__default$2["default"];
|
|
|
|
httpParser.HTTPParser = HTTPParser;
|
|
function HTTPParser(type) {
|
|
assert.ok(type === HTTPParser.REQUEST || type === HTTPParser.RESPONSE || type === undefined);
|
|
if (type === undefined) ; else {
|
|
this.initialize(type);
|
|
}
|
|
}
|
|
HTTPParser.prototype.initialize = function (type, async_resource) {
|
|
assert.ok(type === HTTPParser.REQUEST || type === HTTPParser.RESPONSE);
|
|
this.type = type;
|
|
this.state = type + '_LINE';
|
|
this.info = {
|
|
headers: [],
|
|
upgrade: false
|
|
};
|
|
this.trailers = [];
|
|
this.line = '';
|
|
this.isChunked = false;
|
|
this.connection = '';
|
|
this.headerSize = 0; // for preventing too big headers
|
|
this.body_bytes = null;
|
|
this.isUserCall = false;
|
|
this.hadError = false;
|
|
};
|
|
|
|
HTTPParser.encoding = 'ascii';
|
|
HTTPParser.maxHeaderSize = 80 * 1024; // maxHeaderSize (in bytes) is configurable, but 80kb by default;
|
|
HTTPParser.REQUEST = 'REQUEST';
|
|
HTTPParser.RESPONSE = 'RESPONSE';
|
|
|
|
// Note: *not* starting with kOnHeaders=0 line the Node parser, because any
|
|
// newly added constants (kOnTimeout in Node v12.19.0) will overwrite 0!
|
|
var kOnHeaders = HTTPParser.kOnHeaders = 1;
|
|
var kOnHeadersComplete = HTTPParser.kOnHeadersComplete = 2;
|
|
var kOnBody = HTTPParser.kOnBody = 3;
|
|
var kOnMessageComplete = HTTPParser.kOnMessageComplete = 4;
|
|
|
|
// Some handler stubs, needed for compatibility
|
|
HTTPParser.prototype[kOnHeaders] =
|
|
HTTPParser.prototype[kOnHeadersComplete] =
|
|
HTTPParser.prototype[kOnBody] =
|
|
HTTPParser.prototype[kOnMessageComplete] = function () {};
|
|
|
|
var compatMode0_12 = true;
|
|
Object.defineProperty(HTTPParser, 'kOnExecute', {
|
|
get: function () {
|
|
// hack for backward compatibility
|
|
compatMode0_12 = false;
|
|
return 99;
|
|
}
|
|
});
|
|
|
|
var methods = httpParser.methods = HTTPParser.methods = [
|
|
'DELETE',
|
|
'GET',
|
|
'HEAD',
|
|
'POST',
|
|
'PUT',
|
|
'CONNECT',
|
|
'OPTIONS',
|
|
'TRACE',
|
|
'COPY',
|
|
'LOCK',
|
|
'MKCOL',
|
|
'MOVE',
|
|
'PROPFIND',
|
|
'PROPPATCH',
|
|
'SEARCH',
|
|
'UNLOCK',
|
|
'BIND',
|
|
'REBIND',
|
|
'UNBIND',
|
|
'ACL',
|
|
'REPORT',
|
|
'MKACTIVITY',
|
|
'CHECKOUT',
|
|
'MERGE',
|
|
'M-SEARCH',
|
|
'NOTIFY',
|
|
'SUBSCRIBE',
|
|
'UNSUBSCRIBE',
|
|
'PATCH',
|
|
'PURGE',
|
|
'MKCALENDAR',
|
|
'LINK',
|
|
'UNLINK'
|
|
];
|
|
var method_connect = methods.indexOf('CONNECT');
|
|
HTTPParser.prototype.reinitialize = HTTPParser;
|
|
HTTPParser.prototype.close =
|
|
HTTPParser.prototype.pause =
|
|
HTTPParser.prototype.resume =
|
|
HTTPParser.prototype.free = function () {};
|
|
HTTPParser.prototype._compatMode0_11 = false;
|
|
HTTPParser.prototype.getAsyncId = function() { return 0; };
|
|
|
|
var headerState = {
|
|
REQUEST_LINE: true,
|
|
RESPONSE_LINE: true,
|
|
HEADER: true
|
|
};
|
|
HTTPParser.prototype.execute = function (chunk, start, length) {
|
|
if (!(this instanceof HTTPParser)) {
|
|
throw new TypeError('not a HTTPParser');
|
|
}
|
|
|
|
// backward compat to node < 0.11.4
|
|
// Note: the start and length params were removed in newer version
|
|
start = start || 0;
|
|
length = typeof length === 'number' ? length : chunk.length;
|
|
|
|
this.chunk = chunk;
|
|
this.offset = start;
|
|
var end = this.end = start + length;
|
|
try {
|
|
while (this.offset < end) {
|
|
if (this[this.state]()) {
|
|
break;
|
|
}
|
|
}
|
|
} catch (err) {
|
|
if (this.isUserCall) {
|
|
throw err;
|
|
}
|
|
this.hadError = true;
|
|
return err;
|
|
}
|
|
this.chunk = null;
|
|
length = this.offset - start;
|
|
if (headerState[this.state]) {
|
|
this.headerSize += length;
|
|
if (this.headerSize > HTTPParser.maxHeaderSize) {
|
|
return new Error('max header size exceeded');
|
|
}
|
|
}
|
|
return length;
|
|
};
|
|
|
|
var stateFinishAllowed = {
|
|
REQUEST_LINE: true,
|
|
RESPONSE_LINE: true,
|
|
BODY_RAW: true
|
|
};
|
|
HTTPParser.prototype.finish = function () {
|
|
if (this.hadError) {
|
|
return;
|
|
}
|
|
if (!stateFinishAllowed[this.state]) {
|
|
return new Error('invalid state for EOF');
|
|
}
|
|
if (this.state === 'BODY_RAW') {
|
|
this.userCall()(this[kOnMessageComplete]());
|
|
}
|
|
};
|
|
|
|
// These three methods are used for an internal speed optimization, and it also
|
|
// works if theses are noops. Basically consume() asks us to read the bytes
|
|
// ourselves, but if we don't do it we get them through execute().
|
|
HTTPParser.prototype.consume =
|
|
HTTPParser.prototype.unconsume =
|
|
HTTPParser.prototype.getCurrentBuffer = function () {};
|
|
|
|
//For correct error handling - see HTTPParser#execute
|
|
//Usage: this.userCall()(userFunction('arg'));
|
|
HTTPParser.prototype.userCall = function () {
|
|
this.isUserCall = true;
|
|
var self = this;
|
|
return function (ret) {
|
|
self.isUserCall = false;
|
|
return ret;
|
|
};
|
|
};
|
|
|
|
HTTPParser.prototype.nextRequest = function () {
|
|
this.userCall()(this[kOnMessageComplete]());
|
|
this.reinitialize(this.type);
|
|
};
|
|
|
|
HTTPParser.prototype.consumeLine = function () {
|
|
var end = this.end,
|
|
chunk = this.chunk;
|
|
for (var i = this.offset; i < end; i++) {
|
|
if (chunk[i] === 0x0a) { // \n
|
|
var line = this.line + chunk.toString(HTTPParser.encoding, this.offset, i);
|
|
if (line.charAt(line.length - 1) === '\r') {
|
|
line = line.substr(0, line.length - 1);
|
|
}
|
|
this.line = '';
|
|
this.offset = i + 1;
|
|
return line;
|
|
}
|
|
}
|
|
//line split over multiple chunks
|
|
this.line += chunk.toString(HTTPParser.encoding, this.offset, this.end);
|
|
this.offset = this.end;
|
|
};
|
|
|
|
var headerExp = /^([^: \t]+):[ \t]*((?:.*[^ \t])|)/;
|
|
var headerContinueExp = /^[ \t]+(.*[^ \t])/;
|
|
HTTPParser.prototype.parseHeader = function (line, headers) {
|
|
if (line.indexOf('\r') !== -1) {
|
|
throw parseErrorCode('HPE_LF_EXPECTED');
|
|
}
|
|
|
|
var match = headerExp.exec(line);
|
|
var k = match && match[1];
|
|
if (k) { // skip empty string (malformed header)
|
|
headers.push(k);
|
|
headers.push(match[2]);
|
|
} else {
|
|
var matchContinue = headerContinueExp.exec(line);
|
|
if (matchContinue && headers.length) {
|
|
if (headers[headers.length - 1]) {
|
|
headers[headers.length - 1] += ' ';
|
|
}
|
|
headers[headers.length - 1] += matchContinue[1];
|
|
}
|
|
}
|
|
};
|
|
|
|
var requestExp = /^([A-Z-]+) ([^ ]+) HTTP\/(\d)\.(\d)$/;
|
|
HTTPParser.prototype.REQUEST_LINE = function () {
|
|
var line = this.consumeLine();
|
|
if (!line) {
|
|
return;
|
|
}
|
|
var match = requestExp.exec(line);
|
|
if (match === null) {
|
|
throw parseErrorCode('HPE_INVALID_CONSTANT');
|
|
}
|
|
this.info.method = this._compatMode0_11 ? match[1] : methods.indexOf(match[1]);
|
|
if (this.info.method === -1) {
|
|
throw new Error('invalid request method');
|
|
}
|
|
this.info.url = match[2];
|
|
this.info.versionMajor = +match[3];
|
|
this.info.versionMinor = +match[4];
|
|
this.body_bytes = 0;
|
|
this.state = 'HEADER';
|
|
};
|
|
|
|
var responseExp = /^HTTP\/(\d)\.(\d) (\d{3}) ?(.*)$/;
|
|
HTTPParser.prototype.RESPONSE_LINE = function () {
|
|
var line = this.consumeLine();
|
|
if (!line) {
|
|
return;
|
|
}
|
|
var match = responseExp.exec(line);
|
|
if (match === null) {
|
|
throw parseErrorCode('HPE_INVALID_CONSTANT');
|
|
}
|
|
this.info.versionMajor = +match[1];
|
|
this.info.versionMinor = +match[2];
|
|
var statusCode = this.info.statusCode = +match[3];
|
|
this.info.statusMessage = match[4];
|
|
// Implied zero length.
|
|
if ((statusCode / 100 | 0) === 1 || statusCode === 204 || statusCode === 304) {
|
|
this.body_bytes = 0;
|
|
}
|
|
this.state = 'HEADER';
|
|
};
|
|
|
|
HTTPParser.prototype.shouldKeepAlive = function () {
|
|
if (this.info.versionMajor > 0 && this.info.versionMinor > 0) {
|
|
if (this.connection.indexOf('close') !== -1) {
|
|
return false;
|
|
}
|
|
} else if (this.connection.indexOf('keep-alive') === -1) {
|
|
return false;
|
|
}
|
|
if (this.body_bytes !== null || this.isChunked) { // || skipBody
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
HTTPParser.prototype.HEADER = function () {
|
|
var line = this.consumeLine();
|
|
if (line === undefined) {
|
|
return;
|
|
}
|
|
var info = this.info;
|
|
if (line) {
|
|
this.parseHeader(line, info.headers);
|
|
} else {
|
|
var headers = info.headers;
|
|
var hasContentLength = false;
|
|
var currentContentLengthValue;
|
|
var hasUpgradeHeader = false;
|
|
for (var i = 0; i < headers.length; i += 2) {
|
|
switch (headers[i].toLowerCase()) {
|
|
case 'transfer-encoding':
|
|
this.isChunked = headers[i + 1].toLowerCase() === 'chunked';
|
|
break;
|
|
case 'content-length':
|
|
currentContentLengthValue = +headers[i + 1];
|
|
if (hasContentLength) {
|
|
// Fix duplicate Content-Length header with same values.
|
|
// Throw error only if values are different.
|
|
// Known issues:
|
|
// https://github.com/request/request/issues/2091#issuecomment-328715113
|
|
// https://github.com/nodejs/node/issues/6517#issuecomment-216263771
|
|
if (currentContentLengthValue !== this.body_bytes) {
|
|
throw parseErrorCode('HPE_UNEXPECTED_CONTENT_LENGTH');
|
|
}
|
|
} else {
|
|
hasContentLength = true;
|
|
this.body_bytes = currentContentLengthValue;
|
|
}
|
|
break;
|
|
case 'connection':
|
|
this.connection += headers[i + 1].toLowerCase();
|
|
break;
|
|
case 'upgrade':
|
|
hasUpgradeHeader = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if both isChunked and hasContentLength, isChunked wins
|
|
// This is required so the body is parsed using the chunked method, and matches
|
|
// Chrome's behavior. We could, maybe, ignore them both (would get chunked
|
|
// encoding into the body), and/or disable shouldKeepAlive to be more
|
|
// resilient.
|
|
if (this.isChunked && hasContentLength) {
|
|
hasContentLength = false;
|
|
this.body_bytes = null;
|
|
}
|
|
|
|
// Logic from https://github.com/nodejs/http-parser/blob/921d5585515a153fa00e411cf144280c59b41f90/http_parser.c#L1727-L1737
|
|
// "For responses, "Upgrade: foo" and "Connection: upgrade" are
|
|
// mandatory only when it is a 101 Switching Protocols response,
|
|
// otherwise it is purely informational, to announce support.
|
|
if (hasUpgradeHeader && this.connection.indexOf('upgrade') != -1) {
|
|
info.upgrade = this.type === HTTPParser.REQUEST || info.statusCode === 101;
|
|
} else {
|
|
info.upgrade = info.method === method_connect;
|
|
}
|
|
|
|
if (this.isChunked && info.upgrade) {
|
|
this.isChunked = false;
|
|
}
|
|
|
|
info.shouldKeepAlive = this.shouldKeepAlive();
|
|
//problem which also exists in original node: we should know skipBody before calling onHeadersComplete
|
|
var skipBody;
|
|
if (compatMode0_12) {
|
|
skipBody = this.userCall()(this[kOnHeadersComplete](info));
|
|
} else {
|
|
skipBody = this.userCall()(this[kOnHeadersComplete](info.versionMajor,
|
|
info.versionMinor, info.headers, info.method, info.url, info.statusCode,
|
|
info.statusMessage, info.upgrade, info.shouldKeepAlive));
|
|
}
|
|
if (skipBody === 2) {
|
|
this.nextRequest();
|
|
return true;
|
|
} else if (this.isChunked && !skipBody) {
|
|
this.state = 'BODY_CHUNKHEAD';
|
|
} else if (skipBody || this.body_bytes === 0) {
|
|
this.nextRequest();
|
|
// For older versions of node (v6.x and older?), that return skipBody=1 or skipBody=true,
|
|
// need this "return true;" if it's an upgrade request.
|
|
return info.upgrade;
|
|
} else if (this.body_bytes === null) {
|
|
this.state = 'BODY_RAW';
|
|
} else {
|
|
this.state = 'BODY_SIZED';
|
|
}
|
|
}
|
|
};
|
|
|
|
HTTPParser.prototype.BODY_CHUNKHEAD = function () {
|
|
var line = this.consumeLine();
|
|
if (line === undefined) {
|
|
return;
|
|
}
|
|
this.body_bytes = parseInt(line, 16);
|
|
if (!this.body_bytes) {
|
|
this.state = 'BODY_CHUNKTRAILERS';
|
|
} else {
|
|
this.state = 'BODY_CHUNK';
|
|
}
|
|
};
|
|
|
|
HTTPParser.prototype.BODY_CHUNK = function () {
|
|
var length = Math.min(this.end - this.offset, this.body_bytes);
|
|
this.userCall()(this[kOnBody](this.chunk, this.offset, length));
|
|
this.offset += length;
|
|
this.body_bytes -= length;
|
|
if (!this.body_bytes) {
|
|
this.state = 'BODY_CHUNKEMPTYLINE';
|
|
}
|
|
};
|
|
|
|
HTTPParser.prototype.BODY_CHUNKEMPTYLINE = function () {
|
|
var line = this.consumeLine();
|
|
if (line === undefined) {
|
|
return;
|
|
}
|
|
assert.equal(line, '');
|
|
this.state = 'BODY_CHUNKHEAD';
|
|
};
|
|
|
|
HTTPParser.prototype.BODY_CHUNKTRAILERS = function () {
|
|
var line = this.consumeLine();
|
|
if (line === undefined) {
|
|
return;
|
|
}
|
|
if (line) {
|
|
this.parseHeader(line, this.trailers);
|
|
} else {
|
|
if (this.trailers.length) {
|
|
this.userCall()(this[kOnHeaders](this.trailers, ''));
|
|
}
|
|
this.nextRequest();
|
|
}
|
|
};
|
|
|
|
HTTPParser.prototype.BODY_RAW = function () {
|
|
var length = this.end - this.offset;
|
|
this.userCall()(this[kOnBody](this.chunk, this.offset, length));
|
|
this.offset = this.end;
|
|
};
|
|
|
|
HTTPParser.prototype.BODY_SIZED = function () {
|
|
var length = Math.min(this.end - this.offset, this.body_bytes);
|
|
this.userCall()(this[kOnBody](this.chunk, this.offset, length));
|
|
this.offset += length;
|
|
this.body_bytes -= length;
|
|
if (!this.body_bytes) {
|
|
this.nextRequest();
|
|
}
|
|
};
|
|
|
|
// backward compat to node < 0.11.6
|
|
['Headers', 'HeadersComplete', 'Body', 'MessageComplete'].forEach(function (name) {
|
|
var k = HTTPParser['kOn' + name];
|
|
Object.defineProperty(HTTPParser.prototype, 'on' + name, {
|
|
get: function () {
|
|
return this[k];
|
|
},
|
|
set: function (to) {
|
|
// hack for backward compatibility
|
|
this._compatMode0_11 = true;
|
|
method_connect = 'CONNECT';
|
|
return (this[k] = to);
|
|
}
|
|
});
|
|
});
|
|
|
|
function parseErrorCode(code) {
|
|
var err = new Error('Parse Error');
|
|
err.code = code;
|
|
return err;
|
|
}
|
|
|
|
var NodeHTTPParser = httpParser.HTTPParser,
|
|
Buffer$7 = safeBuffer.exports.Buffer;
|
|
|
|
var TYPES = {
|
|
request: NodeHTTPParser.REQUEST || 'request',
|
|
response: NodeHTTPParser.RESPONSE || 'response'
|
|
};
|
|
|
|
var HttpParser$3 = function(type) {
|
|
this._type = type;
|
|
this._parser = new NodeHTTPParser(TYPES[type]);
|
|
this._complete = false;
|
|
this.headers = {};
|
|
|
|
var current = null,
|
|
self = this;
|
|
|
|
this._parser.onHeaderField = function(b, start, length) {
|
|
current = b.toString('utf8', start, start + length).toLowerCase();
|
|
};
|
|
|
|
this._parser.onHeaderValue = function(b, start, length) {
|
|
var value = b.toString('utf8', start, start + length);
|
|
|
|
if (self.headers.hasOwnProperty(current))
|
|
self.headers[current] += ', ' + value;
|
|
else
|
|
self.headers[current] = value;
|
|
};
|
|
|
|
this._parser.onHeadersComplete = this._parser[NodeHTTPParser.kOnHeadersComplete] =
|
|
function(majorVersion, minorVersion, headers, method, pathname, statusCode) {
|
|
var info = arguments[0];
|
|
|
|
if (typeof info === 'object') {
|
|
method = info.method;
|
|
pathname = info.url;
|
|
statusCode = info.statusCode;
|
|
headers = info.headers;
|
|
}
|
|
|
|
self.method = (typeof method === 'number') ? HttpParser$3.METHODS[method] : method;
|
|
self.statusCode = statusCode;
|
|
self.url = pathname;
|
|
|
|
if (!headers) return;
|
|
|
|
for (var i = 0, n = headers.length, key, value; i < n; i += 2) {
|
|
key = headers[i].toLowerCase();
|
|
value = headers[i+1];
|
|
if (self.headers.hasOwnProperty(key))
|
|
self.headers[key] += ', ' + value;
|
|
else
|
|
self.headers[key] = value;
|
|
}
|
|
|
|
self._complete = true;
|
|
};
|
|
};
|
|
|
|
HttpParser$3.METHODS = {
|
|
0: 'DELETE',
|
|
1: 'GET',
|
|
2: 'HEAD',
|
|
3: 'POST',
|
|
4: 'PUT',
|
|
5: 'CONNECT',
|
|
6: 'OPTIONS',
|
|
7: 'TRACE',
|
|
8: 'COPY',
|
|
9: 'LOCK',
|
|
10: 'MKCOL',
|
|
11: 'MOVE',
|
|
12: 'PROPFIND',
|
|
13: 'PROPPATCH',
|
|
14: 'SEARCH',
|
|
15: 'UNLOCK',
|
|
16: 'BIND',
|
|
17: 'REBIND',
|
|
18: 'UNBIND',
|
|
19: 'ACL',
|
|
20: 'REPORT',
|
|
21: 'MKACTIVITY',
|
|
22: 'CHECKOUT',
|
|
23: 'MERGE',
|
|
24: 'M-SEARCH',
|
|
25: 'NOTIFY',
|
|
26: 'SUBSCRIBE',
|
|
27: 'UNSUBSCRIBE',
|
|
28: 'PATCH',
|
|
29: 'PURGE',
|
|
30: 'MKCALENDAR',
|
|
31: 'LINK',
|
|
32: 'UNLINK'
|
|
};
|
|
|
|
var VERSION = process.version
|
|
? process.version.match(/[0-9]+/g).map(function(n) { return parseInt(n, 10) })
|
|
: [];
|
|
|
|
if (VERSION[0] === 0 && VERSION[1] === 12) {
|
|
HttpParser$3.METHODS[16] = 'REPORT';
|
|
HttpParser$3.METHODS[17] = 'MKACTIVITY';
|
|
HttpParser$3.METHODS[18] = 'CHECKOUT';
|
|
HttpParser$3.METHODS[19] = 'MERGE';
|
|
HttpParser$3.METHODS[20] = 'M-SEARCH';
|
|
HttpParser$3.METHODS[21] = 'NOTIFY';
|
|
HttpParser$3.METHODS[22] = 'SUBSCRIBE';
|
|
HttpParser$3.METHODS[23] = 'UNSUBSCRIBE';
|
|
HttpParser$3.METHODS[24] = 'PATCH';
|
|
HttpParser$3.METHODS[25] = 'PURGE';
|
|
}
|
|
|
|
HttpParser$3.prototype.isComplete = function() {
|
|
return this._complete;
|
|
};
|
|
|
|
HttpParser$3.prototype.parse = function(chunk) {
|
|
var consumed = this._parser.execute(chunk, 0, chunk.length);
|
|
|
|
if (typeof consumed !== 'number') {
|
|
this.error = consumed;
|
|
this._complete = true;
|
|
return;
|
|
}
|
|
|
|
if (this._complete)
|
|
this.body = (consumed < chunk.length)
|
|
? chunk.slice(consumed)
|
|
: Buffer$7.alloc(0);
|
|
};
|
|
|
|
var http_parser = HttpParser$3;
|
|
|
|
var TOKEN = /([!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z]+)/,
|
|
NOTOKEN = /([^!#\$%&'\*\+\-\.\^_`\|~0-9A-Za-z])/g,
|
|
QUOTED = /"((?:\\[\x00-\x7f]|[^\x00-\x08\x0a-\x1f\x7f"\\])*)"/,
|
|
PARAM = new RegExp(TOKEN.source + '(?:=(?:' + TOKEN.source + '|' + QUOTED.source + '))?'),
|
|
EXT = new RegExp(TOKEN.source + '(?: *; *' + PARAM.source + ')*', 'g'),
|
|
EXT_LIST = new RegExp('^' + EXT.source + '(?: *, *' + EXT.source + ')*$'),
|
|
NUMBER = /^-?(0|[1-9][0-9]*)(\.[0-9]+)?$/;
|
|
|
|
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
|
|
var Parser$1 = {
|
|
parseHeader: function(header) {
|
|
var offers = new Offers();
|
|
if (header === '' || header === undefined) return offers;
|
|
|
|
if (!EXT_LIST.test(header))
|
|
throw new SyntaxError('Invalid Sec-WebSocket-Extensions header: ' + header);
|
|
|
|
var values = header.match(EXT);
|
|
|
|
values.forEach(function(value) {
|
|
var params = value.match(new RegExp(PARAM.source, 'g')),
|
|
name = params.shift(),
|
|
offer = {};
|
|
|
|
params.forEach(function(param) {
|
|
var args = param.match(PARAM), key = args[1], data;
|
|
|
|
if (args[2] !== undefined) {
|
|
data = args[2];
|
|
} else if (args[3] !== undefined) {
|
|
data = args[3].replace(/\\/g, '');
|
|
} else {
|
|
data = true;
|
|
}
|
|
if (NUMBER.test(data)) data = parseFloat(data);
|
|
|
|
if (hasOwnProperty.call(offer, key)) {
|
|
offer[key] = [].concat(offer[key]);
|
|
offer[key].push(data);
|
|
} else {
|
|
offer[key] = data;
|
|
}
|
|
}, this);
|
|
offers.push(name, offer);
|
|
}, this);
|
|
|
|
return offers;
|
|
},
|
|
|
|
serializeParams: function(name, params) {
|
|
var values = [];
|
|
|
|
var print = function(key, value) {
|
|
if (value instanceof Array) {
|
|
value.forEach(function(v) { print(key, v); });
|
|
} else if (value === true) {
|
|
values.push(key);
|
|
} else if (typeof value === 'number') {
|
|
values.push(key + '=' + value);
|
|
} else if (NOTOKEN.test(value)) {
|
|
values.push(key + '="' + value.replace(/"/g, '\\"') + '"');
|
|
} else {
|
|
values.push(key + '=' + value);
|
|
}
|
|
};
|
|
|
|
for (var key in params) print(key, params[key]);
|
|
|
|
return [name].concat(values).join('; ');
|
|
}
|
|
};
|
|
|
|
var Offers = function() {
|
|
this._byName = {};
|
|
this._inOrder = [];
|
|
};
|
|
|
|
Offers.prototype.push = function(name, params) {
|
|
if (!hasOwnProperty.call(this._byName, name))
|
|
this._byName[name] = [];
|
|
|
|
this._byName[name].push(params);
|
|
this._inOrder.push({ name: name, params: params });
|
|
};
|
|
|
|
Offers.prototype.eachOffer = function(callback, context) {
|
|
var list = this._inOrder;
|
|
for (var i = 0, n = list.length; i < n; i++)
|
|
callback.call(context, list[i].name, list[i].params);
|
|
};
|
|
|
|
Offers.prototype.byName = function(name) {
|
|
return this._byName[name] || [];
|
|
};
|
|
|
|
Offers.prototype.toArray = function() {
|
|
return this._inOrder.slice();
|
|
};
|
|
|
|
var parser = Parser$1;
|
|
|
|
var RingBuffer$2 = function(bufferSize) {
|
|
this._bufferSize = bufferSize;
|
|
this.clear();
|
|
};
|
|
|
|
RingBuffer$2.prototype.clear = function() {
|
|
this._buffer = new Array(this._bufferSize);
|
|
this._ringOffset = 0;
|
|
this._ringSize = this._bufferSize;
|
|
this._head = 0;
|
|
this._tail = 0;
|
|
this.length = 0;
|
|
};
|
|
|
|
RingBuffer$2.prototype.push = function(value) {
|
|
var expandBuffer = false,
|
|
expandRing = false;
|
|
|
|
if (this._ringSize < this._bufferSize) {
|
|
expandBuffer = (this._tail === 0);
|
|
} else if (this._ringOffset === this._ringSize) {
|
|
expandBuffer = true;
|
|
expandRing = (this._tail === 0);
|
|
}
|
|
|
|
if (expandBuffer) {
|
|
this._tail = this._bufferSize;
|
|
this._buffer = this._buffer.concat(new Array(this._bufferSize));
|
|
this._bufferSize = this._buffer.length;
|
|
|
|
if (expandRing)
|
|
this._ringSize = this._bufferSize;
|
|
}
|
|
|
|
this._buffer[this._tail] = value;
|
|
this.length += 1;
|
|
if (this._tail < this._ringSize) this._ringOffset += 1;
|
|
this._tail = (this._tail + 1) % this._bufferSize;
|
|
};
|
|
|
|
RingBuffer$2.prototype.peek = function() {
|
|
if (this.length === 0) return void 0;
|
|
return this._buffer[this._head];
|
|
};
|
|
|
|
RingBuffer$2.prototype.shift = function() {
|
|
if (this.length === 0) return void 0;
|
|
|
|
var value = this._buffer[this._head];
|
|
this._buffer[this._head] = void 0;
|
|
this.length -= 1;
|
|
this._ringOffset -= 1;
|
|
|
|
if (this._ringOffset === 0 && this.length > 0) {
|
|
this._head = this._ringSize;
|
|
this._ringOffset = this.length;
|
|
this._ringSize = this._bufferSize;
|
|
} else {
|
|
this._head = (this._head + 1) % this._ringSize;
|
|
}
|
|
return value;
|
|
};
|
|
|
|
var ring_buffer = RingBuffer$2;
|
|
|
|
var RingBuffer$1 = ring_buffer;
|
|
|
|
var Functor$1 = function(session, method) {
|
|
this._session = session;
|
|
this._method = method;
|
|
this._queue = new RingBuffer$1(Functor$1.QUEUE_SIZE);
|
|
this._stopped = false;
|
|
this.pending = 0;
|
|
};
|
|
|
|
Functor$1.QUEUE_SIZE = 8;
|
|
|
|
Functor$1.prototype.call = function(error, message, callback, context) {
|
|
if (this._stopped) return;
|
|
|
|
var record = { error: error, message: message, callback: callback, context: context, done: false },
|
|
called = false,
|
|
self = this;
|
|
|
|
this._queue.push(record);
|
|
|
|
if (record.error) {
|
|
record.done = true;
|
|
this._stop();
|
|
return this._flushQueue();
|
|
}
|
|
|
|
var handler = function(err, msg) {
|
|
if (!(called ^ (called = true))) return;
|
|
|
|
if (err) {
|
|
self._stop();
|
|
record.error = err;
|
|
record.message = null;
|
|
} else {
|
|
record.message = msg;
|
|
}
|
|
|
|
record.done = true;
|
|
self._flushQueue();
|
|
};
|
|
|
|
try {
|
|
this._session[this._method](message, handler);
|
|
} catch (err) {
|
|
handler(err);
|
|
}
|
|
};
|
|
|
|
Functor$1.prototype._stop = function() {
|
|
this.pending = this._queue.length;
|
|
this._stopped = true;
|
|
};
|
|
|
|
Functor$1.prototype._flushQueue = function() {
|
|
var queue = this._queue, record;
|
|
|
|
while (queue.length > 0 && queue.peek().done) {
|
|
record = queue.shift();
|
|
if (record.error) {
|
|
this.pending = 0;
|
|
queue.clear();
|
|
} else {
|
|
this.pending -= 1;
|
|
}
|
|
record.callback.call(record.context, record.error, record.message);
|
|
}
|
|
};
|
|
|
|
var functor = Functor$1;
|
|
|
|
var RingBuffer = ring_buffer;
|
|
|
|
var Pledge$2 = function() {
|
|
this._complete = false;
|
|
this._callbacks = new RingBuffer(Pledge$2.QUEUE_SIZE);
|
|
};
|
|
|
|
Pledge$2.QUEUE_SIZE = 4;
|
|
|
|
Pledge$2.all = function(list) {
|
|
var pledge = new Pledge$2(),
|
|
pending = list.length,
|
|
n = pending;
|
|
|
|
if (pending === 0) pledge.done();
|
|
|
|
while (n--) list[n].then(function() {
|
|
pending -= 1;
|
|
if (pending === 0) pledge.done();
|
|
});
|
|
return pledge;
|
|
};
|
|
|
|
Pledge$2.prototype.then = function(callback) {
|
|
if (this._complete) callback();
|
|
else this._callbacks.push(callback);
|
|
};
|
|
|
|
Pledge$2.prototype.done = function() {
|
|
this._complete = true;
|
|
var callbacks = this._callbacks, callback;
|
|
while (callback = callbacks.shift()) callback();
|
|
};
|
|
|
|
var pledge = Pledge$2;
|
|
|
|
var Functor = functor,
|
|
Pledge$1 = pledge;
|
|
|
|
var Cell$1 = function(tuple) {
|
|
this._ext = tuple[0];
|
|
this._session = tuple[1];
|
|
|
|
this._functors = {
|
|
incoming: new Functor(this._session, 'processIncomingMessage'),
|
|
outgoing: new Functor(this._session, 'processOutgoingMessage')
|
|
};
|
|
};
|
|
|
|
Cell$1.prototype.pending = function(direction) {
|
|
var functor = this._functors[direction];
|
|
if (!functor._stopped) functor.pending += 1;
|
|
};
|
|
|
|
Cell$1.prototype.incoming = function(error, message, callback, context) {
|
|
this._exec('incoming', error, message, callback, context);
|
|
};
|
|
|
|
Cell$1.prototype.outgoing = function(error, message, callback, context) {
|
|
this._exec('outgoing', error, message, callback, context);
|
|
};
|
|
|
|
Cell$1.prototype.close = function() {
|
|
this._closed = this._closed || new Pledge$1();
|
|
this._doClose();
|
|
return this._closed;
|
|
};
|
|
|
|
Cell$1.prototype._exec = function(direction, error, message, callback, context) {
|
|
this._functors[direction].call(error, message, function(err, msg) {
|
|
if (err) err.message = this._ext.name + ': ' + err.message;
|
|
callback.call(context, err, msg);
|
|
this._doClose();
|
|
}, this);
|
|
};
|
|
|
|
Cell$1.prototype._doClose = function() {
|
|
var fin = this._functors.incoming,
|
|
fout = this._functors.outgoing;
|
|
|
|
if (!this._closed || fin.pending + fout.pending !== 0) return;
|
|
if (this._session) this._session.close();
|
|
this._session = null;
|
|
this._closed.done();
|
|
};
|
|
|
|
var cell = Cell$1;
|
|
|
|
var Cell = cell,
|
|
Pledge = pledge;
|
|
|
|
var Pipeline$1 = function(sessions) {
|
|
this._cells = sessions.map(function(session) { return new Cell(session) });
|
|
this._stopped = { incoming: false, outgoing: false };
|
|
};
|
|
|
|
Pipeline$1.prototype.processIncomingMessage = function(message, callback, context) {
|
|
if (this._stopped.incoming) return;
|
|
this._loop('incoming', this._cells.length - 1, -1, -1, message, callback, context);
|
|
};
|
|
|
|
Pipeline$1.prototype.processOutgoingMessage = function(message, callback, context) {
|
|
if (this._stopped.outgoing) return;
|
|
this._loop('outgoing', 0, this._cells.length, 1, message, callback, context);
|
|
};
|
|
|
|
Pipeline$1.prototype.close = function(callback, context) {
|
|
this._stopped = { incoming: true, outgoing: true };
|
|
|
|
var closed = this._cells.map(function(a) { return a.close() });
|
|
if (callback)
|
|
Pledge.all(closed).then(function() { callback.call(context); });
|
|
};
|
|
|
|
Pipeline$1.prototype._loop = function(direction, start, end, step, message, callback, context) {
|
|
var cells = this._cells,
|
|
n = cells.length,
|
|
self = this;
|
|
|
|
while (n--) cells[n].pending(direction);
|
|
|
|
var pipe = function(index, error, msg) {
|
|
if (index === end) return callback.call(context, error, msg);
|
|
|
|
cells[index][direction](error, msg, function(err, m) {
|
|
if (err) self._stopped[direction] = true;
|
|
pipe(index + step, err, m);
|
|
});
|
|
};
|
|
pipe(start, null, message);
|
|
};
|
|
|
|
var pipeline = Pipeline$1;
|
|
|
|
var Parser = parser,
|
|
Pipeline = pipeline;
|
|
|
|
var Extensions$1 = function() {
|
|
this._rsv1 = this._rsv2 = this._rsv3 = null;
|
|
|
|
this._byName = {};
|
|
this._inOrder = [];
|
|
this._sessions = [];
|
|
this._index = {};
|
|
};
|
|
|
|
Extensions$1.MESSAGE_OPCODES = [1, 2];
|
|
|
|
var instance$a = {
|
|
add: function(ext) {
|
|
if (typeof ext.name !== 'string') throw new TypeError('extension.name must be a string');
|
|
if (ext.type !== 'permessage') throw new TypeError('extension.type must be "permessage"');
|
|
|
|
if (typeof ext.rsv1 !== 'boolean') throw new TypeError('extension.rsv1 must be true or false');
|
|
if (typeof ext.rsv2 !== 'boolean') throw new TypeError('extension.rsv2 must be true or false');
|
|
if (typeof ext.rsv3 !== 'boolean') throw new TypeError('extension.rsv3 must be true or false');
|
|
|
|
if (this._byName.hasOwnProperty(ext.name))
|
|
throw new TypeError('An extension with name "' + ext.name + '" is already registered');
|
|
|
|
this._byName[ext.name] = ext;
|
|
this._inOrder.push(ext);
|
|
},
|
|
|
|
generateOffer: function() {
|
|
var sessions = [],
|
|
offer = [],
|
|
index = {};
|
|
|
|
this._inOrder.forEach(function(ext) {
|
|
var session = ext.createClientSession();
|
|
if (!session) return;
|
|
|
|
var record = [ext, session];
|
|
sessions.push(record);
|
|
index[ext.name] = record;
|
|
|
|
var offers = session.generateOffer();
|
|
offers = offers ? [].concat(offers) : [];
|
|
|
|
offers.forEach(function(off) {
|
|
offer.push(Parser.serializeParams(ext.name, off));
|
|
}, this);
|
|
}, this);
|
|
|
|
this._sessions = sessions;
|
|
this._index = index;
|
|
|
|
return offer.length > 0 ? offer.join(', ') : null;
|
|
},
|
|
|
|
activate: function(header) {
|
|
var responses = Parser.parseHeader(header),
|
|
sessions = [];
|
|
|
|
responses.eachOffer(function(name, params) {
|
|
var record = this._index[name];
|
|
|
|
if (!record)
|
|
throw new Error('Server sent an extension response for unknown extension "' + name + '"');
|
|
|
|
var ext = record[0],
|
|
session = record[1],
|
|
reserved = this._reserved(ext);
|
|
|
|
if (reserved)
|
|
throw new Error('Server sent two extension responses that use the RSV' +
|
|
reserved[0] + ' bit: "' +
|
|
reserved[1] + '" and "' + ext.name + '"');
|
|
|
|
if (session.activate(params) !== true)
|
|
throw new Error('Server sent unacceptable extension parameters: ' +
|
|
Parser.serializeParams(name, params));
|
|
|
|
this._reserve(ext);
|
|
sessions.push(record);
|
|
}, this);
|
|
|
|
this._sessions = sessions;
|
|
this._pipeline = new Pipeline(sessions);
|
|
},
|
|
|
|
generateResponse: function(header) {
|
|
var sessions = [],
|
|
response = [],
|
|
offers = Parser.parseHeader(header);
|
|
|
|
this._inOrder.forEach(function(ext) {
|
|
var offer = offers.byName(ext.name);
|
|
if (offer.length === 0 || this._reserved(ext)) return;
|
|
|
|
var session = ext.createServerSession(offer);
|
|
if (!session) return;
|
|
|
|
this._reserve(ext);
|
|
sessions.push([ext, session]);
|
|
response.push(Parser.serializeParams(ext.name, session.generateResponse()));
|
|
}, this);
|
|
|
|
this._sessions = sessions;
|
|
this._pipeline = new Pipeline(sessions);
|
|
|
|
return response.length > 0 ? response.join(', ') : null;
|
|
},
|
|
|
|
validFrameRsv: function(frame) {
|
|
var allowed = { rsv1: false, rsv2: false, rsv3: false },
|
|
ext;
|
|
|
|
if (Extensions$1.MESSAGE_OPCODES.indexOf(frame.opcode) >= 0) {
|
|
for (var i = 0, n = this._sessions.length; i < n; i++) {
|
|
ext = this._sessions[i][0];
|
|
allowed.rsv1 = allowed.rsv1 || ext.rsv1;
|
|
allowed.rsv2 = allowed.rsv2 || ext.rsv2;
|
|
allowed.rsv3 = allowed.rsv3 || ext.rsv3;
|
|
}
|
|
}
|
|
|
|
return (allowed.rsv1 || !frame.rsv1) &&
|
|
(allowed.rsv2 || !frame.rsv2) &&
|
|
(allowed.rsv3 || !frame.rsv3);
|
|
},
|
|
|
|
processIncomingMessage: function(message, callback, context) {
|
|
this._pipeline.processIncomingMessage(message, callback, context);
|
|
},
|
|
|
|
processOutgoingMessage: function(message, callback, context) {
|
|
this._pipeline.processOutgoingMessage(message, callback, context);
|
|
},
|
|
|
|
close: function(callback, context) {
|
|
if (!this._pipeline) return callback.call(context);
|
|
this._pipeline.close(callback, context);
|
|
},
|
|
|
|
_reserve: function(ext) {
|
|
this._rsv1 = this._rsv1 || (ext.rsv1 && ext.name);
|
|
this._rsv2 = this._rsv2 || (ext.rsv2 && ext.name);
|
|
this._rsv3 = this._rsv3 || (ext.rsv3 && ext.name);
|
|
},
|
|
|
|
_reserved: function(ext) {
|
|
if (this._rsv1 && ext.rsv1) return [1, this._rsv1];
|
|
if (this._rsv2 && ext.rsv2) return [2, this._rsv2];
|
|
if (this._rsv3 && ext.rsv3) return [3, this._rsv3];
|
|
return false;
|
|
}
|
|
};
|
|
|
|
for (var key$a in instance$a)
|
|
Extensions$1.prototype[key$a] = instance$a[key$a];
|
|
|
|
var websocket_extensions = Extensions$1;
|
|
|
|
var Frame$1 = function() {};
|
|
|
|
var instance$9 = {
|
|
final: false,
|
|
rsv1: false,
|
|
rsv2: false,
|
|
rsv3: false,
|
|
opcode: null,
|
|
masked: false,
|
|
maskingKey: null,
|
|
lengthBytes: 1,
|
|
length: 0,
|
|
payload: null
|
|
};
|
|
|
|
for (var key$9 in instance$9)
|
|
Frame$1.prototype[key$9] = instance$9[key$9];
|
|
|
|
var frame = Frame$1;
|
|
|
|
var Buffer$6 = safeBuffer.exports.Buffer;
|
|
|
|
var Message$1 = function() {
|
|
this.rsv1 = false;
|
|
this.rsv2 = false;
|
|
this.rsv3 = false;
|
|
this.opcode = null;
|
|
this.length = 0;
|
|
this._chunks = [];
|
|
};
|
|
|
|
var instance$8 = {
|
|
read: function() {
|
|
return this.data = this.data || Buffer$6.concat(this._chunks, this.length);
|
|
},
|
|
|
|
pushFrame: function(frame) {
|
|
this.rsv1 = this.rsv1 || frame.rsv1;
|
|
this.rsv2 = this.rsv2 || frame.rsv2;
|
|
this.rsv3 = this.rsv3 || frame.rsv3;
|
|
|
|
if (this.opcode === null) this.opcode = frame.opcode;
|
|
|
|
this._chunks.push(frame.payload);
|
|
this.length += frame.length;
|
|
}
|
|
};
|
|
|
|
for (var key$8 in instance$8)
|
|
Message$1.prototype[key$8] = instance$8[key$8];
|
|
|
|
var message = Message$1;
|
|
|
|
var Buffer$5 = safeBuffer.exports.Buffer,
|
|
crypto$2 = require$$1__default$1["default"],
|
|
util$a = require$$2__default["default"],
|
|
Extensions = websocket_extensions,
|
|
Base$6 = base,
|
|
Frame = frame,
|
|
Message = message;
|
|
|
|
var Hybi$2 = function(request, url, options) {
|
|
Base$6.apply(this, arguments);
|
|
|
|
this._extensions = new Extensions();
|
|
this._stage = 0;
|
|
this._masking = this._options.masking;
|
|
this._protocols = this._options.protocols || [];
|
|
this._requireMasking = this._options.requireMasking;
|
|
this._pingCallbacks = {};
|
|
|
|
if (typeof this._protocols === 'string')
|
|
this._protocols = this._protocols.split(/ *, */);
|
|
|
|
if (!this._request) return;
|
|
|
|
var protos = this._request.headers['sec-websocket-protocol'],
|
|
supported = this._protocols;
|
|
|
|
if (protos !== undefined) {
|
|
if (typeof protos === 'string') protos = protos.split(/ *, */);
|
|
this.protocol = protos.filter(function(p) { return supported.indexOf(p) >= 0 })[0];
|
|
}
|
|
|
|
this.version = 'hybi-' + Hybi$2.VERSION;
|
|
};
|
|
util$a.inherits(Hybi$2, Base$6);
|
|
|
|
Hybi$2.VERSION = '13';
|
|
|
|
Hybi$2.mask = function(payload, mask, offset) {
|
|
if (!mask || mask.length === 0) return payload;
|
|
offset = offset || 0;
|
|
|
|
for (var i = 0, n = payload.length - offset; i < n; i++) {
|
|
payload[offset + i] = payload[offset + i] ^ mask[i % 4];
|
|
}
|
|
return payload;
|
|
};
|
|
|
|
Hybi$2.generateAccept = function(key) {
|
|
var sha1 = crypto$2.createHash('sha1');
|
|
sha1.update(key + Hybi$2.GUID);
|
|
return sha1.digest('base64');
|
|
};
|
|
|
|
Hybi$2.GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
|
|
|
var instance$7 = {
|
|
FIN: 0x80,
|
|
MASK: 0x80,
|
|
RSV1: 0x40,
|
|
RSV2: 0x20,
|
|
RSV3: 0x10,
|
|
OPCODE: 0x0F,
|
|
LENGTH: 0x7F,
|
|
|
|
OPCODES: {
|
|
continuation: 0,
|
|
text: 1,
|
|
binary: 2,
|
|
close: 8,
|
|
ping: 9,
|
|
pong: 10
|
|
},
|
|
|
|
OPCODE_CODES: [0, 1, 2, 8, 9, 10],
|
|
MESSAGE_OPCODES: [0, 1, 2],
|
|
OPENING_OPCODES: [1, 2],
|
|
|
|
ERRORS: {
|
|
normal_closure: 1000,
|
|
going_away: 1001,
|
|
protocol_error: 1002,
|
|
unacceptable: 1003,
|
|
encoding_error: 1007,
|
|
policy_violation: 1008,
|
|
too_large: 1009,
|
|
extension_error: 1010,
|
|
unexpected_condition: 1011
|
|
},
|
|
|
|
ERROR_CODES: [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011],
|
|
DEFAULT_ERROR_CODE: 1000,
|
|
MIN_RESERVED_ERROR: 3000,
|
|
MAX_RESERVED_ERROR: 4999,
|
|
|
|
// http://www.w3.org/International/questions/qa-forms-utf-8.en.php
|
|
UTF8_MATCH: /^([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$/,
|
|
|
|
addExtension: function(extension) {
|
|
this._extensions.add(extension);
|
|
return true;
|
|
},
|
|
|
|
parse: function(chunk) {
|
|
this._reader.put(chunk);
|
|
var buffer = true;
|
|
while (buffer) {
|
|
switch (this._stage) {
|
|
case 0:
|
|
buffer = this._reader.read(1);
|
|
if (buffer) this._parseOpcode(buffer[0]);
|
|
break;
|
|
|
|
case 1:
|
|
buffer = this._reader.read(1);
|
|
if (buffer) this._parseLength(buffer[0]);
|
|
break;
|
|
|
|
case 2:
|
|
buffer = this._reader.read(this._frame.lengthBytes);
|
|
if (buffer) this._parseExtendedLength(buffer);
|
|
break;
|
|
|
|
case 3:
|
|
buffer = this._reader.read(4);
|
|
if (buffer) {
|
|
this._stage = 4;
|
|
this._frame.maskingKey = buffer;
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
buffer = this._reader.read(this._frame.length);
|
|
if (buffer) {
|
|
this._stage = 0;
|
|
this._emitFrame(buffer);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
buffer = null;
|
|
}
|
|
}
|
|
},
|
|
|
|
text: function(message) {
|
|
if (this.readyState > 1) return false;
|
|
return this.frame(message, 'text');
|
|
},
|
|
|
|
binary: function(message) {
|
|
if (this.readyState > 1) return false;
|
|
return this.frame(message, 'binary');
|
|
},
|
|
|
|
ping: function(message, callback) {
|
|
if (this.readyState > 1) return false;
|
|
message = message || '';
|
|
if (callback) this._pingCallbacks[message] = callback;
|
|
return this.frame(message, 'ping');
|
|
},
|
|
|
|
pong: function(message) {
|
|
if (this.readyState > 1) return false;
|
|
message = message ||'';
|
|
return this.frame(message, 'pong');
|
|
},
|
|
|
|
close: function(reason, code) {
|
|
reason = reason || '';
|
|
code = code || this.ERRORS.normal_closure;
|
|
|
|
if (this.readyState <= 0) {
|
|
this.readyState = 3;
|
|
this.emit('close', new Base$6.CloseEvent(code, reason));
|
|
return true;
|
|
} else if (this.readyState === 1) {
|
|
this.readyState = 2;
|
|
this._extensions.close(function() { this.frame(reason, 'close', code); }, this);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
frame: function(buffer, type, code) {
|
|
if (this.readyState <= 0) return this._queue([buffer, type, code]);
|
|
if (this.readyState > 2) return false;
|
|
|
|
if (buffer instanceof Array) buffer = Buffer$5.from(buffer);
|
|
if (typeof buffer === 'number') buffer = buffer.toString();
|
|
|
|
var message = new Message(),
|
|
isText = (typeof buffer === 'string'),
|
|
payload, copy;
|
|
|
|
message.rsv1 = message.rsv2 = message.rsv3 = false;
|
|
message.opcode = this.OPCODES[type || (isText ? 'text' : 'binary')];
|
|
|
|
payload = isText ? Buffer$5.from(buffer, 'utf8') : buffer;
|
|
|
|
if (code) {
|
|
copy = payload;
|
|
payload = Buffer$5.allocUnsafe(2 + copy.length);
|
|
payload.writeUInt16BE(code, 0);
|
|
copy.copy(payload, 2);
|
|
}
|
|
message.data = payload;
|
|
|
|
var onMessageReady = function(message) {
|
|
var frame = new Frame();
|
|
|
|
frame.final = true;
|
|
frame.rsv1 = message.rsv1;
|
|
frame.rsv2 = message.rsv2;
|
|
frame.rsv3 = message.rsv3;
|
|
frame.opcode = message.opcode;
|
|
frame.masked = !!this._masking;
|
|
frame.length = message.data.length;
|
|
frame.payload = message.data;
|
|
|
|
if (frame.masked) frame.maskingKey = crypto$2.randomBytes(4);
|
|
|
|
this._sendFrame(frame);
|
|
};
|
|
|
|
if (this.MESSAGE_OPCODES.indexOf(message.opcode) >= 0)
|
|
this._extensions.processOutgoingMessage(message, function(error, message) {
|
|
if (error) return this._fail('extension_error', error.message);
|
|
onMessageReady.call(this, message);
|
|
}, this);
|
|
else
|
|
onMessageReady.call(this, message);
|
|
|
|
return true;
|
|
},
|
|
|
|
_sendFrame: function(frame) {
|
|
var length = frame.length,
|
|
header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10),
|
|
offset = header + (frame.masked ? 4 : 0),
|
|
buffer = Buffer$5.allocUnsafe(offset + length),
|
|
masked = frame.masked ? this.MASK : 0;
|
|
|
|
buffer[0] = (frame.final ? this.FIN : 0) |
|
|
(frame.rsv1 ? this.RSV1 : 0) |
|
|
(frame.rsv2 ? this.RSV2 : 0) |
|
|
(frame.rsv3 ? this.RSV3 : 0) |
|
|
frame.opcode;
|
|
|
|
if (length <= 125) {
|
|
buffer[1] = masked | length;
|
|
} else if (length <= 65535) {
|
|
buffer[1] = masked | 126;
|
|
buffer.writeUInt16BE(length, 2);
|
|
} else {
|
|
buffer[1] = masked | 127;
|
|
buffer.writeUInt32BE(Math.floor(length / 0x100000000), 2);
|
|
buffer.writeUInt32BE(length % 0x100000000, 6);
|
|
}
|
|
|
|
frame.payload.copy(buffer, offset);
|
|
|
|
if (frame.masked) {
|
|
frame.maskingKey.copy(buffer, header);
|
|
Hybi$2.mask(buffer, frame.maskingKey, offset);
|
|
}
|
|
|
|
this._write(buffer);
|
|
},
|
|
|
|
_handshakeResponse: function() {
|
|
var secKey = this._request.headers['sec-websocket-key'],
|
|
version = this._request.headers['sec-websocket-version'];
|
|
|
|
if (version !== Hybi$2.VERSION)
|
|
throw new Error('Unsupported WebSocket version: ' + version);
|
|
|
|
if (typeof secKey !== 'string')
|
|
throw new Error('Missing handshake request header: Sec-WebSocket-Key');
|
|
|
|
this._headers.set('Upgrade', 'websocket');
|
|
this._headers.set('Connection', 'Upgrade');
|
|
this._headers.set('Sec-WebSocket-Accept', Hybi$2.generateAccept(secKey));
|
|
|
|
if (this.protocol) this._headers.set('Sec-WebSocket-Protocol', this.protocol);
|
|
|
|
var extensions = this._extensions.generateResponse(this._request.headers['sec-websocket-extensions']);
|
|
if (extensions) this._headers.set('Sec-WebSocket-Extensions', extensions);
|
|
|
|
var start = 'HTTP/1.1 101 Switching Protocols',
|
|
headers = [start, this._headers.toString(), ''];
|
|
|
|
return Buffer$5.from(headers.join('\r\n'), 'utf8');
|
|
},
|
|
|
|
_shutdown: function(code, reason, error) {
|
|
delete this._frame;
|
|
delete this._message;
|
|
this._stage = 5;
|
|
|
|
var sendCloseFrame = (this.readyState === 1);
|
|
this.readyState = 2;
|
|
|
|
this._extensions.close(function() {
|
|
if (sendCloseFrame) this.frame(reason, 'close', code);
|
|
this.readyState = 3;
|
|
if (error) this.emit('error', new Error(reason));
|
|
this.emit('close', new Base$6.CloseEvent(code, reason));
|
|
}, this);
|
|
},
|
|
|
|
_fail: function(type, message) {
|
|
if (this.readyState > 1) return;
|
|
this._shutdown(this.ERRORS[type], message, true);
|
|
},
|
|
|
|
_parseOpcode: function(octet) {
|
|
var rsvs = [this.RSV1, this.RSV2, this.RSV3].map(function(rsv) {
|
|
return (octet & rsv) === rsv;
|
|
});
|
|
|
|
var frame = this._frame = new Frame();
|
|
|
|
frame.final = (octet & this.FIN) === this.FIN;
|
|
frame.rsv1 = rsvs[0];
|
|
frame.rsv2 = rsvs[1];
|
|
frame.rsv3 = rsvs[2];
|
|
frame.opcode = (octet & this.OPCODE);
|
|
|
|
this._stage = 1;
|
|
|
|
if (!this._extensions.validFrameRsv(frame))
|
|
return this._fail('protocol_error',
|
|
'One or more reserved bits are on: reserved1 = ' + (frame.rsv1 ? 1 : 0) +
|
|
', reserved2 = ' + (frame.rsv2 ? 1 : 0) +
|
|
', reserved3 = ' + (frame.rsv3 ? 1 : 0));
|
|
|
|
if (this.OPCODE_CODES.indexOf(frame.opcode) < 0)
|
|
return this._fail('protocol_error', 'Unrecognized frame opcode: ' + frame.opcode);
|
|
|
|
if (this.MESSAGE_OPCODES.indexOf(frame.opcode) < 0 && !frame.final)
|
|
return this._fail('protocol_error', 'Received fragmented control frame: opcode = ' + frame.opcode);
|
|
|
|
if (this._message && this.OPENING_OPCODES.indexOf(frame.opcode) >= 0)
|
|
return this._fail('protocol_error', 'Received new data frame but previous continuous frame is unfinished');
|
|
},
|
|
|
|
_parseLength: function(octet) {
|
|
var frame = this._frame;
|
|
frame.masked = (octet & this.MASK) === this.MASK;
|
|
frame.length = (octet & this.LENGTH);
|
|
|
|
if (frame.length >= 0 && frame.length <= 125) {
|
|
this._stage = frame.masked ? 3 : 4;
|
|
if (!this._checkFrameLength()) return;
|
|
} else {
|
|
this._stage = 2;
|
|
frame.lengthBytes = (frame.length === 126 ? 2 : 8);
|
|
}
|
|
|
|
if (this._requireMasking && !frame.masked)
|
|
return this._fail('unacceptable', 'Received unmasked frame but masking is required');
|
|
},
|
|
|
|
_parseExtendedLength: function(buffer) {
|
|
var frame = this._frame;
|
|
frame.length = this._readUInt(buffer);
|
|
|
|
this._stage = frame.masked ? 3 : 4;
|
|
|
|
if (this.MESSAGE_OPCODES.indexOf(frame.opcode) < 0 && frame.length > 125)
|
|
return this._fail('protocol_error', 'Received control frame having too long payload: ' + frame.length);
|
|
|
|
if (!this._checkFrameLength()) return;
|
|
},
|
|
|
|
_checkFrameLength: function() {
|
|
var length = this._message ? this._message.length : 0;
|
|
|
|
if (length + this._frame.length > this._maxLength) {
|
|
this._fail('too_large', 'WebSocket frame length too large');
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
},
|
|
|
|
_emitFrame: function(buffer) {
|
|
var frame = this._frame,
|
|
payload = frame.payload = Hybi$2.mask(buffer, frame.maskingKey),
|
|
opcode = frame.opcode,
|
|
message,
|
|
code, reason,
|
|
callbacks, callback;
|
|
|
|
delete this._frame;
|
|
|
|
if (opcode === this.OPCODES.continuation) {
|
|
if (!this._message) return this._fail('protocol_error', 'Received unexpected continuation frame');
|
|
this._message.pushFrame(frame);
|
|
}
|
|
|
|
if (opcode === this.OPCODES.text || opcode === this.OPCODES.binary) {
|
|
this._message = new Message();
|
|
this._message.pushFrame(frame);
|
|
}
|
|
|
|
if (frame.final && this.MESSAGE_OPCODES.indexOf(opcode) >= 0)
|
|
return this._emitMessage(this._message);
|
|
|
|
if (opcode === this.OPCODES.close) {
|
|
code = (payload.length >= 2) ? payload.readUInt16BE(0) : null;
|
|
reason = (payload.length > 2) ? this._encode(payload.slice(2)) : null;
|
|
|
|
if (!(payload.length === 0) &&
|
|
!(code !== null && code >= this.MIN_RESERVED_ERROR && code <= this.MAX_RESERVED_ERROR) &&
|
|
this.ERROR_CODES.indexOf(code) < 0)
|
|
code = this.ERRORS.protocol_error;
|
|
|
|
if (payload.length > 125 || (payload.length > 2 && !reason))
|
|
code = this.ERRORS.protocol_error;
|
|
|
|
this._shutdown(code || this.DEFAULT_ERROR_CODE, reason || '');
|
|
}
|
|
|
|
if (opcode === this.OPCODES.ping) {
|
|
this.frame(payload, 'pong');
|
|
this.emit('ping', new Base$6.PingEvent(payload.toString()));
|
|
}
|
|
|
|
if (opcode === this.OPCODES.pong) {
|
|
callbacks = this._pingCallbacks;
|
|
message = this._encode(payload);
|
|
callback = callbacks[message];
|
|
|
|
delete callbacks[message];
|
|
if (callback) callback();
|
|
|
|
this.emit('pong', new Base$6.PongEvent(payload.toString()));
|
|
}
|
|
},
|
|
|
|
_emitMessage: function(message) {
|
|
var message = this._message;
|
|
message.read();
|
|
|
|
delete this._message;
|
|
|
|
this._extensions.processIncomingMessage(message, function(error, message) {
|
|
if (error) return this._fail('extension_error', error.message);
|
|
|
|
var payload = message.data;
|
|
if (message.opcode === this.OPCODES.text) payload = this._encode(payload);
|
|
|
|
if (payload === null)
|
|
return this._fail('encoding_error', 'Could not decode a text frame as UTF-8');
|
|
else
|
|
this.emit('message', new Base$6.MessageEvent(payload));
|
|
}, this);
|
|
},
|
|
|
|
_encode: function(buffer) {
|
|
try {
|
|
var string = buffer.toString('binary', 0, buffer.length);
|
|
if (!this.UTF8_MATCH.test(string)) return null;
|
|
} catch (e) {}
|
|
return buffer.toString('utf8', 0, buffer.length);
|
|
},
|
|
|
|
_readUInt: function(buffer) {
|
|
if (buffer.length === 2) return buffer.readUInt16BE(0);
|
|
|
|
return buffer.readUInt32BE(0) * 0x100000000 +
|
|
buffer.readUInt32BE(4);
|
|
}
|
|
};
|
|
|
|
for (var key$7 in instance$7)
|
|
Hybi$2.prototype[key$7] = instance$7[key$7];
|
|
|
|
var hybi = Hybi$2;
|
|
|
|
var Buffer$4 = safeBuffer.exports.Buffer,
|
|
Stream$2 = require$$0__default$1["default"].Stream,
|
|
url$2 = require$$2__default$1["default"],
|
|
util$9 = require$$2__default["default"],
|
|
Base$5 = base,
|
|
Headers$1 = headers,
|
|
HttpParser$2 = http_parser;
|
|
|
|
var PORTS = { 'ws:': 80, 'wss:': 443 };
|
|
|
|
var Proxy$1 = function(client, origin, options) {
|
|
this._client = client;
|
|
this._http = new HttpParser$2('response');
|
|
this._origin = (typeof client.url === 'object') ? client.url : url$2.parse(client.url);
|
|
this._url = (typeof origin === 'object') ? origin : url$2.parse(origin);
|
|
this._options = options || {};
|
|
this._state = 0;
|
|
|
|
this.readable = this.writable = true;
|
|
this._paused = false;
|
|
|
|
this._headers = new Headers$1();
|
|
this._headers.set('Host', this._origin.host);
|
|
this._headers.set('Connection', 'keep-alive');
|
|
this._headers.set('Proxy-Connection', 'keep-alive');
|
|
|
|
var auth = this._url.auth && Buffer$4.from(this._url.auth, 'utf8').toString('base64');
|
|
if (auth) this._headers.set('Proxy-Authorization', 'Basic ' + auth);
|
|
};
|
|
util$9.inherits(Proxy$1, Stream$2);
|
|
|
|
var instance$6 = {
|
|
setHeader: function(name, value) {
|
|
if (this._state !== 0) return false;
|
|
this._headers.set(name, value);
|
|
return true;
|
|
},
|
|
|
|
start: function() {
|
|
if (this._state !== 0) return false;
|
|
this._state = 1;
|
|
|
|
var origin = this._origin,
|
|
port = origin.port || PORTS[origin.protocol],
|
|
start = 'CONNECT ' + origin.hostname + ':' + port + ' HTTP/1.1';
|
|
|
|
var headers = [start, this._headers.toString(), ''];
|
|
|
|
this.emit('data', Buffer$4.from(headers.join('\r\n'), 'utf8'));
|
|
return true;
|
|
},
|
|
|
|
pause: function() {
|
|
this._paused = true;
|
|
},
|
|
|
|
resume: function() {
|
|
this._paused = false;
|
|
this.emit('drain');
|
|
},
|
|
|
|
write: function(chunk) {
|
|
if (!this.writable) return false;
|
|
|
|
this._http.parse(chunk);
|
|
if (!this._http.isComplete()) return !this._paused;
|
|
|
|
this.statusCode = this._http.statusCode;
|
|
this.headers = this._http.headers;
|
|
|
|
if (this.statusCode === 200) {
|
|
this.emit('connect', new Base$5.ConnectEvent());
|
|
} else {
|
|
var message = "Can't establish a connection to the server at " + this._origin.href;
|
|
this.emit('error', new Error(message));
|
|
}
|
|
this.end();
|
|
return !this._paused;
|
|
},
|
|
|
|
end: function(chunk) {
|
|
if (!this.writable) return;
|
|
if (chunk !== undefined) this.write(chunk);
|
|
this.readable = this.writable = false;
|
|
this.emit('close');
|
|
this.emit('end');
|
|
},
|
|
|
|
destroy: function() {
|
|
this.end();
|
|
}
|
|
};
|
|
|
|
for (var key$6 in instance$6)
|
|
Proxy$1.prototype[key$6] = instance$6[key$6];
|
|
|
|
var proxy = Proxy$1;
|
|
|
|
var Buffer$3 = safeBuffer.exports.Buffer,
|
|
crypto$1 = require$$1__default$1["default"],
|
|
url$1 = require$$2__default$1["default"],
|
|
util$8 = require$$2__default["default"],
|
|
HttpParser$1 = http_parser,
|
|
Base$4 = base,
|
|
Hybi$1 = hybi,
|
|
Proxy = proxy;
|
|
|
|
var Client$2 = function(_url, options) {
|
|
this.version = 'hybi-' + Hybi$1.VERSION;
|
|
Hybi$1.call(this, null, _url, options);
|
|
|
|
this.readyState = -1;
|
|
this._key = Client$2.generateKey();
|
|
this._accept = Hybi$1.generateAccept(this._key);
|
|
this._http = new HttpParser$1('response');
|
|
|
|
var uri = url$1.parse(this.url),
|
|
auth = uri.auth && Buffer$3.from(uri.auth, 'utf8').toString('base64');
|
|
|
|
if (this.VALID_PROTOCOLS.indexOf(uri.protocol) < 0)
|
|
throw new Error(this.url + ' is not a valid WebSocket URL');
|
|
|
|
this._pathname = (uri.pathname || '/') + (uri.search || '');
|
|
|
|
this._headers.set('Host', uri.host);
|
|
this._headers.set('Upgrade', 'websocket');
|
|
this._headers.set('Connection', 'Upgrade');
|
|
this._headers.set('Sec-WebSocket-Key', this._key);
|
|
this._headers.set('Sec-WebSocket-Version', Hybi$1.VERSION);
|
|
|
|
if (this._protocols.length > 0)
|
|
this._headers.set('Sec-WebSocket-Protocol', this._protocols.join(', '));
|
|
|
|
if (auth)
|
|
this._headers.set('Authorization', 'Basic ' + auth);
|
|
};
|
|
util$8.inherits(Client$2, Hybi$1);
|
|
|
|
Client$2.generateKey = function() {
|
|
return crypto$1.randomBytes(16).toString('base64');
|
|
};
|
|
|
|
var instance$5 = {
|
|
VALID_PROTOCOLS: ['ws:', 'wss:'],
|
|
|
|
proxy: function(origin, options) {
|
|
return new Proxy(this, origin, options);
|
|
},
|
|
|
|
start: function() {
|
|
if (this.readyState !== -1) return false;
|
|
this._write(this._handshakeRequest());
|
|
this.readyState = 0;
|
|
return true;
|
|
},
|
|
|
|
parse: function(chunk) {
|
|
if (this.readyState === 3) return;
|
|
if (this.readyState > 0) return Hybi$1.prototype.parse.call(this, chunk);
|
|
|
|
this._http.parse(chunk);
|
|
if (!this._http.isComplete()) return;
|
|
|
|
this._validateHandshake();
|
|
if (this.readyState === 3) return;
|
|
|
|
this._open();
|
|
this.parse(this._http.body);
|
|
},
|
|
|
|
_handshakeRequest: function() {
|
|
var extensions = this._extensions.generateOffer();
|
|
if (extensions)
|
|
this._headers.set('Sec-WebSocket-Extensions', extensions);
|
|
|
|
var start = 'GET ' + this._pathname + ' HTTP/1.1',
|
|
headers = [start, this._headers.toString(), ''];
|
|
|
|
return Buffer$3.from(headers.join('\r\n'), 'utf8');
|
|
},
|
|
|
|
_failHandshake: function(message) {
|
|
message = 'Error during WebSocket handshake: ' + message;
|
|
this.readyState = 3;
|
|
this.emit('error', new Error(message));
|
|
this.emit('close', new Base$4.CloseEvent(this.ERRORS.protocol_error, message));
|
|
},
|
|
|
|
_validateHandshake: function() {
|
|
this.statusCode = this._http.statusCode;
|
|
this.headers = this._http.headers;
|
|
|
|
if (this._http.error)
|
|
return this._failHandshake(this._http.error.message);
|
|
|
|
if (this._http.statusCode !== 101)
|
|
return this._failHandshake('Unexpected response code: ' + this._http.statusCode);
|
|
|
|
var headers = this._http.headers,
|
|
upgrade = headers['upgrade'] || '',
|
|
connection = headers['connection'] || '',
|
|
accept = headers['sec-websocket-accept'] || '',
|
|
protocol = headers['sec-websocket-protocol'] || '';
|
|
|
|
if (upgrade === '')
|
|
return this._failHandshake("'Upgrade' header is missing");
|
|
if (upgrade.toLowerCase() !== 'websocket')
|
|
return this._failHandshake("'Upgrade' header value is not 'WebSocket'");
|
|
|
|
if (connection === '')
|
|
return this._failHandshake("'Connection' header is missing");
|
|
if (connection.toLowerCase() !== 'upgrade')
|
|
return this._failHandshake("'Connection' header value is not 'Upgrade'");
|
|
|
|
if (accept !== this._accept)
|
|
return this._failHandshake('Sec-WebSocket-Accept mismatch');
|
|
|
|
this.protocol = null;
|
|
|
|
if (protocol !== '') {
|
|
if (this._protocols.indexOf(protocol) < 0)
|
|
return this._failHandshake('Sec-WebSocket-Protocol mismatch');
|
|
else
|
|
this.protocol = protocol;
|
|
}
|
|
|
|
try {
|
|
this._extensions.activate(this.headers['sec-websocket-extensions']);
|
|
} catch (e) {
|
|
return this._failHandshake(e.message);
|
|
}
|
|
}
|
|
};
|
|
|
|
for (var key$5 in instance$5)
|
|
Client$2.prototype[key$5] = instance$5[key$5];
|
|
|
|
var client$1 = Client$2;
|
|
|
|
var Buffer$2 = safeBuffer.exports.Buffer,
|
|
Base$3 = base,
|
|
util$7 = require$$2__default["default"];
|
|
|
|
var Draft75$2 = function(request, url, options) {
|
|
Base$3.apply(this, arguments);
|
|
this._stage = 0;
|
|
this.version = 'hixie-75';
|
|
|
|
this._headers.set('Upgrade', 'WebSocket');
|
|
this._headers.set('Connection', 'Upgrade');
|
|
this._headers.set('WebSocket-Origin', this._request.headers.origin);
|
|
this._headers.set('WebSocket-Location', this.url);
|
|
};
|
|
util$7.inherits(Draft75$2, Base$3);
|
|
|
|
var instance$4 = {
|
|
close: function() {
|
|
if (this.readyState === 3) return false;
|
|
this.readyState = 3;
|
|
this.emit('close', new Base$3.CloseEvent(null, null));
|
|
return true;
|
|
},
|
|
|
|
parse: function(chunk) {
|
|
if (this.readyState > 1) return;
|
|
|
|
this._reader.put(chunk);
|
|
|
|
this._reader.eachByte(function(octet) {
|
|
var message;
|
|
|
|
switch (this._stage) {
|
|
case -1:
|
|
this._body.push(octet);
|
|
this._sendHandshakeBody();
|
|
break;
|
|
|
|
case 0:
|
|
this._parseLeadingByte(octet);
|
|
break;
|
|
|
|
case 1:
|
|
this._length = (octet & 0x7F) + 128 * this._length;
|
|
|
|
if (this._closing && this._length === 0) {
|
|
return this.close();
|
|
}
|
|
else if ((octet & 0x80) !== 0x80) {
|
|
if (this._length === 0) {
|
|
this._stage = 0;
|
|
}
|
|
else {
|
|
this._skipped = 0;
|
|
this._stage = 2;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
if (octet === 0xFF) {
|
|
this._stage = 0;
|
|
message = Buffer$2.from(this._buffer).toString('utf8', 0, this._buffer.length);
|
|
this.emit('message', new Base$3.MessageEvent(message));
|
|
}
|
|
else {
|
|
if (this._length) {
|
|
this._skipped += 1;
|
|
if (this._skipped === this._length)
|
|
this._stage = 0;
|
|
} else {
|
|
this._buffer.push(octet);
|
|
if (this._buffer.length > this._maxLength) return this.close();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
frame: function(buffer) {
|
|
if (this.readyState === 0) return this._queue([buffer]);
|
|
if (this.readyState > 1) return false;
|
|
|
|
if (typeof buffer !== 'string') buffer = buffer.toString();
|
|
|
|
var length = Buffer$2.byteLength(buffer),
|
|
frame = Buffer$2.allocUnsafe(length + 2);
|
|
|
|
frame[0] = 0x00;
|
|
frame.write(buffer, 1);
|
|
frame[frame.length - 1] = 0xFF;
|
|
|
|
this._write(frame);
|
|
return true;
|
|
},
|
|
|
|
_handshakeResponse: function() {
|
|
var start = 'HTTP/1.1 101 Web Socket Protocol Handshake',
|
|
headers = [start, this._headers.toString(), ''];
|
|
|
|
return Buffer$2.from(headers.join('\r\n'), 'utf8');
|
|
},
|
|
|
|
_parseLeadingByte: function(octet) {
|
|
if ((octet & 0x80) === 0x80) {
|
|
this._length = 0;
|
|
this._stage = 1;
|
|
} else {
|
|
delete this._length;
|
|
delete this._skipped;
|
|
this._buffer = [];
|
|
this._stage = 2;
|
|
}
|
|
}
|
|
};
|
|
|
|
for (var key$4 in instance$4)
|
|
Draft75$2.prototype[key$4] = instance$4[key$4];
|
|
|
|
var draft75 = Draft75$2;
|
|
|
|
var Buffer$1 = safeBuffer.exports.Buffer,
|
|
Base$2 = base,
|
|
Draft75$1 = draft75,
|
|
crypto = require$$1__default$1["default"],
|
|
util$6 = require$$2__default["default"];
|
|
|
|
|
|
var numberFromKey = function(key) {
|
|
return parseInt((key.match(/[0-9]/g) || []).join(''), 10);
|
|
};
|
|
|
|
var spacesInKey = function(key) {
|
|
return (key.match(/ /g) || []).length;
|
|
};
|
|
|
|
|
|
var Draft76$1 = function(request, url, options) {
|
|
Draft75$1.apply(this, arguments);
|
|
this._stage = -1;
|
|
this._body = [];
|
|
this.version = 'hixie-76';
|
|
|
|
this._headers.clear();
|
|
|
|
this._headers.set('Upgrade', 'WebSocket');
|
|
this._headers.set('Connection', 'Upgrade');
|
|
this._headers.set('Sec-WebSocket-Origin', this._request.headers.origin);
|
|
this._headers.set('Sec-WebSocket-Location', this.url);
|
|
};
|
|
util$6.inherits(Draft76$1, Draft75$1);
|
|
|
|
var instance$3 = {
|
|
BODY_SIZE: 8,
|
|
|
|
start: function() {
|
|
if (!Draft75$1.prototype.start.call(this)) return false;
|
|
this._started = true;
|
|
this._sendHandshakeBody();
|
|
return true;
|
|
},
|
|
|
|
close: function() {
|
|
if (this.readyState === 3) return false;
|
|
if (this.readyState === 1) this._write(Buffer$1.from([0xFF, 0x00]));
|
|
this.readyState = 3;
|
|
this.emit('close', new Base$2.CloseEvent(null, null));
|
|
return true;
|
|
},
|
|
|
|
_handshakeResponse: function() {
|
|
var headers = this._request.headers,
|
|
key1 = headers['sec-websocket-key1'],
|
|
key2 = headers['sec-websocket-key2'];
|
|
|
|
if (!key1) throw new Error('Missing required header: Sec-WebSocket-Key1');
|
|
if (!key2) throw new Error('Missing required header: Sec-WebSocket-Key2');
|
|
|
|
var number1 = numberFromKey(key1),
|
|
spaces1 = spacesInKey(key1),
|
|
|
|
number2 = numberFromKey(key2),
|
|
spaces2 = spacesInKey(key2);
|
|
|
|
if (number1 % spaces1 !== 0 || number2 % spaces2 !== 0)
|
|
throw new Error('Client sent invalid Sec-WebSocket-Key headers');
|
|
|
|
this._keyValues = [number1 / spaces1, number2 / spaces2];
|
|
|
|
var start = 'HTTP/1.1 101 WebSocket Protocol Handshake',
|
|
headers = [start, this._headers.toString(), ''];
|
|
|
|
return Buffer$1.from(headers.join('\r\n'), 'binary');
|
|
},
|
|
|
|
_handshakeSignature: function() {
|
|
if (this._body.length < this.BODY_SIZE) return null;
|
|
|
|
var md5 = crypto.createHash('md5'),
|
|
buffer = Buffer$1.allocUnsafe(8 + this.BODY_SIZE);
|
|
|
|
buffer.writeUInt32BE(this._keyValues[0], 0);
|
|
buffer.writeUInt32BE(this._keyValues[1], 4);
|
|
Buffer$1.from(this._body).copy(buffer, 8, 0, this.BODY_SIZE);
|
|
|
|
md5.update(buffer);
|
|
return Buffer$1.from(md5.digest('binary'), 'binary');
|
|
},
|
|
|
|
_sendHandshakeBody: function() {
|
|
if (!this._started) return;
|
|
var signature = this._handshakeSignature();
|
|
if (!signature) return;
|
|
|
|
this._write(signature);
|
|
this._stage = 0;
|
|
this._open();
|
|
|
|
if (this._body.length > this.BODY_SIZE)
|
|
this.parse(this._body.slice(this.BODY_SIZE));
|
|
},
|
|
|
|
_parseLeadingByte: function(octet) {
|
|
if (octet !== 0xFF)
|
|
return Draft75$1.prototype._parseLeadingByte.call(this, octet);
|
|
|
|
this._closing = true;
|
|
this._length = 0;
|
|
this._stage = 1;
|
|
}
|
|
};
|
|
|
|
for (var key$3 in instance$3)
|
|
Draft76$1.prototype[key$3] = instance$3[key$3];
|
|
|
|
var draft76 = Draft76$1;
|
|
|
|
var util$5 = require$$2__default["default"],
|
|
HttpParser = http_parser,
|
|
Base$1 = base,
|
|
Draft75 = draft75,
|
|
Draft76 = draft76,
|
|
Hybi = hybi;
|
|
|
|
var Server$1 = function(options) {
|
|
Base$1.call(this, null, null, options);
|
|
this._http = new HttpParser('request');
|
|
};
|
|
util$5.inherits(Server$1, Base$1);
|
|
|
|
var instance$2 = {
|
|
EVENTS: ['open', 'message', 'error', 'close', 'ping', 'pong'],
|
|
|
|
_bindEventListeners: function() {
|
|
this.messages.on('error', function() {});
|
|
this.on('error', function() {});
|
|
},
|
|
|
|
parse: function(chunk) {
|
|
if (this._delegate) return this._delegate.parse(chunk);
|
|
|
|
this._http.parse(chunk);
|
|
if (!this._http.isComplete()) return;
|
|
|
|
this.method = this._http.method;
|
|
this.url = this._http.url;
|
|
this.headers = this._http.headers;
|
|
this.body = this._http.body;
|
|
|
|
var self = this;
|
|
this._delegate = Server$1.http(this, this._options);
|
|
this._delegate.messages = this.messages;
|
|
this._delegate.io = this.io;
|
|
this._open();
|
|
|
|
this.EVENTS.forEach(function(event) {
|
|
this._delegate.on(event, function(e) { self.emit(event, e); });
|
|
}, this);
|
|
|
|
this.protocol = this._delegate.protocol;
|
|
this.version = this._delegate.version;
|
|
|
|
this.parse(this._http.body);
|
|
this.emit('connect', new Base$1.ConnectEvent());
|
|
},
|
|
|
|
_open: function() {
|
|
this.__queue.forEach(function(msg) {
|
|
this._delegate[msg[0]].apply(this._delegate, msg[1]);
|
|
}, this);
|
|
this.__queue = [];
|
|
}
|
|
};
|
|
|
|
['addExtension', 'setHeader', 'start', 'frame', 'text', 'binary', 'ping', 'close'].forEach(function(method) {
|
|
instance$2[method] = function() {
|
|
if (this._delegate) {
|
|
return this._delegate[method].apply(this._delegate, arguments);
|
|
} else {
|
|
this.__queue.push([method, arguments]);
|
|
return true;
|
|
}
|
|
};
|
|
});
|
|
|
|
for (var key$2 in instance$2)
|
|
Server$1.prototype[key$2] = instance$2[key$2];
|
|
|
|
Server$1.isSecureRequest = function(request) {
|
|
if (request.connection && request.connection.authorized !== undefined) return true;
|
|
if (request.socket && request.socket.secure) return true;
|
|
|
|
var headers = request.headers;
|
|
if (!headers) return false;
|
|
if (headers['https'] === 'on') return true;
|
|
if (headers['x-forwarded-ssl'] === 'on') return true;
|
|
if (headers['x-forwarded-scheme'] === 'https') return true;
|
|
if (headers['x-forwarded-proto'] === 'https') return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
Server$1.determineUrl = function(request) {
|
|
var scheme = this.isSecureRequest(request) ? 'wss:' : 'ws:';
|
|
return scheme + '//' + request.headers.host + request.url;
|
|
};
|
|
|
|
Server$1.http = function(request, options) {
|
|
options = options || {};
|
|
if (options.requireMasking === undefined) options.requireMasking = true;
|
|
|
|
var headers = request.headers,
|
|
version = headers['sec-websocket-version'],
|
|
key = headers['sec-websocket-key'],
|
|
key1 = headers['sec-websocket-key1'],
|
|
key2 = headers['sec-websocket-key2'],
|
|
url = this.determineUrl(request);
|
|
|
|
if (version || key)
|
|
return new Hybi(request, url, options);
|
|
else if (key1 || key2)
|
|
return new Draft76(request, url, options);
|
|
else
|
|
return new Draft75(request, url, options);
|
|
};
|
|
|
|
var server = Server$1;
|
|
|
|
// Protocol references:
|
|
//
|
|
// * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
|
|
// * http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
|
// * http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
|
|
|
var Base = base,
|
|
Client$1 = client$1,
|
|
Server = server;
|
|
|
|
var Driver = {
|
|
client: function(url, options) {
|
|
options = options || {};
|
|
if (options.masking === undefined) options.masking = true;
|
|
return new Client$1(url, options);
|
|
},
|
|
|
|
server: function(options) {
|
|
options = options || {};
|
|
if (options.requireMasking === undefined) options.requireMasking = true;
|
|
return new Server(options);
|
|
},
|
|
|
|
http: function() {
|
|
return Server.http.apply(Server, arguments);
|
|
},
|
|
|
|
isSecureRequest: function(request) {
|
|
return Server.isSecureRequest(request);
|
|
},
|
|
|
|
isWebSocket: function(request) {
|
|
return Base.isWebSocket(request);
|
|
},
|
|
|
|
validateOptions: function(options, validKeys) {
|
|
Base.validateOptions(options, validKeys);
|
|
}
|
|
};
|
|
|
|
var driver$4 = Driver;
|
|
|
|
var Event$3 = function(eventType, options) {
|
|
this.type = eventType;
|
|
for (var key in options)
|
|
this[key] = options[key];
|
|
};
|
|
|
|
Event$3.prototype.initEvent = function(eventType, canBubble, cancelable) {
|
|
this.type = eventType;
|
|
this.bubbles = canBubble;
|
|
this.cancelable = cancelable;
|
|
};
|
|
|
|
Event$3.prototype.stopPropagation = function() {};
|
|
Event$3.prototype.preventDefault = function() {};
|
|
|
|
Event$3.CAPTURING_PHASE = 1;
|
|
Event$3.AT_TARGET = 2;
|
|
Event$3.BUBBLING_PHASE = 3;
|
|
|
|
var event = Event$3;
|
|
|
|
var Event$2 = event;
|
|
|
|
var EventTarget$2 = {
|
|
onopen: null,
|
|
onmessage: null,
|
|
onerror: null,
|
|
onclose: null,
|
|
|
|
addEventListener: function(eventType, listener, useCapture) {
|
|
this.on(eventType, listener);
|
|
},
|
|
|
|
removeEventListener: function(eventType, listener, useCapture) {
|
|
this.removeListener(eventType, listener);
|
|
},
|
|
|
|
dispatchEvent: function(event) {
|
|
event.target = event.currentTarget = this;
|
|
event.eventPhase = Event$2.AT_TARGET;
|
|
|
|
if (this['on' + event.type])
|
|
this['on' + event.type](event);
|
|
|
|
this.emit(event.type, event);
|
|
}
|
|
};
|
|
|
|
var event_target = EventTarget$2;
|
|
|
|
var Stream$1 = require$$0__default$1["default"].Stream,
|
|
util$4 = require$$2__default["default"],
|
|
driver$3 = driver$4,
|
|
EventTarget$1 = event_target,
|
|
Event$1 = event;
|
|
|
|
var API$3 = function(options) {
|
|
options = options || {};
|
|
driver$3.validateOptions(options, ['headers', 'extensions', 'maxLength', 'ping', 'proxy', 'tls', 'ca']);
|
|
|
|
this.readable = this.writable = true;
|
|
|
|
var headers = options.headers;
|
|
if (headers) {
|
|
for (var name in headers) this._driver.setHeader(name, headers[name]);
|
|
}
|
|
|
|
var extensions = options.extensions;
|
|
if (extensions) {
|
|
[].concat(extensions).forEach(this._driver.addExtension, this._driver);
|
|
}
|
|
|
|
this._ping = options.ping;
|
|
this._pingId = 0;
|
|
this.readyState = API$3.CONNECTING;
|
|
this.bufferedAmount = 0;
|
|
this.protocol = '';
|
|
this.url = this._driver.url;
|
|
this.version = this._driver.version;
|
|
|
|
var self = this;
|
|
|
|
this._driver.on('open', function(e) { self._open(); });
|
|
this._driver.on('message', function(e) { self._receiveMessage(e.data); });
|
|
this._driver.on('close', function(e) { self._beginClose(e.reason, e.code); });
|
|
|
|
this._driver.on('error', function(error) {
|
|
self._emitError(error.message);
|
|
});
|
|
this.on('error', function() {});
|
|
|
|
this._driver.messages.on('drain', function() {
|
|
self.emit('drain');
|
|
});
|
|
|
|
if (this._ping)
|
|
this._pingTimer = setInterval(function() {
|
|
self._pingId += 1;
|
|
self.ping(self._pingId.toString());
|
|
}, this._ping * 1000);
|
|
|
|
this._configureStream();
|
|
|
|
if (!this._proxy) {
|
|
this._stream.pipe(this._driver.io);
|
|
this._driver.io.pipe(this._stream);
|
|
}
|
|
};
|
|
util$4.inherits(API$3, Stream$1);
|
|
|
|
API$3.CONNECTING = 0;
|
|
API$3.OPEN = 1;
|
|
API$3.CLOSING = 2;
|
|
API$3.CLOSED = 3;
|
|
|
|
API$3.CLOSE_TIMEOUT = 30000;
|
|
|
|
var instance$1 = {
|
|
write: function(data) {
|
|
return this.send(data);
|
|
},
|
|
|
|
end: function(data) {
|
|
if (data !== undefined) this.send(data);
|
|
this.close();
|
|
},
|
|
|
|
pause: function() {
|
|
return this._driver.messages.pause();
|
|
},
|
|
|
|
resume: function() {
|
|
return this._driver.messages.resume();
|
|
},
|
|
|
|
send: function(data) {
|
|
if (this.readyState > API$3.OPEN) return false;
|
|
if (!(data instanceof Buffer)) data = String(data);
|
|
return this._driver.messages.write(data);
|
|
},
|
|
|
|
ping: function(message, callback) {
|
|
if (this.readyState > API$3.OPEN) return false;
|
|
return this._driver.ping(message, callback);
|
|
},
|
|
|
|
close: function(code, reason) {
|
|
if (code === undefined) code = 1000;
|
|
if (reason === undefined) reason = '';
|
|
|
|
if (code !== 1000 && (code < 3000 || code > 4999))
|
|
throw new Error("Failed to execute 'close' on WebSocket: " +
|
|
"The code must be either 1000, or between 3000 and 4999. " +
|
|
code + " is neither.");
|
|
|
|
if (this.readyState < API$3.CLOSING) {
|
|
var self = this;
|
|
this._closeTimer = setTimeout(function() {
|
|
self._beginClose('', 1006);
|
|
}, API$3.CLOSE_TIMEOUT);
|
|
}
|
|
|
|
if (this.readyState !== API$3.CLOSED) this.readyState = API$3.CLOSING;
|
|
|
|
this._driver.close(reason, code);
|
|
},
|
|
|
|
_configureStream: function() {
|
|
var self = this;
|
|
|
|
this._stream.setTimeout(0);
|
|
this._stream.setNoDelay(true);
|
|
|
|
['close', 'end'].forEach(function(event) {
|
|
this._stream.on(event, function() { self._finalizeClose(); });
|
|
}, this);
|
|
|
|
this._stream.on('error', function(error) {
|
|
self._emitError('Network error: ' + self.url + ': ' + error.message);
|
|
self._finalizeClose();
|
|
});
|
|
},
|
|
|
|
_open: function() {
|
|
if (this.readyState !== API$3.CONNECTING) return;
|
|
|
|
this.readyState = API$3.OPEN;
|
|
this.protocol = this._driver.protocol || '';
|
|
|
|
var event = new Event$1('open');
|
|
event.initEvent('open', false, false);
|
|
this.dispatchEvent(event);
|
|
},
|
|
|
|
_receiveMessage: function(data) {
|
|
if (this.readyState > API$3.OPEN) return false;
|
|
|
|
if (this.readable) this.emit('data', data);
|
|
|
|
var event = new Event$1('message', { data: data });
|
|
event.initEvent('message', false, false);
|
|
this.dispatchEvent(event);
|
|
},
|
|
|
|
_emitError: function(message) {
|
|
if (this.readyState >= API$3.CLOSING) return;
|
|
|
|
var event = new Event$1('error', { message: message });
|
|
event.initEvent('error', false, false);
|
|
this.dispatchEvent(event);
|
|
},
|
|
|
|
_beginClose: function(reason, code) {
|
|
if (this.readyState === API$3.CLOSED) return;
|
|
this.readyState = API$3.CLOSING;
|
|
this._closeParams = [reason, code];
|
|
|
|
if (this._stream) {
|
|
this._stream.destroy();
|
|
if (!this._stream.readable) this._finalizeClose();
|
|
}
|
|
},
|
|
|
|
_finalizeClose: function() {
|
|
if (this.readyState === API$3.CLOSED) return;
|
|
this.readyState = API$3.CLOSED;
|
|
|
|
if (this._closeTimer) clearTimeout(this._closeTimer);
|
|
if (this._pingTimer) clearInterval(this._pingTimer);
|
|
if (this._stream) this._stream.end();
|
|
|
|
if (this.readable) this.emit('end');
|
|
this.readable = this.writable = false;
|
|
|
|
var reason = this._closeParams ? this._closeParams[0] : '',
|
|
code = this._closeParams ? this._closeParams[1] : 1006;
|
|
|
|
var event = new Event$1('close', { code: code, reason: reason });
|
|
event.initEvent('close', false, false);
|
|
this.dispatchEvent(event);
|
|
}
|
|
};
|
|
|
|
for (var method$1 in instance$1) API$3.prototype[method$1] = instance$1[method$1];
|
|
for (var key$1 in EventTarget$1) API$3.prototype[key$1] = EventTarget$1[key$1];
|
|
|
|
var api = API$3;
|
|
|
|
var util$3 = require$$2__default["default"],
|
|
net = require$$1__default$2["default"],
|
|
tls = require$$2__default$2["default"],
|
|
url = require$$2__default$1["default"],
|
|
driver$2 = driver$4,
|
|
API$2 = api;
|
|
|
|
var DEFAULT_PORTS = { 'http:': 80, 'https:': 443, 'ws:':80, 'wss:': 443 },
|
|
SECURE_PROTOCOLS = ['https:', 'wss:'];
|
|
|
|
var Client = function(_url, protocols, options) {
|
|
options = options || {};
|
|
|
|
this.url = _url;
|
|
this._driver = driver$2.client(this.url, { maxLength: options.maxLength, protocols: protocols });
|
|
|
|
['open', 'error'].forEach(function(event) {
|
|
this._driver.on(event, function() {
|
|
self.headers = self._driver.headers;
|
|
self.statusCode = self._driver.statusCode;
|
|
});
|
|
}, this);
|
|
|
|
var proxy = options.proxy || {},
|
|
endpoint = url.parse(proxy.origin || this.url),
|
|
port = endpoint.port || DEFAULT_PORTS[endpoint.protocol],
|
|
secure = SECURE_PROTOCOLS.indexOf(endpoint.protocol) >= 0,
|
|
onConnect = function() { self._onConnect(); },
|
|
netOptions = options.net || {},
|
|
originTLS = options.tls || {},
|
|
socketTLS = proxy.origin ? (proxy.tls || {}) : originTLS,
|
|
self = this;
|
|
|
|
netOptions.host = socketTLS.host = endpoint.hostname;
|
|
netOptions.port = socketTLS.port = port;
|
|
|
|
originTLS.ca = originTLS.ca || options.ca;
|
|
socketTLS.servername = socketTLS.servername || endpoint.hostname;
|
|
|
|
this._stream = secure
|
|
? tls.connect(socketTLS, onConnect)
|
|
: net.connect(netOptions, onConnect);
|
|
|
|
if (proxy.origin) this._configureProxy(proxy, originTLS);
|
|
|
|
API$2.call(this, options);
|
|
};
|
|
util$3.inherits(Client, API$2);
|
|
|
|
Client.prototype._onConnect = function() {
|
|
var worker = this._proxy || this._driver;
|
|
worker.start();
|
|
};
|
|
|
|
Client.prototype._configureProxy = function(proxy, originTLS) {
|
|
var uri = url.parse(this.url),
|
|
secure = SECURE_PROTOCOLS.indexOf(uri.protocol) >= 0,
|
|
self = this,
|
|
name;
|
|
|
|
this._proxy = this._driver.proxy(proxy.origin);
|
|
|
|
if (proxy.headers) {
|
|
for (name in proxy.headers) this._proxy.setHeader(name, proxy.headers[name]);
|
|
}
|
|
|
|
this._proxy.pipe(this._stream, { end: false });
|
|
this._stream.pipe(this._proxy);
|
|
|
|
this._proxy.on('connect', function() {
|
|
if (secure) {
|
|
var options = { socket: self._stream, servername: uri.hostname };
|
|
for (name in originTLS) options[name] = originTLS[name];
|
|
self._stream = tls.connect(options);
|
|
self._configureStream();
|
|
}
|
|
self._driver.io.pipe(self._stream);
|
|
self._stream.pipe(self._driver.io);
|
|
self._driver.start();
|
|
});
|
|
|
|
this._proxy.on('error', function(error) {
|
|
self._driver.emit('error', error);
|
|
});
|
|
};
|
|
|
|
var client = Client;
|
|
|
|
var Stream = require$$0__default$1["default"].Stream,
|
|
util$2 = require$$2__default["default"],
|
|
driver$1 = driver$4,
|
|
Headers = headers,
|
|
API$1 = api,
|
|
EventTarget = event_target,
|
|
Event = event;
|
|
|
|
var EventSource = function(request, response, options) {
|
|
this.writable = true;
|
|
options = options || {};
|
|
|
|
this._stream = response.socket;
|
|
this._ping = options.ping || this.DEFAULT_PING;
|
|
this._retry = options.retry || this.DEFAULT_RETRY;
|
|
|
|
var scheme = driver$1.isSecureRequest(request) ? 'https:' : 'http:';
|
|
this.url = scheme + '//' + request.headers.host + request.url;
|
|
this.lastEventId = request.headers['last-event-id'] || '';
|
|
this.readyState = API$1.CONNECTING;
|
|
|
|
var headers = new Headers(),
|
|
self = this;
|
|
|
|
if (options.headers) {
|
|
for (var key in options.headers) headers.set(key, options.headers[key]);
|
|
}
|
|
|
|
if (!this._stream || !this._stream.writable) return;
|
|
process.nextTick(function() { self._open(); });
|
|
|
|
this._stream.setTimeout(0);
|
|
this._stream.setNoDelay(true);
|
|
|
|
var handshake = 'HTTP/1.1 200 OK\r\n' +
|
|
'Content-Type: text/event-stream\r\n' +
|
|
'Cache-Control: no-cache, no-store\r\n' +
|
|
'Connection: close\r\n' +
|
|
headers.toString() +
|
|
'\r\n' +
|
|
'retry: ' + Math.floor(this._retry * 1000) + '\r\n\r\n';
|
|
|
|
this._write(handshake);
|
|
|
|
this._stream.on('drain', function() { self.emit('drain'); });
|
|
|
|
if (this._ping)
|
|
this._pingTimer = setInterval(function() { self.ping(); }, this._ping * 1000);
|
|
|
|
['error', 'end'].forEach(function(event) {
|
|
self._stream.on(event, function() { self.close(); });
|
|
});
|
|
};
|
|
util$2.inherits(EventSource, Stream);
|
|
|
|
EventSource.isEventSource = function(request) {
|
|
if (request.method !== 'GET') return false;
|
|
var accept = (request.headers.accept || '').split(/\s*,\s*/);
|
|
return accept.indexOf('text/event-stream') >= 0;
|
|
};
|
|
|
|
var instance = {
|
|
DEFAULT_PING: 10,
|
|
DEFAULT_RETRY: 5,
|
|
|
|
_write: function(chunk) {
|
|
if (!this.writable) return false;
|
|
try {
|
|
return this._stream.write(chunk, 'utf8');
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
_open: function() {
|
|
if (this.readyState !== API$1.CONNECTING) return;
|
|
|
|
this.readyState = API$1.OPEN;
|
|
|
|
var event = new Event('open');
|
|
event.initEvent('open', false, false);
|
|
this.dispatchEvent(event);
|
|
},
|
|
|
|
write: function(message) {
|
|
return this.send(message);
|
|
},
|
|
|
|
end: function(message) {
|
|
if (message !== undefined) this.write(message);
|
|
this.close();
|
|
},
|
|
|
|
send: function(message, options) {
|
|
if (this.readyState > API$1.OPEN) return false;
|
|
|
|
message = String(message).replace(/(\r\n|\r|\n)/g, '$1data: ');
|
|
options = options || {};
|
|
|
|
var frame = '';
|
|
if (options.event) frame += 'event: ' + options.event + '\r\n';
|
|
if (options.id) frame += 'id: ' + options.id + '\r\n';
|
|
frame += 'data: ' + message + '\r\n\r\n';
|
|
|
|
return this._write(frame);
|
|
},
|
|
|
|
ping: function() {
|
|
return this._write(':\r\n\r\n');
|
|
},
|
|
|
|
close: function() {
|
|
if (this. |