-
Notifications
You must be signed in to change notification settings - Fork 0
/
multipartform.js
201 lines (173 loc) · 5.18 KB
/
multipartform.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
var fs = require('fs');
exports.defaultBoundary = '48940923NODERESLTER3890457293';
// This little object allows us hijack the write method via duck-typing
// and write to strings or regular streams that support the write method.
function Stream(stream) {
//If the user pases a string for stream,we initalize one to write to
if (this._isString(stream)) {
this.string = "";
}
this.stream = stream;
}
Stream.prototype = {
//write to an internal String or to the Stream
write: function(data) {
if (this.string != undefined) {
this.string += data;
} else {
this.stream.write(data, "binary");
}
},
//stolen from underscore.js
_isString: function(obj) {
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
}
}
function File(path, filename, fileSize, encoding, contentType) {
this.path = path;
this.filename = filename || this._basename(path);
this.fileSize = fileSize;
this.encoding = encoding || "binary";
this.contentType = contentType || 'application/octet-stream';
}
File.prototype = {
_basename: function(path) {
var parts = path.split(/\/|\\/);
return parts[parts.length - 1];
}
};
function Data(filename, contentType, data) {
this.filename = filename;
this.contentType = contentType || 'application/octet-stream';
this.data = data;
}
function Part(name, value, boundary) {
this.name = name;
this.value = value;
this.boundary = boundary;
}
Part.prototype = {
//returns the Content-Disposition header
header: function() {
var header;
if (this.value.data) {
header = "Content-Disposition: form-data; name=\"" + this.name +
"\"; filename=\"" + this.value.filename + "\"\r\n" +
"Content-Type: " + this.value.contentType;
} if (this.value instanceof File) {
header = "Content-Disposition: form-data; name=\"" + this.name +
"\"; filename=\"" + this.value.filename + "\"\r\n" +
"Content-Length: " + this.value.fileSize + "\r\n" +
"Content-Type: " + this.value.contentType;
} else {
header = "Content-Disposition: form-data; name=\"" + this.name + "\"";
}
return "--" + this.boundary + "\r\n" + header + "\r\n\r\n";
},
//calculates the size of the Part
sizeOf: function() {
var valueSize;
if (this.value instanceof File) {
valueSize = this.value.fileSize;
} else if (this.value.data) {
valueSize = this.value.data.length;
} else {
valueSize = this.value.length;
}
return valueSize + this.header().length + 2;
},
// Writes the Part out to a writable stream that supports the write(data) method
// You can also pass in a String and a String will be returned to the callback
// with the whole Part
// Calls the callback when complete
write: function(stream, callback) {
var self = this;
//first write the Content-Disposition
stream.write(this.header());
//Now write out the body of the Part
if (this.value instanceof File) {
fs.open(this.value.path, "r", 0666, function (err, fd) {
if (err) throw err;
position = 0;
(function reader () {
fs.read(fd, 1024 * 4, position, "binary", function (er, chunk) {
if (er) callback(err);
stream.write(chunk);
position += 1024 * 4;
if (chunk) reader();
else {
stream.write("\r\n")
callback();
fs.close(fd);
}
});
})(); // reader()
});
} else {
stream.write(this.value + "\r\n");
callback();
}
}
}
//Renamed to MultiPartRequest from Request
function MultiPartRequest(data, boundary) {
this.encoding = 'binary';
this.boundary = boundary || exports.defaultBoundary;
this.data = data;
this.partNames = this._partNames();
}
MultiPartRequest.prototype = {
_partNames: function() {
partNames = []
for (var name in this.data) {
partNames.push(name)
}
return partNames;
},
write: function(stream, callback) {
var partCount = 0, self = this;
// wrap the stream in our own Stream object
// See the Stream function above for the benefits of this
var stream = new Stream(stream);
// Let each part write itself out to the stream
(function writePart() {
partName = this.partNames[partCount];
part = new Part(partName, self.data[partName], self.boundary);
part.write(stream, function (err) {
if (err) {
callback(err);
return;
}
partCount += 1;
if (partCount < self.partNames.length)
writePart();
else {
stream.write('--' + self.boundary + '--' + "\r\n");
if (callback) callback(stream.string || "");
}
});
})();
}
}
var exportMethods = {
file: function(path, filename, fileSize, encoding, contentType) {
return new File(path, filename, fileSize, encoding, contentType)
},
data: function(filename, contentType, data) {
return new Data(filename, contentType, data);
},
sizeOf: function(parts, boundary) {
var totalSize = 0;
boundary = boundary || exports.defaultBoundary;
for (var name in parts) totalSize += new Part(name, parts[name], boundary).sizeOf();
return totalSize + boundary.length + 6;
},
write: function(stream, data, callback, boundary) {
var r = new MultiPartRequest(data, boundary);
r.write(stream, callback);
return r;
}
}
Object.keys(exportMethods).forEach(function(exportMethod) {
exports[exportMethod] = exportMethods[exportMethod]
})