Permalink
Browse files

Merge pull request #17 from DocuSignDev/master

Allow custom headers and pre-known length in parts
  • Loading branch information...
alexindigo committed Dec 5, 2012
2 parents 5c0387b + b16d14e commit f180a85983ec9dce99958fd51d5db0fbe5219555
Showing with 128 additions and 27 deletions.
  1. +53 −27 lib/form_data.js
  2. +75 −0 test/integration/test-custom-headers.js
View
@@ -20,44 +20,61 @@ util.inherits(FormData, CombinedStream);
FormData.LINE_BREAK = '\r\n';
-FormData.prototype.append = function(field, value) {
+FormData.prototype.append = function(field, value, options) {
+ options = options || {};
+
var append = CombinedStream.prototype.append.bind(this);
// all that streamy business can't handle numbers
if (typeof value == 'number') value = ''+value;
- var header = this._multiPartHeader(field, value);
- var footer = this._multiPartFooter(field, value);
+ var header = this._multiPartHeader(field, value, options);
+ var footer = this._multiPartFooter(field, value, options);
append(header);
append(value);
append(footer);
- this._trackLength(header, value)
+ // pass along options.knownLength
+ this._trackLength(header, value, options);
};
-FormData.prototype._trackLength = function(header, value) {
+FormData.prototype._trackLength = function(header, value, options) {
var valueLength = 0;
- if (Buffer.isBuffer(value)) {
+
+ // used w/ trackLengthSync(), when length is known.
+ // e.g. for streaming directly from a remote server,
+ // w/ a known file a size, and not wanting to wait for
+ // incoming file to finish to get its size.
+ if (options.knownLength != null) {
+ valueLength += +options.knownLength;
+ } else if (Buffer.isBuffer(value)) {
valueLength = value.length;
} else if (typeof value === 'string') {
valueLength = Buffer.byteLength(value);
}
this._valueLength += valueLength;
+
+ // @check why add CRLF? does this account for custom/multiple CRLFs?
this._overheadLength +=
Buffer.byteLength(header) +
+ FormData.LINE_BREAK.length;
- // empty or ethier doesn't have path or not an http response
+ // empty or either doesn't have path or not an http response
if (!value || ( !value.path && !(value.readable && value.hasOwnProperty('httpVersion')) )) {
return;
}
this._lengthRetrievers.push(function(next) {
+ // do we already know the size?
+ // 0 additional leaves value from getSyncLength()
+ if (options.knownLength != null) {
+ next(null, 0);
+
// check if it's local file
- if (value.hasOwnProperty('fd')) {
+ } else if (value.hasOwnProperty('fd')) {
fs.stat(value.path, function(err, stat) {
if (err) {
next(err);
@@ -87,31 +104,40 @@ FormData.prototype._trackLength = function(header, value) {
});
};
-FormData.prototype._multiPartHeader = function(field, value) {
+FormData.prototype._multiPartHeader = function(field, value, options) {
var boundary = this.getBoundary();
- var header =
- '--' + boundary + FormData.LINE_BREAK +
- 'Content-Disposition: form-data; name="' + field + '"';
-
- // fs- and request- streams have path property
- // TODO: Use request's response mime-type
- if (value.path) {
- header +=
- '; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK +
- 'Content-Type: ' + mime.lookup(value.path);
-
- // http response has not
- } else if (value.readable && value.hasOwnProperty('httpVersion')) {
- header +=
- '; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +
- 'Content-Type: ' + value.headers['content-type'];
+ var header = '';
+
+ // custom header specified (as string)?
+ // it becomes responsible for boundary
+ // (e.g. to handle extra CRLFs on .NET servers)
+ if (options.header != null) {
+ header = options.header;
+ } else {
+ header += '--' + boundary + FormData.LINE_BREAK +
+ 'Content-Disposition: form-data; name="' + field + '"';
+
+ // fs- and request- streams have path property
+ // TODO: Use request's response mime-type
+ if (value.path) {
+ header +=
+ '; filename="' + path.basename(value.path) + '"' + FormData.LINE_BREAK +
+ 'Content-Type: ' + mime.lookup(value.path);
+
+ // http response has not
+ } else if (value.readable && value.hasOwnProperty('httpVersion')) {
+ header +=
+ '; filename="' + path.basename(value.client._httpMessage.path) + '"' + FormData.LINE_BREAK +
+ 'Content-Type: ' + value.headers['content-type'];
+ }
+
+ header += FormData.LINE_BREAK + FormData.LINE_BREAK;
}
- header += FormData.LINE_BREAK + FormData.LINE_BREAK;
return header;
};
-FormData.prototype._multiPartFooter = function(field, value) {
+FormData.prototype._multiPartFooter = function(field, value, options) {
return function(next) {
var footer = FormData.LINE_BREAK;
@@ -0,0 +1,75 @@
+/*
+test custom headers, added in pull request:
+https://github.com/felixge/node-form-data/pull/17
+*/
+
+var common = require('../common');
+var assert = common.assert;
+var http = require('http');
+
+var FormData = require(common.dir.lib + '/form_data');
+
+var CRLF = '\r\n';
+
+var testHeader = 'X-Test-Fake: 123';
+
+var expectedLength;
+
+
+var server = http.createServer(function(req, res) {
+ var data = '';
+ req.setEncoding('utf8');
+
+ req.on('data', function(d) {
+ data += d;
+ });
+
+ req.on('end', function() {
+ assert.ok( data.indexOf( testHeader ) != -1 );
+
+ // content-length would be 1000+ w/actual buffer size,
+ // but smaller w/overridden size.
+ assert.ok( typeof req.headers['content-length'] !== 'undefined' );
+ assert.equal(req.headers['content-length'], expectedLength);
+
+ res.writeHead(200);
+ res.end('done');
+ });
+});
+
+
+server.listen(common.port, function() {
+ var form = new FormData();
+
+ var options = {
+ header:
+ CRLF + '--' + form.getBoundary() + CRLF +
+ testHeader +
+ CRLF + CRLF,
+
+ // override content-length,
+ // much lower than actual buffer size (1000)
+ knownLength: 1
+ };
+
+ var bufferData = [];
+ for (var z = 0; z < 1000; z++) {
+ bufferData.push(1);
+ }
+ var buffer = new Buffer(bufferData);
+
+ form.append('my_buffer', buffer, options);
+
+ // (available to req handler)
+ expectedLength = form._lastBoundary().length + form._overheadLength + options.knownLength;
+
+ form.submit('http://localhost:' + common.port + '/', function(err, res) {
+ if (err) {
+ throw err;
+ }
+
+ assert.strictEqual(res.statusCode, 200);
+ server.close();
+ });
+
+});

0 comments on commit f180a85

Please sign in to comment.