| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578 |
- 'use strict'
- var parser = exports
- var transport = require('../../../spdy-transport')
- var base = transport.protocol.base
- var utils = base.utils
- var constants = require('./').constants
- var assert = require('assert')
- var util = require('util')
- function Parser (options) {
- base.Parser.call(this, options)
- this.isServer = options.isServer
- this.waiting = constants.PREFACE_SIZE
- this.state = 'preface'
- this.pendingHeader = null
- // Header Block queue
- this._lastHeaderBlock = null
- this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
- this.maxHeaderListSize = constants.DEFAULT_MAX_HEADER_LIST_SIZE
- }
- util.inherits(Parser, base.Parser)
- parser.create = function create (options) {
- return new Parser(options)
- }
- Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
- this.maxFrameSize = size
- }
- Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
- this.maxHeaderListSize = size
- }
- // Only for testing
- Parser.prototype.skipPreface = function skipPreface () {
- // Just some number bigger than 3.1, doesn't really matter for HTTP2
- this.setVersion(4)
- // Parse frame header!
- this.state = 'frame-head'
- this.waiting = constants.FRAME_HEADER_SIZE
- }
- Parser.prototype.execute = function execute (buffer, callback) {
- if (this.state === 'preface') { return this.onPreface(buffer, callback) }
- if (this.state === 'frame-head') {
- return this.onFrameHead(buffer, callback)
- }
- assert(this.state === 'frame-body' && this.pendingHeader !== null)
- var self = this
- var header = this.pendingHeader
- this.pendingHeader = null
- this.onFrameBody(header, buffer, function (err, frame) {
- if (err) {
- return callback(err)
- }
- self.state = 'frame-head'
- self.partial = false
- self.waiting = constants.FRAME_HEADER_SIZE
- callback(null, frame)
- })
- }
- Parser.prototype.executePartial = function executePartial (buffer, callback) {
- var header = this.pendingHeader
- assert.strictEqual(header.flags & constants.flags.PADDED, 0)
- if (this.window) { this.window.recv.update(-buffer.size) }
- callback(null, {
- type: 'DATA',
- id: header.id,
- // Partial DATA can't be FIN
- fin: false,
- data: buffer.take(buffer.size)
- })
- }
- Parser.prototype.onPreface = function onPreface (buffer, callback) {
- if (buffer.take(buffer.size).toString() !== constants.PREFACE) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid preface'))
- }
- this.skipPreface()
- callback(null, null)
- }
- Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
- var header = {
- length: buffer.readUInt24BE(),
- control: true,
- type: buffer.readUInt8(),
- flags: buffer.readUInt8(),
- id: buffer.readUInt32BE() & 0x7fffffff
- }
- if (header.length > this.maxFrameSize) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'Frame length OOB'))
- }
- header.control = header.type !== constants.frameType.DATA
- this.state = 'frame-body'
- this.pendingHeader = header
- this.waiting = header.length
- this.partial = !header.control
- // TODO(indutny): eventually support partial padded DATA
- if (this.partial) {
- this.partial = (header.flags & constants.flags.PADDED) === 0
- }
- callback(null, null)
- }
- Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
- var frameType = constants.frameType
- if (header.type === frameType.DATA) {
- this.onDataFrame(header, buffer, callback)
- } else if (header.type === frameType.HEADERS) {
- this.onHeadersFrame(header, buffer, callback)
- } else if (header.type === frameType.CONTINUATION) {
- this.onContinuationFrame(header, buffer, callback)
- } else if (header.type === frameType.WINDOW_UPDATE) {
- this.onWindowUpdateFrame(header, buffer, callback)
- } else if (header.type === frameType.RST_STREAM) {
- this.onRSTFrame(header, buffer, callback)
- } else if (header.type === frameType.SETTINGS) {
- this.onSettingsFrame(header, buffer, callback)
- } else if (header.type === frameType.PUSH_PROMISE) {
- this.onPushPromiseFrame(header, buffer, callback)
- } else if (header.type === frameType.PING) {
- this.onPingFrame(header, buffer, callback)
- } else if (header.type === frameType.GOAWAY) {
- this.onGoawayFrame(header, buffer, callback)
- } else if (header.type === frameType.PRIORITY) {
- this.onPriorityFrame(header, buffer, callback)
- } else if (header.type === frameType.X_FORWARDED_FOR) {
- this.onXForwardedFrame(header, buffer, callback)
- } else {
- this.onUnknownFrame(header, buffer, callback)
- }
- }
- Parser.prototype.onUnknownFrame = function onUnknownFrame (header, buffer, callback) {
- if (this._lastHeaderBlock !== null) {
- callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Received unknown frame in the middle of a header block'))
- return
- }
- callback(null, { type: 'unknown: ' + header.type })
- }
- Parser.prototype.unpadData = function unpadData (header, body, callback) {
- var isPadded = (header.flags & constants.flags.PADDED) !== 0
- if (!isPadded) { return callback(null, body) }
- if (!body.has(1)) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'Not enough space for padding'))
- }
- var pad = body.readUInt8()
- if (!body.has(pad)) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid padding size'))
- }
- var contents = body.clone(body.size - pad)
- body.skip(body.size)
- callback(null, contents)
- }
- Parser.prototype.onDataFrame = function onDataFrame (header, body, callback) {
- var isEndStream = (header.flags & constants.flags.END_STREAM) !== 0
- if (header.id === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Received DATA frame with stream=0'))
- }
- // Count received bytes
- if (this.window) {
- this.window.recv.update(-body.size)
- }
- this.unpadData(header, body, function (err, data) {
- if (err) {
- return callback(err)
- }
- callback(null, {
- type: 'DATA',
- id: header.id,
- fin: isEndStream,
- data: data.take(data.size)
- })
- })
- }
- Parser.prototype.initHeaderBlock = function initHeaderBlock (header,
- frame,
- block,
- callback) {
- if (this._lastHeaderBlock) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Duplicate Stream ID'))
- }
- this._lastHeaderBlock = {
- id: header.id,
- frame: frame,
- queue: [],
- size: 0
- }
- this.queueHeaderBlock(header, block, callback)
- }
- Parser.prototype.queueHeaderBlock = function queueHeaderBlock (header,
- block,
- callback) {
- var self = this
- var item = this._lastHeaderBlock
- if (!this._lastHeaderBlock || item.id !== header.id) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'No matching stream for continuation'))
- }
- var fin = (header.flags & constants.flags.END_HEADERS) !== 0
- var chunks = block.toChunks()
- for (var i = 0; i < chunks.length; i++) {
- var chunk = chunks[i]
- item.queue.push(chunk)
- item.size += chunk.length
- }
- if (item.size >= self.maxHeaderListSize) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Compressed header list is too large'))
- }
- if (!fin) { return callback(null, null) }
- this._lastHeaderBlock = null
- this.decompress.write(item.queue, function (err, chunks) {
- if (err) {
- return callback(self.error(constants.error.COMPRESSION_ERROR,
- err.message))
- }
- var headers = {}
- var size = 0
- for (var i = 0; i < chunks.length; i++) {
- var header = chunks[i]
- size += header.name.length + header.value.length + 32
- if (size >= self.maxHeaderListSize) {
- return callback(self.error(constants.error.PROTOCOL_ERROR,
- 'Header list is too large'))
- }
- if (/[A-Z]/.test(header.name)) {
- return callback(self.error(constants.error.PROTOCOL_ERROR,
- 'Header name must be lowercase'))
- }
- utils.addHeaderLine(header.name, header.value, headers)
- }
- item.frame.headers = headers
- item.frame.path = headers[':path']
- callback(null, item.frame)
- })
- }
- Parser.prototype.onHeadersFrame = function onHeadersFrame (header,
- body,
- callback) {
- var self = this
- if (header.id === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for HEADERS'))
- }
- this.unpadData(header, body, function (err, data) {
- if (err) { return callback(err) }
- var isPriority = (header.flags & constants.flags.PRIORITY) !== 0
- if (!data.has(isPriority ? 5 : 0)) {
- return callback(self.error(constants.error.FRAME_SIZE_ERROR,
- 'Not enough data for HEADERS'))
- }
- var exclusive = false
- var dependency = 0
- var weight = constants.DEFAULT_WEIGHT
- if (isPriority) {
- dependency = data.readUInt32BE()
- exclusive = (dependency & 0x80000000) !== 0
- dependency &= 0x7fffffff
- // Weight's range is [1, 256]
- weight = data.readUInt8() + 1
- }
- if (dependency === header.id) {
- return callback(self.error(constants.error.PROTOCOL_ERROR,
- 'Stream can\'t dependend on itself'))
- }
- var streamInfo = {
- type: 'HEADERS',
- id: header.id,
- priority: {
- parent: dependency,
- exclusive: exclusive,
- weight: weight
- },
- fin: (header.flags & constants.flags.END_STREAM) !== 0,
- writable: true,
- headers: null,
- path: null
- }
- self.initHeaderBlock(header, streamInfo, data, callback)
- })
- }
- Parser.prototype.onContinuationFrame = function onContinuationFrame (header,
- body,
- callback) {
- this.queueHeaderBlock(header, body, callback)
- }
- Parser.prototype.onRSTFrame = function onRSTFrame (header, body, callback) {
- if (body.size !== 4) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'RST_STREAM length not 4'))
- }
- if (header.id === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for RST_STREAM'))
- }
- callback(null, {
- type: 'RST',
- id: header.id,
- code: constants.errorByCode[body.readUInt32BE()]
- })
- }
- Parser.prototype._validateSettings = function _validateSettings (settings) {
- if (settings['enable_push'] !== undefined &&
- settings['enable_push'] !== 0 &&
- settings['enable_push'] !== 1) {
- return this.error(constants.error.PROTOCOL_ERROR,
- 'SETTINGS_ENABLE_PUSH must be 0 or 1')
- }
- if (settings['initial_window_size'] !== undefined &&
- (settings['initial_window_size'] > constants.MAX_INITIAL_WINDOW_SIZE ||
- settings['initial_window_size'] < 0)) {
- return this.error(constants.error.FLOW_CONTROL_ERROR,
- 'SETTINGS_INITIAL_WINDOW_SIZE is OOB')
- }
- if (settings['max_frame_size'] !== undefined &&
- (settings['max_frame_size'] > constants.ABSOLUTE_MAX_FRAME_SIZE ||
- settings['max_frame_size'] < constants.INITIAL_MAX_FRAME_SIZE)) {
- return this.error(constants.error.PROTOCOL_ERROR,
- 'SETTINGS_MAX_FRAME_SIZE is OOB')
- }
- return undefined
- }
- Parser.prototype.onSettingsFrame = function onSettingsFrame (header,
- body,
- callback) {
- if (header.id !== 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for SETTINGS'))
- }
- var isAck = (header.flags & constants.flags.ACK) !== 0
- if (isAck && body.size !== 0) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'SETTINGS with ACK and non-zero length'))
- }
- if (isAck) {
- return callback(null, { type: 'ACK_SETTINGS' })
- }
- if (body.size % 6 !== 0) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'SETTINGS length not multiple of 6'))
- }
- var settings = {}
- while (!body.isEmpty()) {
- var id = body.readUInt16BE()
- var value = body.readUInt32BE()
- var name = constants.settingsIndex[id]
- if (name) {
- settings[name] = value
- }
- }
- var err = this._validateSettings(settings)
- if (err !== undefined) {
- return callback(err)
- }
- callback(null, {
- type: 'SETTINGS',
- settings: settings
- })
- }
- Parser.prototype.onPushPromiseFrame = function onPushPromiseFrame (header,
- body,
- callback) {
- if (header.id === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for PUSH_PROMISE'))
- }
- var self = this
- this.unpadData(header, body, function (err, data) {
- if (err) {
- return callback(err)
- }
- if (!data.has(4)) {
- return callback(self.error(constants.error.FRAME_SIZE_ERROR,
- 'PUSH_PROMISE length less than 4'))
- }
- var streamInfo = {
- type: 'PUSH_PROMISE',
- id: header.id,
- fin: false,
- promisedId: data.readUInt32BE() & 0x7fffffff,
- headers: null,
- path: null
- }
- self.initHeaderBlock(header, streamInfo, data, callback)
- })
- }
- Parser.prototype.onPingFrame = function onPingFrame (header, body, callback) {
- if (body.size !== 8) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'PING length != 8'))
- }
- if (header.id !== 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for PING'))
- }
- var ack = (header.flags & constants.flags.ACK) !== 0
- callback(null, { type: 'PING', opaque: body.take(body.size), ack: ack })
- }
- Parser.prototype.onGoawayFrame = function onGoawayFrame (header,
- body,
- callback) {
- if (!body.has(8)) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'GOAWAY length < 8'))
- }
- if (header.id !== 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for GOAWAY'))
- }
- var frame = {
- type: 'GOAWAY',
- lastId: body.readUInt32BE(),
- code: constants.goawayByCode[body.readUInt32BE()]
- }
- if (body.size !== 0) { frame.debug = body.take(body.size) }
- callback(null, frame)
- }
- Parser.prototype.onPriorityFrame = function onPriorityFrame (header,
- body,
- callback) {
- if (body.size !== 5) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'PRIORITY length != 5'))
- }
- if (header.id === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for PRIORITY'))
- }
- var dependency = body.readUInt32BE()
- // Again the range is from 1 to 256
- var weight = body.readUInt8() + 1
- if (dependency === header.id) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Stream can\'t dependend on itself'))
- }
- callback(null, {
- type: 'PRIORITY',
- id: header.id,
- priority: {
- exclusive: (dependency & 0x80000000) !== 0,
- parent: dependency & 0x7fffffff,
- weight: weight
- }
- })
- }
- Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (header,
- body,
- callback) {
- if (body.size !== 4) {
- return callback(this.error(constants.error.FRAME_SIZE_ERROR,
- 'WINDOW_UPDATE length != 4'))
- }
- var delta = body.readInt32BE()
- if (delta === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'WINDOW_UPDATE delta == 0'))
- }
- callback(null, {
- type: 'WINDOW_UPDATE',
- id: header.id,
- delta: delta
- })
- }
- Parser.prototype.onXForwardedFrame = function onXForwardedFrame (header,
- body,
- callback) {
- callback(null, {
- type: 'X_FORWARDED_FOR',
- host: body.take(body.size).toString()
- })
- }
|