1 | 'use strict'
|
2 |
|
3 | var uuid = require('node-uuid')
|
4 | , CombinedStream = require('combined-stream')
|
5 | , isstream = require('isstream')
|
6 |
|
7 |
|
8 | function Multipart (request) {
|
9 | this.request = request
|
10 | this.boundary = uuid()
|
11 | this.chunked = false
|
12 | this.body = null
|
13 | }
|
14 |
|
15 | Multipart.prototype.isChunked = function (options) {
|
16 | var self = this
|
17 | , chunked = false
|
18 | , 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 |
|
46 | Multipart.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 |
|
66 | Multipart.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(new Buffer(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 |
|
101 | Multipart.prototype.onRequest = function (options) {
|
102 | var self = this
|
103 |
|
104 | var chunked = self.isChunked(options)
|
105 | , parts = options.data || options
|
106 |
|
107 | self.setHeaders(chunked)
|
108 | self.chunked = chunked
|
109 | self.body = self.build(parts, chunked)
|
110 | }
|
111 |
|
112 | exports.Multipart = Multipart
|