multipart.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. 'use strict'
  2. var uuid = require('uuid')
  3. var CombinedStream = require('combined-stream')
  4. var isstream = require('isstream')
  5. var Buffer = require('safe-buffer').Buffer
  6. function Multipart (request) {
  7. this.request = request
  8. this.boundary = uuid()
  9. this.chunked = false
  10. this.body = null
  11. }
  12. Multipart.prototype.isChunked = function (options) {
  13. var self = this
  14. var chunked = false
  15. var parts = options.data || options
  16. if (!parts.forEach) {
  17. self.request.emit('error', new Error('Argument error, options.multipart.'))
  18. }
  19. if (options.chunked !== undefined) {
  20. chunked = options.chunked
  21. }
  22. if (self.request.getHeader('transfer-encoding') === 'chunked') {
  23. chunked = true
  24. }
  25. if (!chunked) {
  26. parts.forEach(function (part) {
  27. if (typeof part.body === 'undefined') {
  28. self.request.emit('error', new Error('Body attribute missing in multipart.'))
  29. }
  30. if (isstream(part.body)) {
  31. chunked = true
  32. }
  33. })
  34. }
  35. return chunked
  36. }
  37. Multipart.prototype.setHeaders = function (chunked) {
  38. var self = this
  39. if (chunked && !self.request.hasHeader('transfer-encoding')) {
  40. self.request.setHeader('transfer-encoding', 'chunked')
  41. }
  42. var header = self.request.getHeader('content-type')
  43. if (!header || header.indexOf('multipart') === -1) {
  44. self.request.setHeader('content-type', 'multipart/related; boundary=' + self.boundary)
  45. } else {
  46. if (header.indexOf('boundary') !== -1) {
  47. self.boundary = header.replace(/.*boundary=([^\s;]+).*/, '$1')
  48. } else {
  49. self.request.setHeader('content-type', header + '; boundary=' + self.boundary)
  50. }
  51. }
  52. }
  53. Multipart.prototype.build = function (parts, chunked) {
  54. var self = this
  55. var body = chunked ? new CombinedStream() : []
  56. function add (part) {
  57. if (typeof part === 'number') {
  58. part = part.toString()
  59. }
  60. return chunked ? body.append(part) : body.push(Buffer.from(part))
  61. }
  62. if (self.request.preambleCRLF) {
  63. add('\r\n')
  64. }
  65. parts.forEach(function (part) {
  66. var preamble = '--' + self.boundary + '\r\n'
  67. Object.keys(part).forEach(function (key) {
  68. if (key === 'body') { return }
  69. preamble += key + ': ' + part[key] + '\r\n'
  70. })
  71. preamble += '\r\n'
  72. add(preamble)
  73. add(part.body)
  74. add('\r\n')
  75. })
  76. add('--' + self.boundary + '--')
  77. if (self.request.postambleCRLF) {
  78. add('\r\n')
  79. }
  80. return body
  81. }
  82. Multipart.prototype.onRequest = function (options) {
  83. var self = this
  84. var chunked = self.isChunked(options)
  85. var parts = options.data || options
  86. self.setHeaders(chunked)
  87. self.chunked = chunked
  88. self.body = self.build(parts, chunked)
  89. }
  90. exports.Multipart = Multipart