-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmultipart-form-data.js
138 lines (110 loc) · 3.59 KB
/
multipart-form-data.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
export function MultipartFormDataBuilder () {
const values = []
const files = []
function build () {
const crlf = '\r\n'
const boundaryKey = createBoundaryKey()
const boundary = `--${boundaryKey}`
const delimiter = `${crlf}--${boundary}`
const closeDelimiter = `${delimiter}--`
function buildBody () {
let multipartBody = Buffer.from('')
values.forEach(value => {
multipartBody = Buffer.concat([
multipartBody,
Buffer.from(`${delimiter}${crlf}Content-Disposition: form-data; name="${value.name}"${crlf}`),
Buffer.from(`${crlf}${value.value}`)
])
})
files.forEach(value => {
multipartBody = Buffer.concat([
multipartBody,
Buffer.from(`${delimiter}${crlf}Content-Disposition: form-data; name="${value.name}"; filename="${value.filename}"${crlf}Content-Type: ${value.contentType}${crlf}${crlf}`),
value.data
])
})
multipartBody = Buffer.concat([
multipartBody,
Buffer.from(closeDelimiter)
])
return multipartBody
}
const body = buildBody()
return {
headers: {
'Content-Type': `multipart/form-data; boundary=${boundary}`,
'Content-Length': body.length
},
body
}
}
function append (name, value) {
values.push({ name, value })
}
function appendFile (name, data, filename, contentType) {
files.push({ name, data, filename, contentType })
}
function createBoundaryKey () {
function randomInteger (min, max) {
return Math.floor((Math.random() * (max - min + 1)) + min)
}
return randomInteger(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER)
}
return {
build,
append,
appendFile
}
}
export function MultipartFormData (headers, rawBody) {
function parseBoundary (contentType) {
const contentTypePattern = /multipart\/form-data; boundary=-[-]+(.+)/gi
const match = contentTypePattern.exec(contentType)
if (!match) {
throw new Error(`Unexpected Content-Type '${contentType}' (expecting: 'multipart/form-data')`)
}
return match[1]
}
function splitLines (body, boundary) {
return body.split(new RegExp(`-[-]+${boundary}`, 'g'))
.filter(line => line !== '--')
}
function parsePropertyLines (lines) {
return lines
.map(line => new RegExp(`^\r\nContent-Disposition: form-data; name="(.+)"\r\n\r\n(.*)\r\n$`, 'g').exec(line))
.filter(match => !!match)
.map(match => ({ [match[1]]: match[2] }))
.reduce((keyValuePair, obj) => ({ ...obj, ...keyValuePair }), {})
}
function parseFileLines (lines) {
const files = {}
lines
.map(line => new RegExp(`^\r\nContent-Disposition: form-data; name="(.+)"; filename="(.+)"\r\nContent-Type: (.+)\r\n\r\n((?:.)*)\r\n$`, 'gsmu').exec(line))
.filter(match => !!match)
.forEach(match => {
const property = match[1]
const file = {
filename: match[2],
mimeType: match[3],
data: Buffer.from(match[4])
}
files[property] ? files[property].push(file) : files[property] = [file]
})
return files
}
function parse () {
const contentType = headers['Content-Type'] || headers['content-type']
if (!contentType) {
throw new Error('Content-Type missing in headers')
}
const boundary = parseBoundary(contentType)
const body = rawBody.toString()
const lines = splitLines(body, boundary)
const properties = parsePropertyLines(lines)
const files = parseFileLines(lines)
return { ...properties, ...files }
}
return {
parse
}
}