| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485 |
- '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')
- var OffsetBuffer = require('obuf')
- function Parser (options) {
- base.Parser.call(this, options)
- this.isServer = options.isServer
- this.waiting = constants.FRAME_HEADER_SIZE
- this.state = 'frame-head'
- this.pendingHeader = null
- }
- util.inherits(Parser, base.Parser)
- parser.create = function create (options) {
- return new Parser(options)
- }
- Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
- // http2-only
- }
- Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
- // http2-only
- }
- // Only for testing
- Parser.prototype.skipPreface = function skipPreface () {
- }
- Parser.prototype.execute = function execute (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.waiting = constants.FRAME_HEADER_SIZE
- self.partial = false
- callback(null, frame)
- })
- }
- Parser.prototype.executePartial = function executePartial (buffer, callback) {
- var header = this.pendingHeader
- if (this.window) {
- this.window.recv.update(-buffer.size)
- }
- // DATA frame
- callback(null, {
- type: 'DATA',
- id: header.id,
- // Partial DATA can't be FIN
- fin: false,
- data: buffer.take(buffer.size)
- })
- }
- Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
- var header = {
- control: (buffer.peekUInt8() & 0x80) === 0x80,
- version: null,
- type: null,
- id: null,
- flags: null,
- length: null
- }
- if (header.control) {
- header.version = buffer.readUInt16BE() & 0x7fff
- header.type = buffer.readUInt16BE()
- } else {
- header.id = buffer.readUInt32BE(0) & 0x7fffffff
- }
- header.flags = buffer.readUInt8()
- header.length = buffer.readUInt24BE()
- if (this.version === null && header.control) {
- // TODO(indutny): do ProtocolError here and in the rest of errors
- if (header.version !== 2 && header.version !== 3) {
- return callback(new Error('Unsupported SPDY version: ' + header.version))
- }
- this.setVersion(header.version)
- }
- this.state = 'frame-body'
- this.waiting = header.length
- this.pendingHeader = header
- this.partial = !header.control
- callback(null, null)
- }
- Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
- // Data frame
- if (!header.control) {
- // Count received bytes
- if (this.window) {
- this.window.recv.update(-buffer.size)
- }
- // No support for compressed DATA
- if ((header.flags & constants.flags.FLAG_COMPRESSED) !== 0) {
- return callback(new Error('DATA compression not supported'))
- }
- if (header.id === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for DATA'))
- }
- return callback(null, {
- type: 'DATA',
- id: header.id,
- fin: (header.flags & constants.flags.FLAG_FIN) !== 0,
- data: buffer.take(buffer.size)
- })
- }
- if (header.type === 0x01 || header.type === 0x02) { // SYN_STREAM or SYN_REPLY
- this.onSynHeadFrame(header.type, header.flags, buffer, callback)
- } else if (header.type === 0x03) { // RST_STREAM
- this.onRSTFrame(buffer, callback)
- } else if (header.type === 0x04) { // SETTINGS
- this.onSettingsFrame(buffer, callback)
- } else if (header.type === 0x05) {
- callback(null, { type: 'NOOP' })
- } else if (header.type === 0x06) { // PING
- this.onPingFrame(buffer, callback)
- } else if (header.type === 0x07) { // GOAWAY
- this.onGoawayFrame(buffer, callback)
- } else if (header.type === 0x08) { // HEADERS
- this.onHeaderFrames(buffer, callback)
- } else if (header.type === 0x09) { // WINDOW_UPDATE
- this.onWindowUpdateFrame(buffer, callback)
- } else if (header.type === 0xf000) { // X-FORWARDED
- this.onXForwardedFrame(buffer, callback)
- } else {
- callback(null, { type: 'unknown: ' + header.type })
- }
- }
- Parser.prototype._filterHeader = function _filterHeader (headers, name) {
- var res = {}
- var keys = Object.keys(headers)
- for (var i = 0; i < keys.length; i++) {
- var key = keys[i]
- if (key !== name) {
- res[key] = headers[key]
- }
- }
- return res
- }
- Parser.prototype.onSynHeadFrame = function onSynHeadFrame (type,
- flags,
- body,
- callback) {
- var self = this
- var stream = type === 0x01
- var offset = stream ? 10 : this.version === 2 ? 6 : 4
- if (!body.has(offset)) {
- return callback(new Error('SynHead OOB'))
- }
- var head = body.clone(offset)
- body.skip(offset)
- this.parseKVs(body, function (err, headers) {
- if (err) {
- return callback(err)
- }
- if (stream &&
- (!headers[':method'] || !headers[':path'])) {
- return callback(new Error('Missing `:method` and/or `:path` header'))
- }
- var id = head.readUInt32BE() & 0x7fffffff
- if (id === 0) {
- return callback(self.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for HEADERS'))
- }
- var associated = stream ? head.readUInt32BE() & 0x7fffffff : 0
- var priority = stream
- ? head.readUInt8() >> 5
- : utils.weightToPriority(constants.DEFAULT_WEIGHT)
- var fin = (flags & constants.flags.FLAG_FIN) !== 0
- var unidir = (flags & constants.flags.FLAG_UNIDIRECTIONAL) !== 0
- var path = headers[':path']
- var isPush = stream && associated !== 0
- var weight = utils.priorityToWeight(priority)
- var priorityInfo = {
- weight: weight,
- exclusive: false,
- parent: 0
- }
- if (!isPush) {
- callback(null, {
- type: 'HEADERS',
- id: id,
- priority: priorityInfo,
- fin: fin,
- writable: !unidir,
- headers: headers,
- path: path
- })
- return
- }
- if (stream && !headers[':status']) {
- return callback(new Error('Missing `:status` header'))
- }
- var filteredHeaders = self._filterHeader(headers, ':status')
- callback(null, [ {
- type: 'PUSH_PROMISE',
- id: associated,
- fin: false,
- promisedId: id,
- headers: filteredHeaders,
- path: path
- }, {
- type: 'HEADERS',
- id: id,
- fin: fin,
- priority: priorityInfo,
- writable: true,
- path: undefined,
- headers: {
- ':status': headers[':status']
- }
- }])
- })
- }
- Parser.prototype.onHeaderFrames = function onHeaderFrames (body, callback) {
- var offset = this.version === 2 ? 6 : 4
- if (!body.has(offset)) {
- return callback(new Error('HEADERS OOB'))
- }
- var streamId = body.readUInt32BE() & 0x7fffffff
- if (this.version === 2) { body.skip(2) }
- this.parseKVs(body, function (err, headers) {
- if (err) { return callback(err) }
- callback(null, {
- type: 'HEADERS',
- priority: {
- parent: 0,
- exclusive: false,
- weight: constants.DEFAULT_WEIGHT
- },
- id: streamId,
- fin: false,
- writable: true,
- path: undefined,
- headers: headers
- })
- })
- }
- Parser.prototype.parseKVs = function parseKVs (buffer, callback) {
- var self = this
- this.decompress.write(buffer.toChunks(), function (err, chunks) {
- if (err) {
- return callback(err)
- }
- var buffer = new OffsetBuffer()
- for (var i = 0; i < chunks.length; i++) {
- buffer.push(chunks[i])
- }
- var size = self.version === 2 ? 2 : 4
- if (!buffer.has(size)) { return callback(new Error('KV OOB')) }
- var count = self.version === 2
- ? buffer.readUInt16BE()
- : buffer.readUInt32BE()
- var headers = {}
- function readString () {
- if (!buffer.has(size)) { return null }
- var len = self.version === 2
- ? buffer.readUInt16BE()
- : buffer.readUInt32BE()
- if (!buffer.has(len)) { return null }
- var value = buffer.take(len)
- return value.toString()
- }
- while (count > 0) {
- var key = readString()
- var value = readString()
- if (key === null || value === null) {
- return callback(new Error('Headers OOB'))
- }
- if (self.version < 3) {
- var isInternal = /^(method|version|url|host|scheme|status)$/.test(key)
- if (key === 'url') {
- key = 'path'
- }
- if (isInternal) {
- key = ':' + key
- }
- }
- // Compatibility with HTTP2
- if (key === ':status') {
- value = value.split(/ /g, 2)[0]
- }
- count--
- if (key === ':host') {
- key = ':authority'
- }
- // Skip version, not present in HTTP2
- if (key === ':version') {
- continue
- }
- value = value.split(/\0/g)
- for (var j = 0; j < value.length; j++) {
- utils.addHeaderLine(key, value[j], headers)
- }
- }
- callback(null, headers)
- })
- }
- Parser.prototype.onRSTFrame = function onRSTFrame (body, callback) {
- if (!body.has(8)) { return callback(new Error('RST OOB')) }
- var frame = {
- type: 'RST',
- id: body.readUInt32BE() & 0x7fffffff,
- code: constants.errorByCode[body.readUInt32BE()]
- }
- if (frame.id === 0) {
- return callback(this.error(constants.error.PROTOCOL_ERROR,
- 'Invalid stream id for RST'))
- }
- if (body.size !== 0) {
- frame.extra = body.take(body.size)
- }
- callback(null, frame)
- }
- Parser.prototype.onSettingsFrame = function onSettingsFrame (body, callback) {
- if (!body.has(4)) {
- return callback(new Error('SETTINGS OOB'))
- }
- var settings = {}
- var number = body.readUInt32BE()
- var 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 (!body.has(number * 8)) {
- return callback(new Error('SETTINGS OOB#2'))
- }
- for (var i = 0; i < number; i++) {
- var id = this.version === 2
- ? body.readUInt32LE()
- : body.readUInt32BE()
- var flags = (id >> 24) & 0xff
- id = id & 0xffffff
- // Skip persisted settings
- if (flags & 0x2) { continue }
- var name = idMap[id]
- settings[name] = body.readUInt32BE()
- }
- callback(null, {
- type: 'SETTINGS',
- settings: settings
- })
- }
- Parser.prototype.onPingFrame = function onPingFrame (body, callback) {
- if (!body.has(4)) {
- return callback(new Error('PING OOB'))
- }
- var isServer = this.isServer
- var opaque = body.clone(body.size).take(body.size)
- var id = body.readUInt32BE()
- var ack = isServer ? (id % 2 === 0) : (id % 2 === 1)
- callback(null, { type: 'PING', opaque: opaque, ack: ack })
- }
- Parser.prototype.onGoawayFrame = function onGoawayFrame (body, callback) {
- if (!body.has(8)) {
- return callback(new Error('GOAWAY OOB'))
- }
- callback(null, {
- type: 'GOAWAY',
- lastId: body.readUInt32BE() & 0x7fffffff,
- code: constants.goawayByCode[body.readUInt32BE()]
- })
- }
- Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (body,
- callback) {
- if (!body.has(8)) {
- return callback(new Error('WINDOW_UPDATE OOB'))
- }
- callback(null, {
- type: 'WINDOW_UPDATE',
- id: body.readUInt32BE() & 0x7fffffff,
- delta: body.readInt32BE()
- })
- }
- Parser.prototype.onXForwardedFrame = function onXForwardedFrame (body,
- callback) {
- if (!body.has(4)) {
- return callback(new Error('X_FORWARDED OOB'))
- }
- var len = body.readUInt32BE()
- if (!body.has(len)) { return callback(new Error('X_FORWARDED host length OOB')) }
- callback(null, {
- type: 'X_FORWARDED_FOR',
- host: body.take(len).toString()
- })
- }
|