215ba947c51ff32987b859f5a3b10743dbbb21245666069724ba633ff231adb68e7aa20e4593903b323f5ae88e6590e031d220b2a781e9c75cf79444640720 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. 'use strict'
  2. var parser = exports
  3. var transport = require('../../../spdy-transport')
  4. var base = transport.protocol.base
  5. var utils = base.utils
  6. var constants = require('./').constants
  7. var assert = require('assert')
  8. var util = require('util')
  9. function Parser (options) {
  10. base.Parser.call(this, options)
  11. this.isServer = options.isServer
  12. this.waiting = constants.PREFACE_SIZE
  13. this.state = 'preface'
  14. this.pendingHeader = null
  15. // Header Block queue
  16. this._lastHeaderBlock = null
  17. this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
  18. this.maxHeaderListSize = constants.DEFAULT_MAX_HEADER_LIST_SIZE
  19. }
  20. util.inherits(Parser, base.Parser)
  21. parser.create = function create (options) {
  22. return new Parser(options)
  23. }
  24. Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
  25. this.maxFrameSize = size
  26. }
  27. Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
  28. this.maxHeaderListSize = size
  29. }
  30. // Only for testing
  31. Parser.prototype.skipPreface = function skipPreface () {
  32. // Just some number bigger than 3.1, doesn't really matter for HTTP2
  33. this.setVersion(4)
  34. // Parse frame header!
  35. this.state = 'frame-head'
  36. this.waiting = constants.FRAME_HEADER_SIZE
  37. }
  38. Parser.prototype.execute = function execute (buffer, callback) {
  39. if (this.state === 'preface') { return this.onPreface(buffer, callback) }
  40. if (this.state === 'frame-head') {
  41. return this.onFrameHead(buffer, callback)
  42. }
  43. assert(this.state === 'frame-body' && this.pendingHeader !== null)
  44. var self = this
  45. var header = this.pendingHeader
  46. this.pendingHeader = null
  47. this.onFrameBody(header, buffer, function (err, frame) {
  48. if (err) {
  49. return callback(err)
  50. }
  51. self.state = 'frame-head'
  52. self.partial = false
  53. self.waiting = constants.FRAME_HEADER_SIZE
  54. callback(null, frame)
  55. })
  56. }
  57. Parser.prototype.executePartial = function executePartial (buffer, callback) {
  58. var header = this.pendingHeader
  59. assert.strictEqual(header.flags & constants.flags.PADDED, 0)
  60. if (this.window) { this.window.recv.update(-buffer.size) }
  61. callback(null, {
  62. type: 'DATA',
  63. id: header.id,
  64. // Partial DATA can't be FIN
  65. fin: false,
  66. data: buffer.take(buffer.size)
  67. })
  68. }
  69. Parser.prototype.onPreface = function onPreface (buffer, callback) {
  70. if (buffer.take(buffer.size).toString() !== constants.PREFACE) {
  71. return callback(this.error(constants.error.PROTOCOL_ERROR,
  72. 'Invalid preface'))
  73. }
  74. this.skipPreface()
  75. callback(null, null)
  76. }
  77. Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
  78. var header = {
  79. length: buffer.readUInt24BE(),
  80. control: true,
  81. type: buffer.readUInt8(),
  82. flags: buffer.readUInt8(),
  83. id: buffer.readUInt32BE() & 0x7fffffff
  84. }
  85. if (header.length > this.maxFrameSize) {
  86. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  87. 'Frame length OOB'))
  88. }
  89. header.control = header.type !== constants.frameType.DATA
  90. this.state = 'frame-body'
  91. this.pendingHeader = header
  92. this.waiting = header.length
  93. this.partial = !header.control
  94. // TODO(indutny): eventually support partial padded DATA
  95. if (this.partial) {
  96. this.partial = (header.flags & constants.flags.PADDED) === 0
  97. }
  98. callback(null, null)
  99. }
  100. Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
  101. var frameType = constants.frameType
  102. if (header.type === frameType.DATA) {
  103. this.onDataFrame(header, buffer, callback)
  104. } else if (header.type === frameType.HEADERS) {
  105. this.onHeadersFrame(header, buffer, callback)
  106. } else if (header.type === frameType.CONTINUATION) {
  107. this.onContinuationFrame(header, buffer, callback)
  108. } else if (header.type === frameType.WINDOW_UPDATE) {
  109. this.onWindowUpdateFrame(header, buffer, callback)
  110. } else if (header.type === frameType.RST_STREAM) {
  111. this.onRSTFrame(header, buffer, callback)
  112. } else if (header.type === frameType.SETTINGS) {
  113. this.onSettingsFrame(header, buffer, callback)
  114. } else if (header.type === frameType.PUSH_PROMISE) {
  115. this.onPushPromiseFrame(header, buffer, callback)
  116. } else if (header.type === frameType.PING) {
  117. this.onPingFrame(header, buffer, callback)
  118. } else if (header.type === frameType.GOAWAY) {
  119. this.onGoawayFrame(header, buffer, callback)
  120. } else if (header.type === frameType.PRIORITY) {
  121. this.onPriorityFrame(header, buffer, callback)
  122. } else if (header.type === frameType.X_FORWARDED_FOR) {
  123. this.onXForwardedFrame(header, buffer, callback)
  124. } else {
  125. this.onUnknownFrame(header, buffer, callback)
  126. }
  127. }
  128. Parser.prototype.onUnknownFrame = function onUnknownFrame (header, buffer, callback) {
  129. if (this._lastHeaderBlock !== null) {
  130. callback(this.error(constants.error.PROTOCOL_ERROR,
  131. 'Received unknown frame in the middle of a header block'))
  132. return
  133. }
  134. callback(null, { type: 'unknown: ' + header.type })
  135. }
  136. Parser.prototype.unpadData = function unpadData (header, body, callback) {
  137. var isPadded = (header.flags & constants.flags.PADDED) !== 0
  138. if (!isPadded) { return callback(null, body) }
  139. if (!body.has(1)) {
  140. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  141. 'Not enough space for padding'))
  142. }
  143. var pad = body.readUInt8()
  144. if (!body.has(pad)) {
  145. return callback(this.error(constants.error.PROTOCOL_ERROR,
  146. 'Invalid padding size'))
  147. }
  148. var contents = body.clone(body.size - pad)
  149. body.skip(body.size)
  150. callback(null, contents)
  151. }
  152. Parser.prototype.onDataFrame = function onDataFrame (header, body, callback) {
  153. var isEndStream = (header.flags & constants.flags.END_STREAM) !== 0
  154. if (header.id === 0) {
  155. return callback(this.error(constants.error.PROTOCOL_ERROR,
  156. 'Received DATA frame with stream=0'))
  157. }
  158. // Count received bytes
  159. if (this.window) {
  160. this.window.recv.update(-body.size)
  161. }
  162. this.unpadData(header, body, function (err, data) {
  163. if (err) {
  164. return callback(err)
  165. }
  166. callback(null, {
  167. type: 'DATA',
  168. id: header.id,
  169. fin: isEndStream,
  170. data: data.take(data.size)
  171. })
  172. })
  173. }
  174. Parser.prototype.initHeaderBlock = function initHeaderBlock (header,
  175. frame,
  176. block,
  177. callback) {
  178. if (this._lastHeaderBlock) {
  179. return callback(this.error(constants.error.PROTOCOL_ERROR,
  180. 'Duplicate Stream ID'))
  181. }
  182. this._lastHeaderBlock = {
  183. id: header.id,
  184. frame: frame,
  185. queue: [],
  186. size: 0
  187. }
  188. this.queueHeaderBlock(header, block, callback)
  189. }
  190. Parser.prototype.queueHeaderBlock = function queueHeaderBlock (header,
  191. block,
  192. callback) {
  193. var self = this
  194. var item = this._lastHeaderBlock
  195. if (!this._lastHeaderBlock || item.id !== header.id) {
  196. return callback(this.error(constants.error.PROTOCOL_ERROR,
  197. 'No matching stream for continuation'))
  198. }
  199. var fin = (header.flags & constants.flags.END_HEADERS) !== 0
  200. var chunks = block.toChunks()
  201. for (var i = 0; i < chunks.length; i++) {
  202. var chunk = chunks[i]
  203. item.queue.push(chunk)
  204. item.size += chunk.length
  205. }
  206. if (item.size >= self.maxHeaderListSize) {
  207. return callback(this.error(constants.error.PROTOCOL_ERROR,
  208. 'Compressed header list is too large'))
  209. }
  210. if (!fin) { return callback(null, null) }
  211. this._lastHeaderBlock = null
  212. this.decompress.write(item.queue, function (err, chunks) {
  213. if (err) {
  214. return callback(self.error(constants.error.COMPRESSION_ERROR,
  215. err.message))
  216. }
  217. var headers = {}
  218. var size = 0
  219. for (var i = 0; i < chunks.length; i++) {
  220. var header = chunks[i]
  221. size += header.name.length + header.value.length + 32
  222. if (size >= self.maxHeaderListSize) {
  223. return callback(self.error(constants.error.PROTOCOL_ERROR,
  224. 'Header list is too large'))
  225. }
  226. if (/[A-Z]/.test(header.name)) {
  227. return callback(self.error(constants.error.PROTOCOL_ERROR,
  228. 'Header name must be lowercase'))
  229. }
  230. utils.addHeaderLine(header.name, header.value, headers)
  231. }
  232. item.frame.headers = headers
  233. item.frame.path = headers[':path']
  234. callback(null, item.frame)
  235. })
  236. }
  237. Parser.prototype.onHeadersFrame = function onHeadersFrame (header,
  238. body,
  239. callback) {
  240. var self = this
  241. if (header.id === 0) {
  242. return callback(this.error(constants.error.PROTOCOL_ERROR,
  243. 'Invalid stream id for HEADERS'))
  244. }
  245. this.unpadData(header, body, function (err, data) {
  246. if (err) { return callback(err) }
  247. var isPriority = (header.flags & constants.flags.PRIORITY) !== 0
  248. if (!data.has(isPriority ? 5 : 0)) {
  249. return callback(self.error(constants.error.FRAME_SIZE_ERROR,
  250. 'Not enough data for HEADERS'))
  251. }
  252. var exclusive = false
  253. var dependency = 0
  254. var weight = constants.DEFAULT_WEIGHT
  255. if (isPriority) {
  256. dependency = data.readUInt32BE()
  257. exclusive = (dependency & 0x80000000) !== 0
  258. dependency &= 0x7fffffff
  259. // Weight's range is [1, 256]
  260. weight = data.readUInt8() + 1
  261. }
  262. if (dependency === header.id) {
  263. return callback(self.error(constants.error.PROTOCOL_ERROR,
  264. 'Stream can\'t dependend on itself'))
  265. }
  266. var streamInfo = {
  267. type: 'HEADERS',
  268. id: header.id,
  269. priority: {
  270. parent: dependency,
  271. exclusive: exclusive,
  272. weight: weight
  273. },
  274. fin: (header.flags & constants.flags.END_STREAM) !== 0,
  275. writable: true,
  276. headers: null,
  277. path: null
  278. }
  279. self.initHeaderBlock(header, streamInfo, data, callback)
  280. })
  281. }
  282. Parser.prototype.onContinuationFrame = function onContinuationFrame (header,
  283. body,
  284. callback) {
  285. this.queueHeaderBlock(header, body, callback)
  286. }
  287. Parser.prototype.onRSTFrame = function onRSTFrame (header, body, callback) {
  288. if (body.size !== 4) {
  289. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  290. 'RST_STREAM length not 4'))
  291. }
  292. if (header.id === 0) {
  293. return callback(this.error(constants.error.PROTOCOL_ERROR,
  294. 'Invalid stream id for RST_STREAM'))
  295. }
  296. callback(null, {
  297. type: 'RST',
  298. id: header.id,
  299. code: constants.errorByCode[body.readUInt32BE()]
  300. })
  301. }
  302. Parser.prototype._validateSettings = function _validateSettings (settings) {
  303. if (settings['enable_push'] !== undefined &&
  304. settings['enable_push'] !== 0 &&
  305. settings['enable_push'] !== 1) {
  306. return this.error(constants.error.PROTOCOL_ERROR,
  307. 'SETTINGS_ENABLE_PUSH must be 0 or 1')
  308. }
  309. if (settings['initial_window_size'] !== undefined &&
  310. (settings['initial_window_size'] > constants.MAX_INITIAL_WINDOW_SIZE ||
  311. settings['initial_window_size'] < 0)) {
  312. return this.error(constants.error.FLOW_CONTROL_ERROR,
  313. 'SETTINGS_INITIAL_WINDOW_SIZE is OOB')
  314. }
  315. if (settings['max_frame_size'] !== undefined &&
  316. (settings['max_frame_size'] > constants.ABSOLUTE_MAX_FRAME_SIZE ||
  317. settings['max_frame_size'] < constants.INITIAL_MAX_FRAME_SIZE)) {
  318. return this.error(constants.error.PROTOCOL_ERROR,
  319. 'SETTINGS_MAX_FRAME_SIZE is OOB')
  320. }
  321. return undefined
  322. }
  323. Parser.prototype.onSettingsFrame = function onSettingsFrame (header,
  324. body,
  325. callback) {
  326. if (header.id !== 0) {
  327. return callback(this.error(constants.error.PROTOCOL_ERROR,
  328. 'Invalid stream id for SETTINGS'))
  329. }
  330. var isAck = (header.flags & constants.flags.ACK) !== 0
  331. if (isAck && body.size !== 0) {
  332. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  333. 'SETTINGS with ACK and non-zero length'))
  334. }
  335. if (isAck) {
  336. return callback(null, { type: 'ACK_SETTINGS' })
  337. }
  338. if (body.size % 6 !== 0) {
  339. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  340. 'SETTINGS length not multiple of 6'))
  341. }
  342. var settings = {}
  343. while (!body.isEmpty()) {
  344. var id = body.readUInt16BE()
  345. var value = body.readUInt32BE()
  346. var name = constants.settingsIndex[id]
  347. if (name) {
  348. settings[name] = value
  349. }
  350. }
  351. var err = this._validateSettings(settings)
  352. if (err !== undefined) {
  353. return callback(err)
  354. }
  355. callback(null, {
  356. type: 'SETTINGS',
  357. settings: settings
  358. })
  359. }
  360. Parser.prototype.onPushPromiseFrame = function onPushPromiseFrame (header,
  361. body,
  362. callback) {
  363. if (header.id === 0) {
  364. return callback(this.error(constants.error.PROTOCOL_ERROR,
  365. 'Invalid stream id for PUSH_PROMISE'))
  366. }
  367. var self = this
  368. this.unpadData(header, body, function (err, data) {
  369. if (err) {
  370. return callback(err)
  371. }
  372. if (!data.has(4)) {
  373. return callback(self.error(constants.error.FRAME_SIZE_ERROR,
  374. 'PUSH_PROMISE length less than 4'))
  375. }
  376. var streamInfo = {
  377. type: 'PUSH_PROMISE',
  378. id: header.id,
  379. fin: false,
  380. promisedId: data.readUInt32BE() & 0x7fffffff,
  381. headers: null,
  382. path: null
  383. }
  384. self.initHeaderBlock(header, streamInfo, data, callback)
  385. })
  386. }
  387. Parser.prototype.onPingFrame = function onPingFrame (header, body, callback) {
  388. if (body.size !== 8) {
  389. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  390. 'PING length != 8'))
  391. }
  392. if (header.id !== 0) {
  393. return callback(this.error(constants.error.PROTOCOL_ERROR,
  394. 'Invalid stream id for PING'))
  395. }
  396. var ack = (header.flags & constants.flags.ACK) !== 0
  397. callback(null, { type: 'PING', opaque: body.take(body.size), ack: ack })
  398. }
  399. Parser.prototype.onGoawayFrame = function onGoawayFrame (header,
  400. body,
  401. callback) {
  402. if (!body.has(8)) {
  403. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  404. 'GOAWAY length < 8'))
  405. }
  406. if (header.id !== 0) {
  407. return callback(this.error(constants.error.PROTOCOL_ERROR,
  408. 'Invalid stream id for GOAWAY'))
  409. }
  410. var frame = {
  411. type: 'GOAWAY',
  412. lastId: body.readUInt32BE(),
  413. code: constants.goawayByCode[body.readUInt32BE()]
  414. }
  415. if (body.size !== 0) { frame.debug = body.take(body.size) }
  416. callback(null, frame)
  417. }
  418. Parser.prototype.onPriorityFrame = function onPriorityFrame (header,
  419. body,
  420. callback) {
  421. if (body.size !== 5) {
  422. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  423. 'PRIORITY length != 5'))
  424. }
  425. if (header.id === 0) {
  426. return callback(this.error(constants.error.PROTOCOL_ERROR,
  427. 'Invalid stream id for PRIORITY'))
  428. }
  429. var dependency = body.readUInt32BE()
  430. // Again the range is from 1 to 256
  431. var weight = body.readUInt8() + 1
  432. if (dependency === header.id) {
  433. return callback(this.error(constants.error.PROTOCOL_ERROR,
  434. 'Stream can\'t dependend on itself'))
  435. }
  436. callback(null, {
  437. type: 'PRIORITY',
  438. id: header.id,
  439. priority: {
  440. exclusive: (dependency & 0x80000000) !== 0,
  441. parent: dependency & 0x7fffffff,
  442. weight: weight
  443. }
  444. })
  445. }
  446. Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (header,
  447. body,
  448. callback) {
  449. if (body.size !== 4) {
  450. return callback(this.error(constants.error.FRAME_SIZE_ERROR,
  451. 'WINDOW_UPDATE length != 4'))
  452. }
  453. var delta = body.readInt32BE()
  454. if (delta === 0) {
  455. return callback(this.error(constants.error.PROTOCOL_ERROR,
  456. 'WINDOW_UPDATE delta == 0'))
  457. }
  458. callback(null, {
  459. type: 'WINDOW_UPDATE',
  460. id: header.id,
  461. delta: delta
  462. })
  463. }
  464. Parser.prototype.onXForwardedFrame = function onXForwardedFrame (header,
  465. body,
  466. callback) {
  467. callback(null, {
  468. type: 'X_FORWARDED_FOR',
  469. host: body.take(body.size).toString()
  470. })
  471. }