UNPKG

litejs

Version:

Single-page application framework

276 lines (242 loc) 6.81 kB
var fs = require("fs") , os = require("os") , path = require("path") , qs = require("querystring") , Writable = require("stream").Writable , accept = require("./accept.js").accept , rnrn = Buffer.from("\r\n\r\n") , negotiateContent = accept({ 'application/json': function(str) { return JSON.parse(str || "{}") }, 'application/x-www-form-urlencoded': function(str) { return qs.parse(str) }, // Subtypes: // - alternative // - byterange https://tools.ietf.org/html/rfc7233#section-5.4.1 // - digest // - encrypted // - form-data https://tools.ietf.org/html/rfc7578 // - mixed // - related https://tools.ietf.org/html/rfc2387 // - report // - signed // - x-mixed-replace 'multipart/*;boundary=': null }) , negotiateDisposition = accept([ 'form-data;name="";filename=""' ]) , seq = 0 module.exports = getContent getContent.multipart = multipart makeTable(rnrn) function getContent(next, reqOpts) { var i, tmp , req = this , head = req.headers , negod = negotiateContent(head["content-type"] || head.accept || "*") , stream = req req.body = {} if (head["content-encoding"]) { tmp = head["content-encoding"].split(/\W+/) for (i = tmp.length; i--; ) { if (tmp[i] === "gzip" || tmp[i] === "deflate") { // Decompress Gzip or Deflate by auto-detecting the header stream = stream.pipe(zlib.createUnzip()) } else if (tmp[i] && tmp[i] !== "identity") { throw "Unsupported Media Type" } } } if (negod.type === "multipart") { stream = stream.pipe(multipart(negod.boundary, reqOpts || {}, req)) stream.on("field", function(negod) { req.body[negod.name] = negod.content.toString() }) stream.on("file", function(negod) { if (!req.files) req.files = [] req.files.push(negod) }) stream.on("finish", function() { next(null, req.body, req.files) if (req.files) { for (var i = req.files.length; i--; ) { if (req.files[i].tmp) fs.unlink(req.files[i].tmp, nop) } } }) } else { tmp = "" stream.on("data", function handleData(data) { tmp += data // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST if (tmp.length > req.opts.maxBodySize) { stream.destroy("Payload Too Large") // 431 Payload Too Large } }) .on("end", handleEnd) } stream.on("error", next) function handleEnd() { try { req.body = typeof negod.o === "function" ? negod.o(tmp) : tmp next(null, req.body, req.files, negod) } catch (e) { next(e) } } } function multipart(boundary, reqOpts, req) { // nodejs HTTP_MAX_HEADER_SIZE (80*1024) // { "name": "in[]", filename: "a.jpg", tmp: "/tmp/a.123", size: 123 } if (typeof boundary === "string") { boundary = Buffer.from("\r\n--" + boundary) } seq++ makeTable(boundary) var headers, fileStream , negod = reqOpts.preamble && { preamble: true } , needle = boundary , bufs = [rnrn.slice(2)] , bufsBytes = 2 , nextPos = needle.length - 3 , remainingFields = number(reqOpts.maxFields, req.opts.maxFields, 1000) , remainingFiles = number(reqOpts.maxFiles, req.opts.maxFiles, 1000) return new Writable({ write: function(chunk, enc, cb) { var buf, bufNum, i, j , writable = this , pos = nextPos , len = chunk.length , last = needle[needle.length - 1] , cut = 0 if (pos > len) { bufs.push(chunk) bufsBytes += len nextPos -= len return cb() } jump:for (; pos < len; ) { if (chunk[pos] === last) { buf = chunk bufNum = bufs.length i = needle.length - 1 j = pos for (; i > 0; ) { if (j < 1 && bufNum > 0) { buf = bufs[--bufNum] j = buf.length } if (needle[--i] !== buf[--j]) { pos += needle.jump[last] continue jump } } // match found if (bufsBytes > 0) { bufs.push(chunk) buf = Buffer.concat(bufs, pos + bufsBytes - needle.length + 1) bufsBytes = bufs.length = 0 } else if (cut > 0 || pos < len - 1) { buf = buf.slice(cut, pos - needle.length + 1) } if (needle === boundary) { if (negod) { if (!remainingFields--) return writable.destroy({ code: 413, message: "maxFields exceeded"}) negod.content = buf writable.emit(negod.filename === "" ? "field" : "file", negod) negod = null } else if (fileStream) { fileStream.end(buf) fileStream = null } needle = rnrn } else { // content start headers = parseHeaders(buf.toString()) negod = negotiateDisposition(headers["content-disposition"]) negod.headers = headers if (negod.filename && reqOpts.path !== null) { if (!remainingFiles--) return writable.destroy({ code: 413, message: "maxFiles exceeded"}) writable.emit("file", negod, saveTo) if (!fileStream) { saveTo( (reqOpts.path || os.tmpdir() + "/upload-") + process.pid + "-" + seq + "-" + remainingFiles ) } } needle = boundary } cut = pos + 1 last = needle[needle.length - 1] pos += needle.length } else { pos += needle.jump[chunk[pos]] } } nextPos = pos - len if (cut < len) { bufs.push(cut ? chunk.slice(cut) : chunk) bufsBytes += bufs[bufs.length - 1].length } if (fileStream) { for (; bufs.length > 1 && bufsBytes - bufs[bufs.length - 1].length > needle.length; ) { if (!fileStream.write(bufs.pop())) { return fileStream.once("drain", cb) } } } cb() } }) function saveTo(stream) { fileStream = ( typeof stream === "string" ? fs.createWriteStream(negod.tmp = stream) : stream ) negod = null } } function nop() {} function number(a, b, c) { return ( typeof a === "number" ? a : typeof b === "number" ? b : c ) } // multipart/form-data part accepts only Content-Type, Content-Disposition, and (in limited circumstances) Content-Transfer-Encoding. // Other header fields MUST NOT be included and MUST be ignored. // Content-Transfer-Encoding: 7bit / 8bit / binary / quoted-printable / base64 / ietf-token / x-token // // User Agents that recognize Multipart/Related will ignore the Content-Disposition header's disposition type. // Other User Agents will process the Multipart/Related as Multipart/Mixed and may make use of that header's information. function parseHeaders(str) { // 4.8. Other "Content-" Header Fields var i , headers = {} , lines = str.split("\r\n") , len = lines.length for (; len; ) { i = lines[--len].indexOf(":") if (i > 0) { headers[ lines[len].slice(0, i).toLowerCase() ] = lines[len].slice(i + 1).trim() } } return headers } function makeTable(buf) { var len = buf.length , i = 0 , pos = len - 1 , jump = buf.jump = new Uint8Array(256).fill(len) for (; i < pos; ++i) { jump[buf[i]] = pos - i } }