Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

form_data.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. var CombinedStream = require('combined-stream');
  2. var util = require('util');
  3. var path = require('path');
  4. var http = require('http');
  5. var https = require('https');
  6. var parseUrl = require('url').parse;
  7. var fs = require('fs');
  8. var Stream = require('stream').Stream;
  9. var mime = require('mime-types');
  10. var asynckit = require('asynckit');
  11. var populate = require('./populate.js');
  12. // Public API
  13. module.exports = FormData;
  14. // make it a Stream
  15. util.inherits(FormData, CombinedStream);
  16. /**
  17. * Create readable "multipart/form-data" streams.
  18. * Can be used to submit forms
  19. * and file uploads to other web applications.
  20. *
  21. * @constructor
  22. * @param {Object} options - Properties to be added/overriden for FormData and CombinedStream
  23. */
  24. function FormData(options) {
  25. if (!(this instanceof FormData)) {
  26. return new FormData(options);
  27. }
  28. this._overheadLength = 0;
  29. this._valueLength = 0;
  30. this._valuesToMeasure = [];
  31. CombinedStream.call(this);
  32. options = options || {};
  33. for (var option in options) {
  34. this[option] = options[option];
  35. }
  36. }
  37. FormData.LINE_BREAK = '\r\n';
  38. FormData.DEFAULT_CONTENT_TYPE = 'application/octet-stream';
  39. FormData.prototype.append = function(field, value, options) {
  40. options = options || {};
  41. // allow filename as single option
  42. if (typeof options == 'string') {
  43. options = {filename: options};
  44. }
  45. var append = CombinedStream.prototype.append.bind(this);
  46. // all that streamy business can't handle numbers
  47. if (typeof value == 'number') {
  48. value = '' + value;
  49. }
  50. // https://github.com/felixge/node-form-data/issues/38
  51. if (util.isArray(value)) {
  52. // Please convert your array into string
  53. // the way web server expects it
  54. this._error(new Error('Arrays are not supported.'));
  55. return;
  56. }
  57. var header = this._multiPartHeader(field, value, options);
  58. var footer = this._multiPartFooter();
  59. append(header);
  60. append(value);
  61. append(footer);
  62. // pass along options.knownLength
  63. this._trackLength(header, value, options);
  64. };
  65. FormData.prototype._trackLength = function(header, value, options) {
  66. var valueLength = 0;
  67. // used w/ getLengthSync(), when length is known.
  68. // e.g. for streaming directly from a remote server,
  69. // w/ a known file a size, and not wanting to wait for
  70. // incoming file to finish to get its size.
  71. if (options.knownLength != null) {
  72. valueLength += +options.knownLength;
  73. } else if (Buffer.isBuffer(value)) {
  74. valueLength = value.length;
  75. } else if (typeof value === 'string') {
  76. valueLength = Buffer.byteLength(value);
  77. }
  78. this._valueLength += valueLength;
  79. // @check why add CRLF? does this account for custom/multiple CRLFs?
  80. this._overheadLength +=
  81. Buffer.byteLength(header) +
  82. FormData.LINE_BREAK.length;
  83. // empty or either doesn't have path or not an http response or not a stream
  84. if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) && !(value instanceof Stream))) {
  85. return;
  86. }
  87. // no need to bother with the length
  88. if (!options.knownLength) {
  89. this._valuesToMeasure.push(value);
  90. }
  91. };
  92. FormData.prototype._lengthRetriever = function(value, callback) {
  93. if (value.hasOwnProperty('fd')) {
  94. // take read range into a account
  95. // `end` = Infinity –> read file till the end
  96. //
  97. // TODO: Looks like there is bug in Node fs.createReadStream
  98. // it doesn't respect `end` options without `start` options
  99. // Fix it when node fixes it.
  100. // https://github.com/joyent/node/issues/7819
  101. if (value.end != undefined && value.end != Infinity && value.start != undefined) {
  102. // when end specified
  103. // no need to calculate range
  104. // inclusive, starts with 0
  105. callback(null, value.end + 1 - (value.start ? value.start : 0));
  106. // not that fast snoopy
  107. } else {
  108. // still need to fetch file size from fs
  109. fs.stat(value.path, function(err, stat) {
  110. var fileSize;
  111. if (err) {
  112. callback(err);
  113. return;
  114. }
  115. // update final size based on the range options
  116. fileSize = stat.size - (value.start ? value.start : 0);
  117. callback(null, fileSize);
  118. });
  119. }
  120. // or http response
  121. } else if (value.hasOwnProperty('httpVersion')) {
  122. callback(null, +value.headers['content-length']);
  123. // or request stream http://github.com/mikeal/request
  124. } else if (value.hasOwnProperty('httpModule')) {
  125. // wait till response come back
  126. value.on('response', function(response) {
  127. value.pause();
  128. callback(null, +response.headers['content-length']);
  129. });
  130. value.resume();
  131. // something else
  132. } else {
  133. callback('Unknown stream');
  134. }
  135. };
  136. FormData.prototype._multiPartHeader = function(field, value, options) {
  137. // custom header specified (as string)?
  138. // it becomes responsible for boundary
  139. // (e.g. to handle extra CRLFs on .NET servers)
  140. if (typeof options.header == 'string') {
  141. return options.header;
  142. }
  143. var contentDisposition = this._getContentDisposition(value, options);
  144. var contentType = this._getContentType(value, options);
  145. var contents = '';
  146. var headers = {
  147. // add custom disposition as third element or keep it two elements if not
  148. 'Content-Disposition': ['form-data', 'name="' + field + '"'].concat(contentDisposition || []),
  149. // if no content type. allow it to be empty array
  150. 'Content-Type': [].concat(contentType || [])
  151. };
  152. // allow custom headers.
  153. if (typeof options.header == 'object') {
  154. populate(headers, options.header);
  155. }
  156. var header;
  157. for (var prop in headers) {
  158. if (!headers.hasOwnProperty(prop)) continue;
  159. header = headers[prop];
  160. // skip nullish headers.
  161. if (header == null) {
  162. continue;
  163. }
  164. // convert all headers to arrays.
  165. if (!Array.isArray(header)) {
  166. header = [header];
  167. }
  168. // add non-empty headers.
  169. if (header.length) {
  170. contents += prop + ': ' + header.join('; ') + FormData.LINE_BREAK;
  171. }
  172. }
  173. return '--' + this.getBoundary() + FormData.LINE_BREAK + contents + FormData.LINE_BREAK;
  174. };
  175. FormData.prototype._getContentDisposition = function(value, options) {
  176. var filename
  177. , contentDisposition
  178. ;
  179. if (typeof options.filepath === 'string') {
  180. // custom filepath for relative paths
  181. filename = path.normalize(options.filepath).replace(/\\/g, '/');
  182. } else if (options.filename || value.name || value.path) {
  183. // custom filename take precedence
  184. // formidable and the browser add a name property
  185. // fs- and request- streams have path property
  186. filename = path.basename(options.filename || value.name || value.path);
  187. } else if (value.readable && value.hasOwnProperty('httpVersion')) {
  188. // or try http response
  189. filename = path.basename(value.client._httpMessage.path || '');
  190. }
  191. if (filename) {
  192. contentDisposition = 'filename="' + filename + '"';
  193. }
  194. return contentDisposition;
  195. };
  196. FormData.prototype._getContentType = function(value, options) {
  197. // use custom content-type above all
  198. var contentType = options.contentType;
  199. // or try `name` from formidable, browser
  200. if (!contentType && value.name) {
  201. contentType = mime.lookup(value.name);
  202. }
  203. // or try `path` from fs-, request- streams
  204. if (!contentType && value.path) {
  205. contentType = mime.lookup(value.path);
  206. }
  207. // or if it's http-reponse
  208. if (!contentType && value.readable && value.hasOwnProperty('httpVersion')) {
  209. contentType = value.headers['content-type'];
  210. }
  211. // or guess it from the filepath or filename
  212. if (!contentType && (options.filepath || options.filename)) {
  213. contentType = mime.lookup(options.filepath || options.filename);
  214. }
  215. // fallback to the default content type if `value` is not simple value
  216. if (!contentType && typeof value == 'object') {
  217. contentType = FormData.DEFAULT_CONTENT_TYPE;
  218. }
  219. return contentType;
  220. };
  221. FormData.prototype._multiPartFooter = function() {
  222. return function(next) {
  223. var footer = FormData.LINE_BREAK;
  224. var lastPart = (this._streams.length === 0);
  225. if (lastPart) {
  226. footer += this._lastBoundary();
  227. }
  228. next(footer);
  229. }.bind(this);
  230. };
  231. FormData.prototype._lastBoundary = function() {
  232. return '--' + this.getBoundary() + '--' + FormData.LINE_BREAK;
  233. };
  234. FormData.prototype.getHeaders = function(userHeaders) {
  235. var header;
  236. var formHeaders = {
  237. 'content-type': 'multipart/form-data; boundary=' + this.getBoundary()
  238. };
  239. for (header in userHeaders) {
  240. if (userHeaders.hasOwnProperty(header)) {
  241. formHeaders[header.toLowerCase()] = userHeaders[header];
  242. }
  243. }
  244. return formHeaders;
  245. };
  246. FormData.prototype.setBoundary = function(boundary) {
  247. this._boundary = boundary;
  248. };
  249. FormData.prototype.getBoundary = function() {
  250. if (!this._boundary) {
  251. this._generateBoundary();
  252. }
  253. return this._boundary;
  254. };
  255. FormData.prototype.getBuffer = function() {
  256. var dataBuffer = new Buffer.alloc( 0 );
  257. var boundary = this.getBoundary();
  258. // Create the form content. Add Line breaks to the end of data.
  259. for (var i = 0, len = this._streams.length; i < len; i++) {
  260. if (typeof this._streams[i] !== 'function') {
  261. // Add content to the buffer.
  262. if(Buffer.isBuffer(this._streams[i])) {
  263. dataBuffer = Buffer.concat( [dataBuffer, this._streams[i]]);
  264. }else {
  265. dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(this._streams[i])]);
  266. }
  267. // Add break after content.
  268. if (typeof this._streams[i] !== 'string' || this._streams[i].substring( 2, boundary.length + 2 ) !== boundary) {
  269. dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(FormData.LINE_BREAK)] );
  270. }
  271. }
  272. }
  273. // Add the footer and return the Buffer object.
  274. return Buffer.concat( [dataBuffer, Buffer.from(this._lastBoundary())] );
  275. };
  276. FormData.prototype._generateBoundary = function() {
  277. // This generates a 50 character boundary similar to those used by Firefox.
  278. // They are optimized for boyer-moore parsing.
  279. var boundary = '--------------------------';
  280. for (var i = 0; i < 24; i++) {
  281. boundary += Math.floor(Math.random() * 10).toString(16);
  282. }
  283. this._boundary = boundary;
  284. };
  285. // Note: getLengthSync DOESN'T calculate streams length
  286. // As workaround one can calculate file size manually
  287. // and add it as knownLength option
  288. FormData.prototype.getLengthSync = function() {
  289. var knownLength = this._overheadLength + this._valueLength;
  290. // Don't get confused, there are 3 "internal" streams for each keyval pair
  291. // so it basically checks if there is any value added to the form
  292. if (this._streams.length) {
  293. knownLength += this._lastBoundary().length;
  294. }
  295. // https://github.com/form-data/form-data/issues/40
  296. if (!this.hasKnownLength()) {
  297. // Some async length retrievers are present
  298. // therefore synchronous length calculation is false.
  299. // Please use getLength(callback) to get proper length
  300. this._error(new Error('Cannot calculate proper length in synchronous way.'));
  301. }
  302. return knownLength;
  303. };
  304. // Public API to check if length of added values is known
  305. // https://github.com/form-data/form-data/issues/196
  306. // https://github.com/form-data/form-data/issues/262
  307. FormData.prototype.hasKnownLength = function() {
  308. var hasKnownLength = true;
  309. if (this._valuesToMeasure.length) {
  310. hasKnownLength = false;
  311. }
  312. return hasKnownLength;
  313. };
  314. FormData.prototype.getLength = function(cb) {
  315. var knownLength = this._overheadLength + this._valueLength;
  316. if (this._streams.length) {
  317. knownLength += this._lastBoundary().length;
  318. }
  319. if (!this._valuesToMeasure.length) {
  320. process.nextTick(cb.bind(this, null, knownLength));
  321. return;
  322. }
  323. asynckit.parallel(this._valuesToMeasure, this._lengthRetriever, function(err, values) {
  324. if (err) {
  325. cb(err);
  326. return;
  327. }
  328. values.forEach(function(length) {
  329. knownLength += length;
  330. });
  331. cb(null, knownLength);
  332. });
  333. };
  334. FormData.prototype.submit = function(params, cb) {
  335. var request
  336. , options
  337. , defaults = {method: 'post'}
  338. ;
  339. // parse provided url if it's string
  340. // or treat it as options object
  341. if (typeof params == 'string') {
  342. params = parseUrl(params);
  343. options = populate({
  344. port: params.port,
  345. path: params.pathname,
  346. host: params.hostname,
  347. protocol: params.protocol
  348. }, defaults);
  349. // use custom params
  350. } else {
  351. options = populate(params, defaults);
  352. // if no port provided use default one
  353. if (!options.port) {
  354. options.port = options.protocol == 'https:' ? 443 : 80;
  355. }
  356. }
  357. // put that good code in getHeaders to some use
  358. options.headers = this.getHeaders(params.headers);
  359. // https if specified, fallback to http in any other case
  360. if (options.protocol == 'https:') {
  361. request = https.request(options);
  362. } else {
  363. request = http.request(options);
  364. }
  365. // get content length and fire away
  366. this.getLength(function(err, length) {
  367. if (err && err !== 'Unknown stream') {
  368. this._error(err);
  369. return;
  370. }
  371. // add content length
  372. if (length) {
  373. request.setHeader('Content-Length', length);
  374. }
  375. this.pipe(request);
  376. if (cb) {
  377. var onResponse;
  378. var callback = function (error, responce) {
  379. request.removeListener('error', callback);
  380. request.removeListener('response', onResponse);
  381. return cb.call(this, error, responce);
  382. };
  383. onResponse = callback.bind(this, null);
  384. request.on('error', callback);
  385. request.on('response', onResponse);
  386. }
  387. }.bind(this));
  388. return request;
  389. };
  390. FormData.prototype._error = function(err) {
  391. if (!this.error) {
  392. this.error = err;
  393. this.pause();
  394. this.emit('error', err);
  395. }
  396. };
  397. FormData.prototype.toString = function () {
  398. return '[object FormData]';
  399. };