Skip to content

Commit b7b7dee

Browse files
committed
Merge branch 'master' of github.com:form-data/form-data into custom-stream
2 parents 6dd8624 + 55d90ce commit b7b7dee

13 files changed

Lines changed: 411 additions & 61 deletions

File tree

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules/*
2+
index.d.ts

.eslintrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"semi-spacing": 0,
3636
"no-multi-spaces": 0,
3737
"eqeqeq": 0,
38-
"no-mixed-requires": 0,
38+
"no-mixed-requires": 0
3939
},
4040
"env": {
4141
"node": true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
sftp-config.json
1111
yarn.lock
12+
package-lock.json
1213

1314
coverage/
1415
node_modules/

.travis.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ sudo: false
22

33
language: node_js
44
node_js:
5-
- "4"
65
- "6"
76
- "8"
8-
- "9"
7+
- "10"
8+
- "12"
99

1010
os:
1111
- osx
1212
- linux
13+
- windows
1314

1415
install:
1516
- travis_retry npm install
@@ -18,8 +19,8 @@ script:
1819
- uname -a
1920
- node --version
2021
- npm --version
21-
- npm run ci-lint
22-
- npm run ci-test
22+
- if [ "$TRAVIS_OS_NAME" != "windows" ]; then npm run ci-lint; fi
23+
- if [ "$TRAVIS_OS_NAME" = "windows" ]; then npm run test; else npm run ci-test; fi
2324
- npm run check
2425

2526
after_success:

Readme.md

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ The API of this library is inspired by the [XMLHttpRequest-2 FormData Interface]
66

77
[xhr2-fd]: http://dev.w3.org/2006/webapi/XMLHttpRequest-2/Overview.html#the-formdata-interface
88

9-
[![Linux Build](https://img.shields.io/travis/form-data/form-data/master.svg?label=linux:4.x-9.x)](https://travis-ci.org/form-data/form-data)
10-
[![MacOS Build](https://img.shields.io/travis/form-data/form-data/master.svg?label=macos:4.x-9.x)](https://travis-ci.org/form-data/form-data)
11-
[![Windows Build](https://img.shields.io/appveyor/ci/alexindigo/form-data/master.svg?label=windows:4.x-9.x)](https://ci.appveyor.com/project/alexindigo/form-data)
9+
[![Linux Build](https://img.shields.io/travis/form-data/form-data/master.svg?label=linux:6.x-12.x)](https://travis-ci.org/form-data/form-data)
10+
[![MacOS Build](https://img.shields.io/travis/form-data/form-data/master.svg?label=macos:6.x-12.x)](https://travis-ci.org/form-data/form-data)
11+
[![Windows Build](https://img.shields.io/travis/form-data/form-data/master.svg?label=windows:6.x-12.x)](https://travis-ci.org/form-data/form-data)
1212

1313
[![Coverage Status](https://img.shields.io/coveralls/form-data/form-data/master.svg?label=code+coverage)](https://coveralls.io/github/form-data/form-data?branch=master)
1414
[![Dependency Status](https://img.shields.io/david/form-data/form-data.svg)](https://david-dm.org/form-data/form-data)
15-
[![bitHound Overall Score](https://www.bithound.io/github/form-data/form-data/badges/score.svg)](https://www.bithound.io/github/form-data/form-data)
1615

1716
## Install
1817

@@ -185,6 +184,107 @@ form.submit({
185184
});
186185
```
187186

187+
### Methods
188+
189+
- [_Void_ append( **String** _field_, **Mixed** _value_ [, **Mixed** _options_] )](https://github.com/form-data/form-data#void-append-string-field-mixed-value--mixed-options-).
190+
- [_Headers_ getHeaders( [**Headers** _userHeaders_] )](https://github.com/form-data/form-data#array-getheaders-array-userheaders-)
191+
- [_String_ getBoundary()](https://github.com/form-data/form-data#string-getboundary)
192+
- [_Void_ setBoundary()](https://github.com/form-data/form-data#void-setboundary)
193+
- [_Buffer_ getBuffer()](https://github.com/form-data/form-data#buffer-getbuffer)
194+
- [_Integer_ getLengthSync()](https://github.com/form-data/form-data#integer-getlengthsync)
195+
- [_Integer_ getLength( **function** _callback_ )](https://github.com/form-data/form-data#integer-getlength-function-callback-)
196+
- [_Boolean_ hasKnownLength()](https://github.com/form-data/form-data#boolean-hasknownlength)
197+
- [_Request_ submit( _params_, **function** _callback_ )](https://github.com/form-data/form-data#request-submit-params-function-callback-)
198+
- [_String_ toString()](https://github.com/form-data/form-data#string-tostring)
199+
200+
#### _Void_ append( **String** _field_, **Mixed** _value_ [, **Mixed** _options_] )
201+
Append data to the form. You can submit about any format (string, integer, boolean, buffer, etc.). However, Arrays are not supported and need to be turned into strings by the user.
202+
```javascript
203+
var form = new FormData();
204+
form.append( 'my_string', 'my value' );
205+
form.append( 'my_integer', 1 );
206+
form.append( 'my_boolean', true );
207+
form.append( 'my_buffer', new Buffer(10) );
208+
form.append( 'my_array_as_json', JSON.stringify( ['bird','cute'] ) )
209+
```
210+
211+
You may provide a string for options, or an object.
212+
```javascript
213+
// Set filename by providing a string for options
214+
form.append( 'my_file', fs.createReadStream('/foo/bar.jpg'), 'bar.jpg' );
215+
216+
// provide an object.
217+
form.append( 'my_file', fs.createReadStream('/foo/bar.jpg'), {filename: 'bar.jpg', contentType: 'image/jpeg', knownLength: 19806} );
218+
```
219+
220+
#### _Headers_ getHeaders( [**Headers** _userHeaders_] )
221+
This method adds the correct `content-type` header to the provided array of `userHeaders`.
222+
223+
#### _String_ getBoundary()
224+
Return the boundary of the formData. By default, the boundary consists of 26 `-` followed by 24 numbers
225+
for example:
226+
```javascript
227+
--------------------------515890814546601021194782
228+
```
229+
230+
#### _Void_ setBoundary(String _boundary_)
231+
Set the boundary string, overriding the default behavior described above.
232+
233+
_Note: The boundary must be unique and may not appear in the data._
234+
235+
#### _Buffer_ getBuffer()
236+
Return the full formdata request package, as a Buffer. You can insert this Buffer in e.g. Axios to send multipart data.
237+
```javascript
238+
var form = new FormData();
239+
form.append( 'my_buffer', Buffer.from([0x4a,0x42,0x20,0x52,0x6f,0x63,0x6b,0x73]) );
240+
form.append( 'my_file', fs.readFileSync('/foo/bar.jpg') );
241+
242+
axios.post( 'https://example.com/path/to/api',
243+
form.getBuffer(),
244+
form.getHeaders()
245+
)
246+
```
247+
**Note:** Because the output is of type Buffer, you can only append types that are accepted by Buffer: *string, Buffer, ArrayBuffer, Array, or Array-like Object*. A ReadStream for example will result in an error.
248+
249+
#### _Integer_ getLengthSync()
250+
Same as `getLength` but synchronous.
251+
252+
_Note: getLengthSync __doesn't__ calculate streams length._
253+
254+
#### _Integer_ getLength( **function** _callback_ )
255+
Returns the `Content-Length` async. The callback is used to handle errors and continue once the length has been calculated
256+
```javascript
257+
this.getLength(function(err, length) {
258+
if (err) {
259+
this._error(err);
260+
return;
261+
}
262+
263+
// add content length
264+
request.setHeader('Content-Length', length);
265+
266+
...
267+
}.bind(this));
268+
```
269+
270+
#### _Boolean_ hasKnownLength()
271+
Checks if the length of added values is known.
272+
273+
#### _Request_ submit( _params_, **function** _callback_ )
274+
Submit the form to a web application.
275+
```javascript
276+
var form = new FormData();
277+
form.append( 'my_string', 'Hello World' );
278+
279+
form.submit( 'http://example.com/', function(err, res) {
280+
// res – response object (http.IncomingMessage) //
281+
res.resume();
282+
} );
283+
```
284+
285+
#### _String_ toString()
286+
Returns the form data as a string. Don't use this if you are sending files or buffers, use `getBuffer()` instead.
287+
188288
### Integration with other libraries
189289

190290
#### Request
@@ -224,12 +324,34 @@ fetch('http://example.com', { method: 'POST', body: form })
224324
});
225325
```
226326

327+
#### axios
328+
329+
In Node.js you can post a file using [axios](https://github.com/axios/axios):
330+
```javascript
331+
const form = new FormData();
332+
const stream = fs.createReadStream(PATH_TO_FILE);
333+
334+
form.append('image', stream);
335+
336+
// In Node.js environment you need to set boundary in the header field 'Content-Type' by calling method `getHeaders`
337+
const formHeaders = form.getHeaders();
338+
339+
axios.post('http://example.com', form, {
340+
headers: {
341+
...formHeaders,
342+
},
343+
})
344+
.then(response => response)
345+
.catch(error => error)
346+
```
347+
227348
## Notes
228349

229350
- ```getLengthSync()``` method DOESN'T calculate length for streams, use ```knownLength``` options as workaround.
230351
- ```getLength(cb)``` will send an error as first parameter of callback if stream length cannot be calculated (e.g. send in custom streams w/o using ```knownLength```).
231352
- ```sbumit``` will not add `content-length` if form length is unknown or not calculable.
232353
- Starting version `2.x` FormData has dropped support for `[email protected]`.
354+
- Starting version `3.x` FormData has dropped support for `[email protected]`.
233355

234356
## License
235357

appveyor.yml

Lines changed: 0 additions & 21 deletions
This file was deleted.

index.d.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Definitions by: Carlos Ballesteros Velasco <https://github.com/soywiz>
2+
// Leon Yu <https://github.com/leonyu>
3+
// BendingBender <https://github.com/BendingBender>
4+
// Maple Miao <https://github.com/mapleeit>
5+
6+
/// <reference types="node" />
7+
import * as stream from 'stream';
8+
import * as http from 'http';
9+
10+
export = FormData;
11+
12+
// Extracted because @types/node doesn't export interfaces.
13+
interface ReadableOptions {
14+
highWaterMark?: number;
15+
encoding?: string;
16+
objectMode?: boolean;
17+
read?(this: stream.Readable, size: number): void;
18+
destroy?(this: stream.Readable, error: Error | null, callback: (error: Error | null) => void): void;
19+
autoDestroy?: boolean;
20+
}
21+
22+
interface Options extends ReadableOptions {
23+
writable?: boolean;
24+
readable?: boolean;
25+
dataSize?: number;
26+
maxDataSize?: number;
27+
pauseStreams?: boolean;
28+
}
29+
30+
declare class FormData extends stream.Readable {
31+
constructor(options?: Options);
32+
append(key: string, value: any, options?: FormData.AppendOptions | string): void;
33+
getHeaders(userHeaders?: FormData.Headers): FormData.Headers;
34+
submit(
35+
params: string | FormData.SubmitOptions,
36+
callback?: (error: Error | null, response: http.IncomingMessage) => void
37+
): http.ClientRequest;
38+
getBuffer(): Buffer;
39+
setBoundary(boundary: string): void;
40+
getBoundary(): string;
41+
getLength(callback: (err: Error | null, length: number) => void): void;
42+
getLengthSync(): number;
43+
hasKnownLength(): boolean;
44+
}
45+
46+
declare namespace FormData {
47+
interface Headers {
48+
[key: string]: any;
49+
}
50+
51+
interface AppendOptions {
52+
header?: string | Headers;
53+
knownLength?: number;
54+
filename?: string;
55+
filepath?: string;
56+
contentType?: string;
57+
}
58+
59+
interface SubmitOptions extends http.RequestOptions {
60+
protocol?: 'https:' | 'http:';
61+
}
62+
}

lib/form_data.js

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ util.inherits(FormData, CombinedStream);
2626
*/
2727
function FormData(options) {
2828
if (!(this instanceof FormData)) {
29-
return new FormData();
29+
return new FormData(options);
3030
}
3131

3232
this._overheadLength = 0;
@@ -231,7 +231,7 @@ FormData.prototype._getContentDisposition = function(value, options) {
231231
filename = path.basename(options.filename || value.name || value.path);
232232
} else if (value.readable && value.hasOwnProperty('httpVersion')) {
233233
// or try http response
234-
filename = path.basename(value.client._httpMessage.path);
234+
filename = path.basename(value.client._httpMessage.path || '');
235235
}
236236

237237
if (filename) {
@@ -306,6 +306,10 @@ FormData.prototype.getHeaders = function(userHeaders) {
306306
return formHeaders;
307307
};
308308

309+
FormData.prototype.setBoundary = function(boundary) {
310+
this._boundary = boundary;
311+
};
312+
309313
FormData.prototype.getBoundary = function() {
310314
if (!this._boundary) {
311315
this._generateBoundary();
@@ -314,6 +318,32 @@ FormData.prototype.getBoundary = function() {
314318
return this._boundary;
315319
};
316320

321+
FormData.prototype.getBuffer = function() {
322+
var dataBuffer = new Buffer.alloc( 0 );
323+
var boundary = this.getBoundary();
324+
325+
// Create the form content. Add Line breaks to the end of data.
326+
for (var i = 0, len = this._streams.length; i < len; i++) {
327+
if (typeof this._streams[i] !== 'function') {
328+
329+
// Add content to the buffer.
330+
if(Buffer.isBuffer(this._streams[i])) {
331+
dataBuffer = Buffer.concat( [dataBuffer, this._streams[i]]);
332+
}else {
333+
dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(this._streams[i])]);
334+
}
335+
336+
// Add break after content.
337+
if (typeof this._streams[i] !== 'string' || this._streams[i].substring( 2, boundary.length + 2 ) !== boundary) {
338+
dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(FormData.LINE_BREAK)] );
339+
}
340+
}
341+
}
342+
343+
// Add the footer and return the Buffer object.
344+
return Buffer.concat( [dataBuffer, Buffer.from(this._lastBoundary())] );
345+
};
346+
317347
FormData.prototype._generateBoundary = function() {
318348
// This generates a 50 character boundary similar to those used by Firefox.
319349
// They are optimized for boyer-moore parsing.
@@ -439,8 +469,19 @@ FormData.prototype.submit = function(params, cb) {
439469

440470
this.pipe(request);
441471
if (cb) {
442-
request.on('error', cb);
443-
request.on('response', cb.bind(this, null));
472+
var onResponse;
473+
474+
var callback = function (error, responce) {
475+
request.removeListener('error', callback);
476+
request.removeListener('response', onResponse);
477+
478+
return cb.call(this, error, responce);
479+
};
480+
481+
onResponse = callback.bind(this, null);
482+
483+
request.on('error', callback);
484+
request.on('response', onResponse);
444485
}
445486
}.bind(this));
446487

0 commit comments

Comments
 (0)