@@ -7,6 +7,7 @@ var parseUrl = require('url').parse;
77var fs = require ( 'fs' ) ;
88var mime = require ( 'mime-types' ) ;
99var async = require ( 'async' ) ;
10+ var populate = require ( './populate.js' ) ;
1011
1112module . exports = FormData ;
1213function FormData ( ) {
@@ -26,9 +27,13 @@ FormData.LINE_BREAK = '\r\n';
2627FormData . DEFAULT_CONTENT_TYPE = 'application/octet-stream' ;
2728
2829FormData . prototype . append = function ( field , value , options ) {
29- options = ( typeof options === 'string' )
30- ? { filename : options }
31- : options || { } ;
30+
31+ options = options || { } ;
32+
33+ // allow filename as single option
34+ if ( typeof options == 'string' ) {
35+ options = { filename : options } ;
36+ }
3237
3338 var append = CombinedStream . prototype . append . bind ( this ) ;
3439
@@ -142,51 +147,26 @@ FormData.prototype._trackLength = function(header, value, options) {
142147 }
143148} ;
144149
150+ // TODO: Use request's response mime-type
145151FormData . prototype . _multiPartHeader = function ( field , value , options ) {
146152 // custom header specified (as string)?
147153 // it becomes responsible for boundary
148154 // (e.g. to handle extra CRLFs on .NET servers)
149- if ( options . header != null ) {
155+ if ( options . header ) {
150156 return options . header ;
151157 }
152158
159+ var contentDisposition = this . _getContentDisposition ( value , options ) ;
160+ var contentType = this . _getContentType ( value , options ) ;
161+
153162 var contents = '' ;
154163 var headers = {
155- 'Content-Disposition' : [ 'form-data' , 'name="' + field + '"' ] ,
156- 'Content-Type' : [ ]
164+ // add custom disposition as third element or keep it two elements if not
165+ 'Content-Disposition' : [ 'form-data' , 'name="' + field + '"' ] . concat ( contentDisposition || [ ] ) ,
166+ // if no content type. allow it to be empty array
167+ 'Content-Type' : [ ] . concat ( contentType || [ ] )
157168 } ;
158169
159- // fs- and request- streams have path property
160- // or use custom filename and/or contentType
161- // TODO: Use request's response mime-type
162- if ( options . filename || value . path ) {
163- headers [ 'Content-Disposition' ] . push (
164- 'filename="' + path . basename ( options . filename || value . path ) + '"'
165- ) ;
166- headers [ 'Content-Type' ] . push (
167- options . contentType ||
168- mime . lookup ( options . filename || value . path ) ||
169- FormData . DEFAULT_CONTENT_TYPE
170- ) ;
171- // http response has not
172- } else if ( value . readable && value . hasOwnProperty ( 'httpVersion' ) ) {
173- headers [ 'Content-Disposition' ] . push (
174- 'filename="' + path . basename ( value . client . _httpMessage . path ) + '"'
175- ) ;
176- headers [ 'Content-Type' ] . push (
177- options . contentType ||
178- value . headers [ 'content-type' ] ||
179- FormData . DEFAULT_CONTENT_TYPE
180- ) ;
181- } else if ( Buffer . isBuffer ( value ) ) {
182- headers [ 'Content-Type' ] . push (
183- options . contentType ||
184- FormData . DEFAULT_CONTENT_TYPE
185- ) ;
186- } else if ( options . contentType ) {
187- headers [ 'Content-Type' ] . push ( options . contentType ) ;
188- }
189-
190170 for ( var prop in headers ) {
191171 if ( headers [ prop ] . length ) {
192172 contents += prop + ': ' + headers [ prop ] . join ( '; ' ) + FormData . LINE_BREAK ;
@@ -196,6 +176,56 @@ FormData.prototype._multiPartHeader = function(field, value, options) {
196176 return '--' + this . getBoundary ( ) + FormData . LINE_BREAK + contents + FormData . LINE_BREAK ;
197177} ;
198178
179+ FormData . prototype . _getContentDisposition = function ( value , options ) {
180+
181+ var contentDisposition ;
182+
183+ // custom filename takes precedence
184+ // fs- and request- streams have path property
185+ var filename = options . filename || value . path ;
186+
187+ // or try http response
188+ if ( ! filename && value . readable && value . hasOwnProperty ( 'httpVersion' ) ) {
189+ filename = value . client . _httpMessage . path ;
190+ }
191+
192+ if ( filename ) {
193+ contentDisposition = 'filename="' + path . basename ( filename ) + '"' ;
194+ }
195+
196+ return contentDisposition ;
197+ } ;
198+
199+ // TODO: Streamline logic here
200+ // maybe have content-type always present
201+ FormData . prototype . _getContentType = function ( value , options ) {
202+
203+ // use custom content-type above all
204+ var contentType = options . contentType ;
205+
206+ // or try `path` from fs-, request- streams
207+ if ( ! contentType && value . path ) {
208+ contentType = mime . lookup ( value . path ) ;
209+ }
210+
211+ // or if it's http-reponse
212+ if ( ! contentType && value . readable && value . hasOwnProperty ( 'httpVersion' ) ) {
213+ contentType = value . headers [ 'content-type' ] ;
214+ }
215+
216+ // or guess it from the filename
217+ if ( ! contentType && options . filename ) {
218+ contentType = mime . lookup ( options . filename ) ;
219+ }
220+
221+ // fallback to the default content type if `value` is not simple value
222+ if ( ! contentType && typeof value == 'object' ) {
223+ contentType = FormData . DEFAULT_CONTENT_TYPE ;
224+ }
225+
226+ return contentType ;
227+ } ;
228+
199229FormData . prototype . _multiPartFooter = function ( ) {
200230 return function ( next ) {
201231 var footer = FormData . LINE_BREAK ;
@@ -374,13 +404,3 @@ FormData.prototype._error = function(err) {
374404 this . emit ( 'error' , err ) ;
375405 }
376406} ;
377-
378- // populates missing values
379- function populate ( dst , src ) {
380- for ( var prop in src ) {
381- if ( src . hasOwnProperty ( prop ) && ! dst [ prop ] ) {
382- dst [ prop ] = src [ prop ] ;
383- }
384- }
385- return dst ;
386- }
0 commit comments