Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #17 from DocuSignDev/master

Allow custom headers and pre-known length in parts
  • Loading branch information...
commit f180a85983ec9dce99958fd51d5db0fbe5219555 2 parents 5c0387b + b16d14e
Alex Indigo authored December 04, 2012
80  lib/form_data.js
@@ -20,44 +20,61 @@ util.inherits(FormData, CombinedStream);
20 20
 
21 21
 FormData.LINE_BREAK = '\r\n';
22 22
 
23  
-FormData.prototype.append = function(field, value) {
  23
+FormData.prototype.append = function(field, value, options) {
  24
+  options = options || {};
  25
+
24 26
   var append = CombinedStream.prototype.append.bind(this);
25 27
 
26 28
   // all that streamy business can't handle numbers
27 29
   if (typeof value == 'number') value = ''+value;
28 30
 
29  
-  var header = this._multiPartHeader(field, value);
30  
-  var footer = this._multiPartFooter(field, value);
  31
+  var header = this._multiPartHeader(field, value, options);
  32
+  var footer = this._multiPartFooter(field, value, options);
31 33
 
32 34
   append(header);
33 35
   append(value);
34 36
   append(footer);
35 37
 
36  
-  this._trackLength(header, value)
  38
+  // pass along options.knownLength
  39
+  this._trackLength(header, value, options);
37 40
 };
38 41
 
39  
-FormData.prototype._trackLength = function(header, value) {
  42
+FormData.prototype._trackLength = function(header, value, options) {
40 43
   var valueLength = 0;
41  
-  if (Buffer.isBuffer(value)) {
  44
+  
  45
+  // used w/ trackLengthSync(), when length is known.
  46
+  // e.g. for streaming directly from a remote server,
  47
+  // w/ a known file a size, and not wanting to wait for
  48
+  // incoming file to finish to get its size.
  49
+  if (options.knownLength != null) {
  50
+    valueLength += +options.knownLength;
  51
+  } else if (Buffer.isBuffer(value)) {
42 52
     valueLength = value.length;
43 53
   } else if (typeof value === 'string') {
44 54
     valueLength = Buffer.byteLength(value);
45 55
   }
46 56
 
47 57
   this._valueLength += valueLength;
  58
+
  59
+  // @check why add CRLF? does this account for custom/multiple CRLFs?
48 60
   this._overheadLength +=
49 61
     Buffer.byteLength(header) +
50 62
     + FormData.LINE_BREAK.length;
51 63
 
52  
-  // empty or ethier doesn't have path or not an http response
  64
+  // empty or either doesn't have path or not an http response
53 65
   if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) {
54 66
     return;
55 67
   }
56 68
 
57 69
   this._lengthRetrievers.push(function(next) {
58 70
 
  71
+    // do we already know the size?
  72
+    // 0 additional leaves value from getSyncLength()
  73
+    if (options.knownLength != null) {
  74
+      next(null, 0);
  75
+
59 76
     // check if it's local file
60  
-    if (value.hasOwnProperty('fd')) {
  77
+    } else if (value.hasOwnProperty('fd')) {
61 78
       fs.stat(value.path, function(err, stat) {
62 79
         if (err) {
63 80
           next(err);
@@ -87,31 +104,40 @@ FormData.prototype._trackLength = function(header, value) {
87 104
   });
88 105
 };
89 106
 
90  
-FormData.prototype._multiPartHeader = function(field, value) {
  107
+FormData.prototype._multiPartHeader = function(field, value, options) {
91 108
   var boundary = this.getBoundary();
92  
-  var header =
93  
-    '--' + boundary + FormData.LINE_BREAK +
94  
-    'Content-Disposition: form-data; name="' + field + '"';
95  
-
96  
-  // fs- and request- streams have path property
97  
-  // TODO: Use request's response mime-type
98  
-  if (value.path) {
99  
-    header +=
100  
-      '; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK +
101  
-      'Content-Type: ' + mime.lookup(value.path);
102  
-
103  
-  // http response has not
104  
-  } else if (value.readable && value.hasOwnProperty('httpVersion')) {
105  
-    header +=
106  
-      '; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +
107  
-      'Content-Type: ' + value.headers['content-type'];
  109
+  var header = '';
  110
+
  111
+  // custom header specified (as string)?
  112
+  // it becomes responsible for boundary
  113
+  // (e.g. to handle extra CRLFs on .NET servers)
  114
+  if (options.header != null) {
  115
+    header = options.header;
  116
+  } else {
  117
+    header += '--' + boundary + FormData.LINE_BREAK +
  118
+      'Content-Disposition: form-data; name="' + field + '"';
  119
+
  120
+    // fs- and request- streams have path property
  121
+    // TODO: Use request's response mime-type
  122
+    if (value.path) {
  123
+      header +=
  124
+        '; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK +
  125
+        'Content-Type: ' + mime.lookup(value.path);
  126
+
  127
+    // http response has not
  128
+    } else if (value.readable && value.hasOwnProperty('httpVersion')) {
  129
+      header +=
  130
+        '; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +
  131
+        'Content-Type: ' + value.headers['content-type'];
  132
+    }
  133
+
  134
+    header += FormData.LINE_BREAK + FormData.LINE_BREAK;
108 135
   }
109 136
 
110  
-  header += FormData.LINE_BREAK + FormData.LINE_BREAK;
111 137
   return header;
112 138
 };
113 139
 
114  
-FormData.prototype._multiPartFooter = function(field, value) {
  140
+FormData.prototype._multiPartFooter = function(field, value, options) {
115 141
   return function(next) {
116 142
     var footer = FormData.LINE_BREAK;
117 143
 
75  test/integration/test-custom-headers.js
... ...
@@ -0,0 +1,75 @@
  1
+/*
  2
+test custom headers, added in pull request:
  3
+https://github.com/felixge/node-form-data/pull/17
  4
+*/
  5
+
  6
+var common = require('../common');
  7
+var assert = common.assert;
  8
+var http = require('http');
  9
+
  10
+var FormData = require(common.dir.lib + '/form_data');
  11
+
  12
+var CRLF = '\r\n';
  13
+
  14
+var testHeader = 'X-Test-Fake: 123';
  15
+
  16
+var expectedLength;
  17
+
  18
+
  19
+var server = http.createServer(function(req, res) {
  20
+  var data = '';
  21
+  req.setEncoding('utf8');
  22
+
  23
+  req.on('data', function(d) {
  24
+    data += d;
  25
+  });
  26
+
  27
+  req.on('end', function() {
  28
+    assert.ok( data.indexOf( testHeader ) != -1 );
  29
+
  30
+    // content-length would be 1000+ w/actual buffer size,
  31
+    // but smaller w/overridden size.
  32
+    assert.ok( typeof req.headers['content-length'] !== 'undefined' );
  33
+    assert.equal(req.headers['content-length'], expectedLength);
  34
+
  35
+    res.writeHead(200);
  36
+    res.end('done');
  37
+  });
  38
+});
  39
+
  40
+
  41
+server.listen(common.port, function() {
  42
+  var form = new FormData();
  43
+
  44
+  var options = {
  45
+    header:
  46
+      CRLF + '--' + form.getBoundary() + CRLF +
  47
+      testHeader +
  48
+      CRLF + CRLF,
  49
+
  50
+    // override content-length,
  51
+    // much lower than actual buffer size (1000)
  52
+    knownLength: 1
  53
+  };
  54
+
  55
+  var bufferData = [];
  56
+  for (var z = 0; z < 1000; z++) {
  57
+    bufferData.push(1);
  58
+  }
  59
+  var buffer = new Buffer(bufferData);
  60
+
  61
+  form.append('my_buffer', buffer, options);
  62
+
  63
+  // (available to req handler)
  64
+  expectedLength = form._lastBoundary().length + form._overheadLength + options.knownLength;
  65
+
  66
+  form.submit('http://localhost:' + common.port + '/', function(err, res) {
  67
+    if (err) {
  68
+      throw err;
  69
+    }
  70
+
  71
+    assert.strictEqual(res.statusCode, 200);
  72
+    server.close();
  73
+  });
  74
+
  75
+});

0 notes on commit f180a85

Please sign in to comment.
Something went wrong with that request. Please try again.