incoming_form.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. if (global.GENTLY) require = GENTLY.hijack(require);
  2. var fs = require('fs');
  3. var util = require('util'),
  4. path = require('path'),
  5. File = require('./file'),
  6. MultipartParser = require('./multipart_parser').MultipartParser,
  7. QuerystringParser = require('./querystring_parser').QuerystringParser,
  8. OctetParser = require('./octet_parser').OctetParser,
  9. JSONParser = require('./json_parser').JSONParser,
  10. StringDecoder = require('string_decoder').StringDecoder,
  11. EventEmitter = require('events').EventEmitter,
  12. Stream = require('stream').Stream,
  13. os = require('os');
  14. function IncomingForm(opts) {
  15. if (!(this instanceof IncomingForm)) return new IncomingForm(opts);
  16. EventEmitter.call(this);
  17. opts=opts||{};
  18. this.error = null;
  19. this.ended = false;
  20. this.maxFields = opts.maxFields || 1000;
  21. this.maxFieldsSize = opts.maxFieldsSize || 2 * 1024 * 1024;
  22. this.keepExtensions = opts.keepExtensions || false;
  23. this.uploadDir = opts.uploadDir || os.tmpDir();
  24. this.encoding = opts.encoding || 'utf-8';
  25. this.headers = null;
  26. this.type = null;
  27. this.hash = opts.hash || false;
  28. this.multiples = opts.multiples || false;
  29. this.bytesReceived = null;
  30. this.bytesExpected = null;
  31. this._parser = null;
  32. this._flushing = 0;
  33. this._fieldsSize = 0;
  34. this.openedFiles = [];
  35. return this;
  36. }
  37. util.inherits(IncomingForm, EventEmitter);
  38. exports.IncomingForm = IncomingForm;
  39. IncomingForm.prototype.parse = function(req, cb) {
  40. this.pause = function() {
  41. try {
  42. req.pause();
  43. } catch (err) {
  44. // the stream was destroyed
  45. if (!this.ended) {
  46. // before it was completed, crash & burn
  47. this._error(err);
  48. }
  49. return false;
  50. }
  51. return true;
  52. };
  53. this.resume = function() {
  54. try {
  55. req.resume();
  56. } catch (err) {
  57. // the stream was destroyed
  58. if (!this.ended) {
  59. // before it was completed, crash & burn
  60. this._error(err);
  61. }
  62. return false;
  63. }
  64. return true;
  65. };
  66. // Setup callback first, so we don't miss anything from data events emitted
  67. // immediately.
  68. if (cb) {
  69. var fields = {}, files = {};
  70. this
  71. .on('field', function(name, value) {
  72. fields[name] = value;
  73. })
  74. .on('file', function(name, file) {
  75. if (this.multiples) {
  76. if (files[name]) {
  77. if (!Array.isArray(files[name])) {
  78. files[name] = [files[name]];
  79. }
  80. files[name].push(file);
  81. } else {
  82. files[name] = file;
  83. }
  84. } else {
  85. files[name] = file;
  86. }
  87. })
  88. .on('error', function(err) {
  89. cb(err, fields, files);
  90. })
  91. .on('end', function() {
  92. cb(null, fields, files);
  93. });
  94. }
  95. // Parse headers and setup the parser, ready to start listening for data.
  96. this.writeHeaders(req.headers);
  97. // Start listening for data.
  98. var self = this;
  99. req
  100. .on('error', function(err) {
  101. self._error(err);
  102. })
  103. .on('aborted', function() {
  104. self.emit('aborted');
  105. self._error(new Error('Request aborted'));
  106. })
  107. .on('data', function(buffer) {
  108. self.write(buffer);
  109. })
  110. .on('end', function() {
  111. if (self.error) {
  112. return;
  113. }
  114. var err = self._parser.end();
  115. if (err) {
  116. self._error(err);
  117. }
  118. });
  119. return this;
  120. };
  121. IncomingForm.prototype.writeHeaders = function(headers) {
  122. this.headers = headers;
  123. this._parseContentLength();
  124. this._parseContentType();
  125. };
  126. IncomingForm.prototype.write = function(buffer) {
  127. if (this.error) {
  128. return;
  129. }
  130. if (!this._parser) {
  131. this._error(new Error('uninitialized parser'));
  132. return;
  133. }
  134. this.bytesReceived += buffer.length;
  135. this.emit('progress', this.bytesReceived, this.bytesExpected);
  136. var bytesParsed = this._parser.write(buffer);
  137. if (bytesParsed !== buffer.length) {
  138. this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed'));
  139. }
  140. return bytesParsed;
  141. };
  142. IncomingForm.prototype.pause = function() {
  143. // this does nothing, unless overwritten in IncomingForm.parse
  144. return false;
  145. };
  146. IncomingForm.prototype.resume = function() {
  147. // this does nothing, unless overwritten in IncomingForm.parse
  148. return false;
  149. };
  150. IncomingForm.prototype.onPart = function(part) {
  151. // this method can be overwritten by the user
  152. this.handlePart(part);
  153. };
  154. IncomingForm.prototype.handlePart = function(part) {
  155. var self = this;
  156. if (part.filename === undefined) {
  157. var value = ''
  158. , decoder = new StringDecoder(this.encoding);
  159. part.on('data', function(buffer) {
  160. self._fieldsSize += buffer.length;
  161. if (self._fieldsSize > self.maxFieldsSize) {
  162. self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
  163. return;
  164. }
  165. value += decoder.write(buffer);
  166. });
  167. part.on('end', function() {
  168. self.emit('field', part.name, value);
  169. });
  170. return;
  171. }
  172. this._flushing++;
  173. var file = new File({
  174. path: this._uploadPath(part.filename),
  175. name: part.filename,
  176. type: part.mime,
  177. hash: self.hash
  178. });
  179. this.emit('fileBegin', part.name, file);
  180. file.open();
  181. this.openedFiles.push(file);
  182. part.on('data', function(buffer) {
  183. if (buffer.length == 0) {
  184. return;
  185. }
  186. self.pause();
  187. file.write(buffer, function() {
  188. self.resume();
  189. });
  190. });
  191. part.on('end', function() {
  192. file.end(function() {
  193. self._flushing--;
  194. self.emit('file', part.name, file);
  195. self._maybeEnd();
  196. });
  197. });
  198. };
  199. function dummyParser(self) {
  200. return {
  201. end: function () {
  202. self.ended = true;
  203. self._maybeEnd();
  204. return null;
  205. }
  206. };
  207. }
  208. IncomingForm.prototype._parseContentType = function() {
  209. if (this.bytesExpected === 0) {
  210. this._parser = dummyParser(this);
  211. return;
  212. }
  213. if (!this.headers['content-type']) {
  214. this._error(new Error('bad content-type header, no content-type'));
  215. return;
  216. }
  217. if (this.headers['content-type'].match(/octet-stream/i)) {
  218. this._initOctetStream();
  219. return;
  220. }
  221. if (this.headers['content-type'].match(/urlencoded/i)) {
  222. this._initUrlencoded();
  223. return;
  224. }
  225. if (this.headers['content-type'].match(/multipart/i)) {
  226. var m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i);
  227. if (m) {
  228. this._initMultipart(m[1] || m[2]);
  229. } else {
  230. this._error(new Error('bad content-type header, no multipart boundary'));
  231. }
  232. return;
  233. }
  234. if (this.headers['content-type'].match(/json/i)) {
  235. this._initJSONencoded();
  236. return;
  237. }
  238. this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type']));
  239. };
  240. IncomingForm.prototype._error = function(err) {
  241. if (this.error || this.ended) {
  242. return;
  243. }
  244. this.error = err;
  245. this.emit('error', err);
  246. if (Array.isArray(this.openedFiles)) {
  247. this.openedFiles.forEach(function(file) {
  248. file._writeStream.destroy();
  249. setTimeout(fs.unlink, 0, file.path, function(error) { });
  250. });
  251. }
  252. };
  253. IncomingForm.prototype._parseContentLength = function() {
  254. this.bytesReceived = 0;
  255. if (this.headers['content-length']) {
  256. this.bytesExpected = parseInt(this.headers['content-length'], 10);
  257. } else if (this.headers['transfer-encoding'] === undefined) {
  258. this.bytesExpected = 0;
  259. }
  260. if (this.bytesExpected !== null) {
  261. this.emit('progress', this.bytesReceived, this.bytesExpected);
  262. }
  263. };
  264. IncomingForm.prototype._newParser = function() {
  265. return new MultipartParser();
  266. };
  267. IncomingForm.prototype._initMultipart = function(boundary) {
  268. this.type = 'multipart';
  269. var parser = new MultipartParser(),
  270. self = this,
  271. headerField,
  272. headerValue,
  273. part;
  274. parser.initWithBoundary(boundary);
  275. parser.onPartBegin = function() {
  276. part = new Stream();
  277. part.readable = true;
  278. part.headers = {};
  279. part.name = null;
  280. part.filename = null;
  281. part.mime = null;
  282. part.transferEncoding = 'binary';
  283. part.transferBuffer = '';
  284. headerField = '';
  285. headerValue = '';
  286. };
  287. parser.onHeaderField = function(b, start, end) {
  288. headerField += b.toString(self.encoding, start, end);
  289. };
  290. parser.onHeaderValue = function(b, start, end) {
  291. headerValue += b.toString(self.encoding, start, end);
  292. };
  293. parser.onHeaderEnd = function() {
  294. headerField = headerField.toLowerCase();
  295. part.headers[headerField] = headerValue;
  296. var m = headerValue.match(/\bname="([^"]+)"/i);
  297. if (headerField == 'content-disposition') {
  298. if (m) {
  299. part.name = m[1];
  300. }
  301. part.filename = self._fileName(headerValue);
  302. } else if (headerField == 'content-type') {
  303. part.mime = headerValue;
  304. } else if (headerField == 'content-transfer-encoding') {
  305. part.transferEncoding = headerValue.toLowerCase();
  306. }
  307. headerField = '';
  308. headerValue = '';
  309. };
  310. parser.onHeadersEnd = function() {
  311. switch(part.transferEncoding){
  312. case 'binary':
  313. case '7bit':
  314. case '8bit':
  315. parser.onPartData = function(b, start, end) {
  316. part.emit('data', b.slice(start, end));
  317. };
  318. parser.onPartEnd = function() {
  319. part.emit('end');
  320. };
  321. break;
  322. case 'base64':
  323. parser.onPartData = function(b, start, end) {
  324. part.transferBuffer += b.slice(start, end).toString('ascii');
  325. /*
  326. four bytes (chars) in base64 converts to three bytes in binary
  327. encoding. So we should always work with a number of bytes that
  328. can be divided by 4, it will result in a number of buytes that
  329. can be divided vy 3.
  330. */
  331. var offset = parseInt(part.transferBuffer.length / 4, 10) * 4;
  332. part.emit('data', new Buffer(part.transferBuffer.substring(0, offset), 'base64'));
  333. part.transferBuffer = part.transferBuffer.substring(offset);
  334. };
  335. parser.onPartEnd = function() {
  336. part.emit('data', new Buffer(part.transferBuffer, 'base64'));
  337. part.emit('end');
  338. };
  339. break;
  340. default:
  341. return self._error(new Error('unknown transfer-encoding'));
  342. }
  343. self.onPart(part);
  344. };
  345. parser.onEnd = function() {
  346. self.ended = true;
  347. self._maybeEnd();
  348. };
  349. this._parser = parser;
  350. };
  351. IncomingForm.prototype._fileName = function(headerValue) {
  352. var m = headerValue.match(/\bfilename="(.*?)"($|; )/i);
  353. if (!m) return;
  354. var filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
  355. filename = filename.replace(/%22/g, '"');
  356. filename = filename.replace(/&#([\d]{4});/g, function(m, code) {
  357. return String.fromCharCode(code);
  358. });
  359. return filename;
  360. };
  361. IncomingForm.prototype._initUrlencoded = function() {
  362. this.type = 'urlencoded';
  363. var parser = new QuerystringParser(this.maxFields)
  364. , self = this;
  365. parser.onField = function(key, val) {
  366. self.emit('field', key, val);
  367. };
  368. parser.onEnd = function() {
  369. self.ended = true;
  370. self._maybeEnd();
  371. };
  372. this._parser = parser;
  373. };
  374. IncomingForm.prototype._initOctetStream = function() {
  375. this.type = 'octet-stream';
  376. var filename = this.headers['x-file-name'];
  377. var mime = this.headers['content-type'];
  378. var file = new File({
  379. path: this._uploadPath(filename),
  380. name: filename,
  381. type: mime
  382. });
  383. this.emit('fileBegin', filename, file);
  384. file.open();
  385. this._flushing++;
  386. var self = this;
  387. self._parser = new OctetParser();
  388. //Keep track of writes that haven't finished so we don't emit the file before it's done being written
  389. var outstandingWrites = 0;
  390. self._parser.on('data', function(buffer){
  391. self.pause();
  392. outstandingWrites++;
  393. file.write(buffer, function() {
  394. outstandingWrites--;
  395. self.resume();
  396. if(self.ended){
  397. self._parser.emit('doneWritingFile');
  398. }
  399. });
  400. });
  401. self._parser.on('end', function(){
  402. self._flushing--;
  403. self.ended = true;
  404. var done = function(){
  405. file.end(function() {
  406. self.emit('file', 'file', file);
  407. self._maybeEnd();
  408. });
  409. };
  410. if(outstandingWrites === 0){
  411. done();
  412. } else {
  413. self._parser.once('doneWritingFile', done);
  414. }
  415. });
  416. };
  417. IncomingForm.prototype._initJSONencoded = function() {
  418. this.type = 'json';
  419. var parser = new JSONParser()
  420. , self = this;
  421. if (this.bytesExpected) {
  422. parser.initWithLength(this.bytesExpected);
  423. }
  424. parser.onField = function(key, val) {
  425. self.emit('field', key, val);
  426. };
  427. parser.onEnd = function() {
  428. self.ended = true;
  429. self._maybeEnd();
  430. };
  431. this._parser = parser;
  432. };
  433. IncomingForm.prototype._uploadPath = function(filename) {
  434. var name = '';
  435. for (var i = 0; i < 32; i++) {
  436. name += Math.floor(Math.random() * 16).toString(16);
  437. }
  438. if (this.keepExtensions) {
  439. var ext = path.extname(filename);
  440. ext = ext.replace(/(\.[a-z0-9]+).*/i, '$1');
  441. name += ext;
  442. }
  443. return path.join(this.uploadDir, name);
  444. };
  445. IncomingForm.prototype._maybeEnd = function() {
  446. if (!this.ended || this._flushing || this.error) {
  447. return;
  448. }
  449. this.emit('end');
  450. };