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