mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-02 16:19:25 +00:00
566 lines
14 KiB
JavaScript
566 lines
14 KiB
JavaScript
var parser = exports;
|
|
|
|
var spdy = require('../../spdy'),
|
|
utils = spdy.utils,
|
|
util = require('util'),
|
|
stream = require('stream'),
|
|
Buffer = require('buffer').Buffer;
|
|
|
|
var legacy = !stream.Duplex;
|
|
|
|
if (legacy) {
|
|
var DuplexStream = stream;
|
|
} else {
|
|
var DuplexStream = stream.Duplex;
|
|
}
|
|
|
|
//
|
|
// ### function Parser (connection)
|
|
// #### @connection {spdy.Connection} connection
|
|
// SPDY protocol frames parser's @constructor
|
|
//
|
|
function Parser(connection) {
|
|
DuplexStream.call(this);
|
|
|
|
this.paused = false;
|
|
this.buffer = [];
|
|
this.buffered = 0;
|
|
this.waiting = 8;
|
|
|
|
this.state = { type: 'frame-head' };
|
|
this.socket = connection.socket;
|
|
this.connection = connection;
|
|
|
|
this.version = null;
|
|
this.deflate = null;
|
|
this.inflate = null;
|
|
|
|
this.connection = connection;
|
|
|
|
if (legacy) {
|
|
this.readable = this.writable = true;
|
|
}
|
|
}
|
|
util.inherits(Parser, DuplexStream);
|
|
|
|
//
|
|
// ### function create (connection)
|
|
// #### @connection {spdy.Connection} connection
|
|
// @constructor wrapper
|
|
//
|
|
parser.create = function create(connection) {
|
|
return new Parser(connection);
|
|
};
|
|
|
|
//
|
|
// ### function destroy ()
|
|
// Just a stub.
|
|
//
|
|
Parser.prototype.destroy = function destroy() {
|
|
};
|
|
|
|
//
|
|
// ### function _write (data, encoding, cb)
|
|
// #### @data {Buffer} chunk of data
|
|
// #### @encoding {Null} encoding
|
|
// #### @cb {Function} callback
|
|
// Writes or buffers data to parser
|
|
//
|
|
Parser.prototype._write = function write(data, encoding, cb) {
|
|
// Legacy compatibility
|
|
if (!cb) cb = function() {};
|
|
|
|
if (data !== undefined) {
|
|
// Buffer data
|
|
this.buffer.push(data);
|
|
this.buffered += data.length;
|
|
}
|
|
|
|
// Notify caller about state (for piping)
|
|
if (this.paused) {
|
|
this.needDrain = true;
|
|
cb();
|
|
return false;
|
|
}
|
|
|
|
// We shall not do anything until we get all expected data
|
|
if (this.buffered < this.waiting) {
|
|
if (this.needDrain) {
|
|
// Mark parser as drained
|
|
this.needDrain = false;
|
|
this.emit('drain');
|
|
}
|
|
|
|
cb();
|
|
return;
|
|
}
|
|
|
|
var self = this,
|
|
buffer = new Buffer(this.waiting),
|
|
sliced = 0,
|
|
offset = 0;
|
|
|
|
while (this.waiting > offset && sliced < this.buffer.length) {
|
|
var chunk = this.buffer[sliced++],
|
|
overmatched = false;
|
|
|
|
// Copy chunk into `buffer`
|
|
if (chunk.length > this.waiting - offset) {
|
|
chunk.copy(buffer, offset, 0, this.waiting - offset);
|
|
|
|
this.buffer[--sliced] = chunk.slice(this.waiting - offset);
|
|
this.buffered += this.buffer[sliced].length;
|
|
|
|
overmatched = true;
|
|
} else {
|
|
chunk.copy(buffer, offset);
|
|
}
|
|
|
|
// Move offset and decrease amount of buffered data
|
|
offset += chunk.length;
|
|
this.buffered -= chunk.length;
|
|
|
|
if (overmatched) break;
|
|
}
|
|
|
|
// Remove used buffers
|
|
this.buffer = this.buffer.slice(sliced);
|
|
|
|
// Executed parser for buffered data
|
|
this.paused = true;
|
|
var sync = true;
|
|
this.execute(this.state, buffer, function (err, waiting) {
|
|
// Propagate errors
|
|
if (err) {
|
|
// And unpause once execution finished
|
|
self.paused = false;
|
|
|
|
cb();
|
|
return self.emit('error', err);
|
|
}
|
|
|
|
// Set new `waiting`
|
|
self.waiting = waiting;
|
|
|
|
if (sync) {
|
|
utils.nextTick(function() {
|
|
// Unpause right before entering new `_write()` call
|
|
self.paused = false;
|
|
self._write(undefined, null, cb);
|
|
});
|
|
} else {
|
|
// Unpause right before entering new `_write()` call
|
|
self.paused = false;
|
|
self._write(undefined, null, cb);
|
|
}
|
|
});
|
|
sync = false;
|
|
};
|
|
|
|
if (legacy) {
|
|
//
|
|
// ### function write (data, encoding, cb)
|
|
// #### @data {Buffer} chunk of data
|
|
// #### @encoding {Null} encoding
|
|
// #### @cb {Function} callback
|
|
// Legacy method
|
|
//
|
|
Parser.prototype.write = Parser.prototype._write;
|
|
|
|
//
|
|
// ### function end ()
|
|
// Stream's end() implementation
|
|
//
|
|
Parser.prototype.end = function end() {
|
|
this.emit('end');
|
|
};
|
|
}
|
|
|
|
//
|
|
// ### function setVersion (version)
|
|
// #### @version {Number} Protocol version
|
|
// Set protocol version to use
|
|
//
|
|
Parser.prototype.setVersion = function setVersion(version) {
|
|
this.version = version;
|
|
this.emit('version', version);
|
|
this.deflate = spdy.utils.zwrap(this.connection._spdyState.deflate);
|
|
this.inflate = spdy.utils.zwrap(this.connection._spdyState.inflate);
|
|
};
|
|
|
|
//
|
|
// ### function execute (state, data, callback)
|
|
// #### @state {Object} Parser's state
|
|
// #### @data {Buffer} Incoming data
|
|
// #### @callback {Function} continuation callback
|
|
// Parse buffered data
|
|
//
|
|
Parser.prototype.execute = function execute(state, data, callback) {
|
|
if (state.type === 'frame-head') {
|
|
var header = state.header = this.parseHeader(data);
|
|
|
|
if (this.version === null && header.control) {
|
|
if (header.version !== 2 && header.version !== 3) {
|
|
return callback(new Error('Unsupported spdy version: ' +
|
|
header.version));
|
|
}
|
|
this.setVersion(header.version);
|
|
}
|
|
|
|
state.type = 'frame-body';
|
|
callback(null, header.length);
|
|
} else if (state.type === 'frame-body') {
|
|
var self = this;
|
|
|
|
// Data frame
|
|
if (!state.header.control) {
|
|
return onFrame(null, {
|
|
type: 'DATA',
|
|
id: state.header.id,
|
|
fin: (state.header.flags & 0x01) === 0x01,
|
|
compressed: (state.header.flags & 0x02) === 0x02,
|
|
data: data
|
|
});
|
|
} else {
|
|
// Control frame
|
|
this.parseBody(state.header, data, onFrame);
|
|
}
|
|
|
|
function onFrame(err, frame) {
|
|
if (err) return callback(err);
|
|
|
|
self.emit('frame', frame);
|
|
|
|
state.type = 'frame-head';
|
|
callback(null, 8);
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseHeader (data)
|
|
// ### @data {Buffer} incoming data
|
|
// Returns parsed SPDY frame header
|
|
//
|
|
Parser.prototype.parseHeader = function parseHeader(data) {
|
|
var header = {
|
|
control: (data.readUInt8(0) & 0x80) === 0x80 ? true : false,
|
|
version: null,
|
|
type: null,
|
|
id: null,
|
|
flags: data.readUInt8(4),
|
|
length: data.readUInt32BE(4) & 0x00ffffff
|
|
};
|
|
|
|
if (header.control) {
|
|
header.version = data.readUInt16BE(0) & 0x7fff;
|
|
header.type = data.readUInt16BE(2);
|
|
} else {
|
|
header.id = data.readUInt32BE(0) & 0x7fffffff;
|
|
}
|
|
|
|
return header;
|
|
};
|
|
|
|
|
|
//
|
|
// ### function execute (header, body, callback)
|
|
// #### @header {Object} Frame headers
|
|
// #### @body {Buffer} Frame's body
|
|
// #### @callback {Function} Continuation callback
|
|
// Parse frame (decompress data and create streams)
|
|
//
|
|
Parser.prototype.parseBody = function parseBody(header, body, callback) {
|
|
// SYN_STREAM or SYN_REPLY
|
|
if (header.type === 0x01 || header.type === 0x02)
|
|
this.parseSynHead(header.type, header.flags, body, callback);
|
|
// RST_STREAM
|
|
else if (header.type === 0x03)
|
|
this.parseRst(body, callback);
|
|
// SETTINGS
|
|
else if (header.type === 0x04)
|
|
this.parseSettings(body, callback);
|
|
else if (header.type === 0x05)
|
|
callback(null, { type: 'NOOP' });
|
|
// PING
|
|
else if (header.type === 0x06)
|
|
this.parsePing(body, callback);
|
|
// GOAWAY
|
|
else if (header.type === 0x07)
|
|
this.parseGoaway(body, callback);
|
|
// HEADERS
|
|
else if (header.type === 0x08)
|
|
this.parseHeaders(body, callback);
|
|
// WINDOW_UPDATE
|
|
else if (header.type === 0x09)
|
|
this.parseWindowUpdate(body, callback);
|
|
// X-FORWARDED
|
|
else if (header.type === 0xf000)
|
|
this.parseXForwarded(body, callback);
|
|
else
|
|
callback(null, { type: 'unknown: ' + header.type, body: body });
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseSynHead (type, flags, data)
|
|
// #### @type {Number} Frame type
|
|
// #### @flags {Number} Frame flags
|
|
// #### @data {Buffer} input data
|
|
// Returns parsed syn_* frame's head
|
|
//
|
|
Parser.prototype.parseSynHead = function parseSynHead(type,
|
|
flags,
|
|
data,
|
|
callback) {
|
|
var stream = type === 0x01;
|
|
var offset = stream ? 10 : this.version === 2 ? 6 : 4;
|
|
|
|
if (data.length < offset)
|
|
return callback(new Error('SynHead OOB'));
|
|
|
|
var kvs = data.slice(offset);
|
|
this.parseKVs(kvs, function(err, headers) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
if (stream === 'SYN_STREAM' &&
|
|
(!headers.method || !(headers.path || headers.url))) {
|
|
return callback(new Error('Missing `:method` and/or `:path` header'));
|
|
}
|
|
|
|
callback(null, {
|
|
type: stream ? 'SYN_STREAM' : 'SYN_REPLY',
|
|
id: data.readUInt32BE(0, true) & 0x7fffffff,
|
|
associated: stream ? data.readUInt32BE(4, true) & 0x7fffffff : 0,
|
|
priority: stream ? data[8] >> 5 : 0,
|
|
fin: (flags & 0x01) === 0x01,
|
|
unidir: (flags & 0x02) === 0x02,
|
|
headers: headers,
|
|
url: headers.path || headers.url || ''
|
|
});
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseHeaders (data, callback)
|
|
// #### @data {Buffer} input data
|
|
// #### @callback {Function} continuation
|
|
// Parse HEADERS
|
|
//
|
|
Parser.prototype.parseHeaders = function parseHeaders(data, callback) {
|
|
var offset = this.version === 2 ? 6 : 4;
|
|
if (data.length < offset)
|
|
return callback(new Error('HEADERS OOB'));
|
|
|
|
var streamId = data.readUInt32BE(0, true) & 0x7fffffff;
|
|
|
|
this.parseKVs(data.slice(offset), function(err, headers) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
callback(null, {
|
|
type: 'HEADERS',
|
|
id: streamId,
|
|
headers: headers
|
|
});
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseKVs (pairs, callback)
|
|
// #### @pairs {Buffer} header pairs
|
|
// #### @callback {Function} continuation
|
|
// Returns hashmap of parsed headers
|
|
//
|
|
Parser.prototype.parseKVs = function parseKVs(pairs, callback) {
|
|
var self = this;
|
|
this.inflate(pairs, function(err, chunks, length) {
|
|
if (err)
|
|
return callback(err);
|
|
|
|
var pairs = Buffer.concat(chunks, length);
|
|
|
|
var size = self.version === 2 ? 2 : 4;
|
|
if (pairs.length < size)
|
|
return callback(new Error('KV OOB'));
|
|
|
|
var count = size === 2 ? pairs.readUInt16BE(0, true) :
|
|
pairs.readUInt32BE(0, true),
|
|
headers = {};
|
|
|
|
pairs = pairs.slice(size);
|
|
|
|
function readString() {
|
|
if (pairs.length < size)
|
|
return null;
|
|
var len = size === 2 ? pairs.readUInt16BE(0, true) :
|
|
pairs.readUInt32BE(0, true);
|
|
|
|
if (pairs.length < size + len) {
|
|
return null;
|
|
}
|
|
var value = pairs.slice(size, size + len);
|
|
|
|
pairs = pairs.slice(size + len);
|
|
|
|
return value.toString();
|
|
}
|
|
|
|
while(count > 0) {
|
|
var key = readString(),
|
|
value = readString();
|
|
|
|
if (key === null || value === null)
|
|
return callback(new Error('Headers OOB'));
|
|
|
|
if (self.version >= 3)
|
|
headers[key.replace(/^:/, '')] = value;
|
|
else
|
|
headers[key] = value;
|
|
count--;
|
|
}
|
|
|
|
callback(null, headers);
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseRst (data, callback)
|
|
// #### @data {Buffer} input data
|
|
// #### @callback {Function} continuation
|
|
// Parse RST
|
|
//
|
|
Parser.prototype.parseRst = function parseRst(data, callback) {
|
|
if (data.length < 8)
|
|
return callback(new Error('RST OOB'));
|
|
|
|
callback(null, {
|
|
type: 'RST_STREAM',
|
|
id: data.readUInt32BE(0, true) & 0x7fffffff,
|
|
status: data.readUInt32BE(4, true),
|
|
extra: data.length > 8 ? data.slice(8) : null
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseSettings (data, callback)
|
|
// #### @data {Buffer} input data
|
|
// #### @callback {Function} continuation
|
|
// Parse SETTINGS
|
|
//
|
|
Parser.prototype.parseSettings = function parseSettings(data, callback) {
|
|
if (data.length < 4)
|
|
return callback(new Error('SETTINGS OOB'));
|
|
|
|
var settings = {},
|
|
number = data.readUInt32BE(0, true),
|
|
idMap = {
|
|
1: 'upload_bandwidth',
|
|
2: 'download_bandwidth',
|
|
3: 'round_trip_time',
|
|
4: 'max_concurrent_streams',
|
|
5: 'current_cwnd',
|
|
6: 'download_retrans_rate',
|
|
7: 'initial_window_size',
|
|
8: 'client_certificate_vector_size'
|
|
};
|
|
|
|
if (data.length < 4 + number * 8)
|
|
return callback(new Error('SETTINGS OOB#2'));
|
|
|
|
for (var i = 0; i < number; i++) {
|
|
var id = (this.version === 2 ? data.readUInt32LE(4 + i * 8, true) :
|
|
data.readUInt32BE(4 + i * 8, true)),
|
|
flags = (id >> 24) & 0xff;
|
|
id = id & 0xffffff;
|
|
|
|
var name = idMap[id];
|
|
|
|
settings[id] = settings[name] = {
|
|
persist: !!(flags & 0x1),
|
|
persisted: !!(flags & 0x2),
|
|
value: data.readUInt32BE(8 + (i*8), true)
|
|
};
|
|
}
|
|
|
|
callback(null, {
|
|
type: 'SETTINGS',
|
|
settings: settings
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseGoaway (data, callback)
|
|
// #### @data {Buffer} input data
|
|
// #### @callback {Function} continuation
|
|
// Parse PING
|
|
//
|
|
Parser.prototype.parsePing = function parsePing(body, callback) {
|
|
if (body.length < 4)
|
|
return callback(new Error('PING OOB'));
|
|
callback(null, { type: 'PING', pingId: body.readUInt32BE(0, true) });
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseGoaway (data, callback)
|
|
// #### @data {Buffer} input data
|
|
// #### @callback {Function} continuation
|
|
// Parse GOAWAY
|
|
//
|
|
Parser.prototype.parseGoaway = function parseGoaway(data, callback) {
|
|
if (data.length < 4)
|
|
return callback(new Error('GOAWAY OOB'));
|
|
|
|
callback(null, {
|
|
type: 'GOAWAY',
|
|
lastId: data.readUInt32BE(0, true) & 0x7fffffff
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseWindowUpdate (data, callback)
|
|
// #### @data {Buffer} input data
|
|
// #### @callback {Function} continuation
|
|
// Parse WINDOW_UPDATE
|
|
//
|
|
Parser.prototype.parseWindowUpdate = function parseWindowUpdate(data, callback) {
|
|
if (data.length < 8)
|
|
return callback(new Error('WINDOW_UPDATE OOB'));
|
|
|
|
callback(null, {
|
|
type: 'WINDOW_UPDATE',
|
|
id: data.readUInt32BE(0, true) & 0x7fffffff,
|
|
delta: data.readUInt32BE(4, true) & 0x7fffffff
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// ### function parseXForwarded (data, callback)
|
|
// #### @data {Buffer} input data
|
|
// #### @callback {Function} continuation
|
|
// Parse X_FORWARDED
|
|
//
|
|
Parser.prototype.parseXForwarded = function parseXForwarded(data, callback) {
|
|
if (data.length < 4)
|
|
return callback(new Error('X_FORWARDED OOB'));
|
|
|
|
var len = data.readUInt32BE(0, true);
|
|
if (len + 4 > data.length)
|
|
return callback(new Error('X_FORWARDED host length OOB'));
|
|
|
|
callback(null, {
|
|
type: 'X_FORWARDED',
|
|
host: data.slice(4, 4 + len).toString()
|
|
});
|
|
};
|