1 |
|
2 | var fs = require("fs")
|
3 | , os = require("os")
|
4 | , path = require("path")
|
5 | , qs = require("querystring")
|
6 | , Writable = require("stream").Writable
|
7 | , accept = require("./accept.js")
|
8 | , rnrn = Buffer.from("\r\n\r\n")
|
9 | , negotiateContent = accept({
|
10 | 'application/json': function(str) {
|
11 | return JSON.parse(str || "{}")
|
12 | },
|
13 | 'application/x-www-form-urlencoded': function(str) {
|
14 | return qs.parse(str)
|
15 | },
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | 'multipart/*;boundary=': null
|
28 | })
|
29 | , negotiateDisposition = accept([
|
30 | 'form-data;name="";filename=""'
|
31 | ])
|
32 | , seq = 0
|
33 |
|
34 |
|
35 | module.exports = getContent
|
36 | getContent.multipart = multipart
|
37 |
|
38 |
|
39 | makeTable(rnrn)
|
40 |
|
41 | function getContent(next, reqOpts) {
|
42 | var i, tmp
|
43 | , req = this
|
44 | , head = req.headers
|
45 | , negod = negotiateContent(head["content-type"] || head.accept || "*")
|
46 | , stream = req
|
47 |
|
48 | req.body = {}
|
49 |
|
50 | if (head["content-encoding"]) {
|
51 | tmp = head["content-encoding"].split(/\W+/)
|
52 | for (i = tmp.length; i--; ) {
|
53 | if (tmp[i] === "gzip" || tmp[i] === "deflate") {
|
54 |
|
55 | stream = stream.pipe(zlib.createUnzip())
|
56 | } else if (tmp[i] && tmp[i] !== "identity") {
|
57 | throw "Unsupported Media Type"
|
58 | }
|
59 | }
|
60 | }
|
61 |
|
62 | if (negod.type === "multipart") {
|
63 | stream = stream.pipe(multipart(negod.boundary, reqOpts || {}, req))
|
64 |
|
65 | stream.on("field", function(negod) {
|
66 | req.body[negod.name] = negod.content.toString()
|
67 | })
|
68 | stream.on("file", function(negod) {
|
69 | if (!req.files) req.files = []
|
70 | req.files.push(negod)
|
71 | })
|
72 | stream.on("finish", function() {
|
73 | next(null, req.body, req.files)
|
74 | if (req.files) {
|
75 | for (var i = req.files.length; i--; ) {
|
76 | if (req.files[i].tmp) fs.unlink(req.files[i].tmp, nop)
|
77 | }
|
78 | }
|
79 | })
|
80 | } else {
|
81 | tmp = ""
|
82 | stream.on("data", function handleData(data) {
|
83 | tmp += data
|
84 |
|
85 | if (tmp.length > req.opts.maxBodySize) {
|
86 | stream.destroy("Payload Too Large")
|
87 | }
|
88 | })
|
89 | .on("end", handleEnd)
|
90 | }
|
91 |
|
92 | stream.on("error", next)
|
93 |
|
94 | function handleEnd() {
|
95 | try {
|
96 | req.body = typeof negod.o === "function" ? negod.o(tmp) : tmp
|
97 | next(null, req.body, req.files, negod)
|
98 | } catch (e) {
|
99 | next(e)
|
100 | }
|
101 | }
|
102 | }
|
103 |
|
104 | function multipart(boundary, reqOpts, req) {
|
105 |
|
106 |
|
107 | if (typeof boundary === "string") {
|
108 | boundary = Buffer.from("\r\n--" + boundary)
|
109 | }
|
110 |
|
111 | seq++
|
112 | makeTable(boundary)
|
113 |
|
114 | var headers, fileStream
|
115 | , negod = reqOpts.preamble && { preamble: true }
|
116 | , needle = boundary
|
117 | , bufs = [rnrn.slice(2)]
|
118 | , bufsBytes = 2
|
119 | , nextPos = needle.length - 3
|
120 | , remainingFields = number(reqOpts.maxFields, req.opts.maxFields, 1000)
|
121 | , remainingFiles = number(reqOpts.maxFiles, req.opts.maxFiles, 1000)
|
122 |
|
123 | return new Writable({
|
124 | write: function(chunk, enc, cb) {
|
125 | var buf, bufNum, i, j
|
126 | , writable = this
|
127 | , pos = nextPos
|
128 | , len = chunk.length
|
129 | , last = needle[needle.length - 1]
|
130 | , cut = 0
|
131 |
|
132 | if (pos > len) {
|
133 | bufs.push(chunk)
|
134 | bufsBytes += len
|
135 | nextPos -= len
|
136 | return cb()
|
137 | }
|
138 |
|
139 | jump:for (; pos < len; ) {
|
140 | if (chunk[pos] === last) {
|
141 | buf = chunk
|
142 | bufNum = bufs.length
|
143 | i = needle.length - 1
|
144 | j = pos
|
145 | for (; i > 0; ) {
|
146 | if (j < 1 && bufNum > 0) {
|
147 | buf = bufs[--bufNum]
|
148 | j = buf.length
|
149 | }
|
150 | if (needle[--i] !== buf[--j]) {
|
151 | pos += needle.jump[last]
|
152 | continue jump
|
153 | }
|
154 | }
|
155 |
|
156 | if (bufsBytes > 0) {
|
157 | bufs.push(chunk)
|
158 | buf = Buffer.concat(bufs, pos + bufsBytes - needle.length + 1)
|
159 | bufsBytes = bufs.length = 0
|
160 | } else if (cut > 0 || pos < len - 1) {
|
161 | buf = buf.slice(cut, pos - needle.length + 1)
|
162 | }
|
163 | if (needle === boundary) {
|
164 | if (negod) {
|
165 | if (!remainingFields--) return writable.destroy({ code: 413, message: "maxFields exceeded"})
|
166 | negod.content = buf
|
167 | writable.emit(negod.filename === "" ? "field" : "file", negod)
|
168 | negod = null
|
169 | } else if (fileStream) {
|
170 | fileStream.end(buf)
|
171 | fileStream = null
|
172 | }
|
173 | needle = rnrn
|
174 | } else {
|
175 |
|
176 | headers = parseHeaders(buf.toString())
|
177 | negod = negotiateDisposition(headers["content-disposition"])
|
178 | negod.headers = headers
|
179 |
|
180 | if (negod.filename && reqOpts.path !== null) {
|
181 | if (!remainingFiles--) return writable.destroy({ code: 413, message: "maxFiles exceeded"})
|
182 | writable.emit("file", negod, saveTo)
|
183 | if (!fileStream) {
|
184 | saveTo(
|
185 | (reqOpts.path || os.tmpdir() + "/upload-") +
|
186 | process.pid + "-" + seq + "-" + remainingFiles
|
187 | )
|
188 | }
|
189 | }
|
190 | needle = boundary
|
191 | }
|
192 | cut = pos + 1
|
193 | last = needle[needle.length - 1]
|
194 | pos += needle.length
|
195 | } else {
|
196 | pos += needle.jump[chunk[pos]]
|
197 | }
|
198 | }
|
199 |
|
200 | nextPos = pos - len
|
201 |
|
202 | if (cut < len) {
|
203 | bufs.push(cut ? chunk.slice(cut) : chunk)
|
204 | bufsBytes += bufs[bufs.length - 1].length
|
205 | }
|
206 |
|
207 | if (fileStream) {
|
208 | for (; bufs.length > 1 && bufsBytes - bufs[bufs.length - 1].length > needle.length; ) {
|
209 | if (!fileStream.write(bufs.pop())) {
|
210 | return fileStream.once("drain", cb)
|
211 | }
|
212 | }
|
213 | }
|
214 |
|
215 | cb()
|
216 | }
|
217 | })
|
218 |
|
219 | function saveTo(stream) {
|
220 | fileStream = (
|
221 | typeof stream === "string" ?
|
222 | fs.createWriteStream(negod.tmp = stream) :
|
223 | stream
|
224 | )
|
225 | negod = null
|
226 | }
|
227 | }
|
228 |
|
229 | function nop() {}
|
230 |
|
231 | function number(a, b, c) {
|
232 | return (
|
233 | typeof a === "number" ? a :
|
234 | typeof b === "number" ? b :
|
235 | c
|
236 | )
|
237 | }
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 | function parseHeaders(str) {
|
248 |
|
249 | var i
|
250 | , headers = {}
|
251 | , lines = str.split("\r\n")
|
252 | , len = lines.length
|
253 | for (; len; ) {
|
254 | i = lines[--len].indexOf(":")
|
255 | if (i > 0) {
|
256 | headers[
|
257 | lines[len].slice(0, i).toLowerCase()
|
258 | ] = lines[len].slice(i + 1).trim()
|
259 | }
|
260 | }
|
261 | return headers
|
262 | }
|
263 |
|
264 | function makeTable(buf) {
|
265 | var len = buf.length
|
266 | , i = 0
|
267 | , pos = len - 1
|
268 | , jump = buf.jump = new Uint8Array(256).fill(len)
|
269 |
|
270 | for (; i < pos; ++i) {
|
271 | jump[buf[i]] = pos - i
|
272 | }
|
273 | }
|
274 |
|
275 |
|