-
Notifications
You must be signed in to change notification settings - Fork 0
/
parser.js
127 lines (116 loc) · 3.78 KB
/
parser.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
const { Writable } = require('stream');
const { Readable } = require('stream');
class FileReader extends Readable {
constructor(source) {
super(source);
this.source = source;
this.offset = 0;
this.length = source.length;
this.on('end', this._destroy.bind(this));
}
_read(size) {
if (this.offset < this.length) {
this.push(this.source.slice(this.offset, this.offset + size));
this.offset += size;
}
if (this.offset >= this.length) {
this.push(null);
}
}
_destroy() {
this.source = null;
this.offset = null;
this.length = null;
}
}
class Parser extends Writable {
constructor(options) {
super(options);
this.headers = options.headers;
this.RNRN = Buffer.from('\r\n\r\n');
this.buffer = Buffer.alloc(0);
this.boundary = null;
this.cnt = 0;
}
parseData() {
const boundaryPos = this.buffer.indexOf(this.boundary);
const breakPos = this.buffer.indexOf(this.RNRN);
if (boundaryPos !== -1 && breakPos !== -1) {
const dataHeaders = this.buffer.slice(boundaryPos + this.boundary.length + 2, breakPos);
const parsedHeaders = Parser.parseDataHeaders(dataHeaders);
// buffer without data headers
this.buffer = this.buffer.slice(breakPos + this.RNRN.length);
const nextBoundaryPos = this.buffer.indexOf(this.boundary);
const contentDispos = Parser.objectifyContentDispos(parsedHeaders);
const content = this.buffer.slice(0, nextBoundaryPos - 2);
if (contentDispos.hasOwnProperty('filename')) {
this.emitFile(content, contentDispos);
} else {
this.emitData(content, contentDispos);
}
this.buffer = this.buffer.slice(nextBoundaryPos);
this.parseData();
}
}
static objectifyContentDispos(parsedHeaders) {
const objectified = parsedHeaders['content-disposition'].split(';').reduce((acc, item, i) => {
if (i === 0) return acc;
const [prop, val] = item.split('=');
acc[prop.trim()] = val.trim().replace(/"/g, '');
return acc;
}, {});
if (objectified.hasOwnProperty('filename')) {
objectified.contentType = parsedHeaders['content-type'];
}
console.log(objectified);
return objectified;
}
emitFile(file, props) {
const { name, filename, contentType } = props;
const fileStream = new FileReader(file);
this.emit('file', name, fileStream, filename, contentType);
}
emitData(data, props) {
const { name } = props;
this.emit('field', name, data);
}
_write(chunk, encoding, callback) {
if (this.isMultiPart(this.headers)) {
this.buffer = Buffer.concat([this.buffer, chunk]);
this.parseData();
}
console.log(`_write has been called ${++this.cnt} times`);
// console.log(this.headers);
// console.log('*********************************');
// console.log(chunk.toString());
callback();
}
static parseDataHeaders(dataHeaders) {
const headersStr = dataHeaders.toString();
if (headersStr.includes('\r\n')) {
const headers = headersStr.split('\r\n').reduce((acc, string) => {
const [left, right] = string.split(':');
acc[left.toLowerCase()] = right.trim();
return acc;
}, {});
return headers;
}
const [left, right] = headersStr.split(':');
const headers = { [left.toLowerCase()]: right.trim() };
return headers;
}
isMultiPart(headers) {
if (headers.hasOwnProperty('content-type')) {
const [type, boundaryPart] = headers['content-type'].split(' ');
if (type === 'multipart/form-data;') {
const boundary = boundaryPart.replace(/boundary=/i, '');
this.boundary = Buffer.from(`--${boundary}`);
return true;
}
return false;
}
this.emit('error', 'Content type must be defined');
return false;
}
}
module.exports = Parser;