Skip to content

Commit

Permalink
Merge branch 'master' of github.com:form-data/form-data into custom-s…
Browse files Browse the repository at this point in the history
…tream
  • Loading branch information
wxt2005 committed Jul 3, 2020
2 parents 6dd8624 + 55d90ce commit b7b7dee
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 61 deletions.
1 change: 1 addition & 0 deletions .eslintignore
@@ -1 +1,2 @@
node_modules/*
index.d.ts
2 changes: 1 addition & 1 deletion .eslintrc
Expand Up @@ -35,7 +35,7 @@
"semi-spacing": 0,
"no-multi-spaces": 0,
"eqeqeq": 0,
"no-mixed-requires": 0,
"no-mixed-requires": 0
},
"env": {
"node": true
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -9,6 +9,7 @@

sftp-config.json
yarn.lock
package-lock.json

coverage/
node_modules/
Expand Down
9 changes: 5 additions & 4 deletions .travis.yml
Expand Up @@ -2,14 +2,15 @@ sudo: false

language: node_js
node_js:
- "4"
- "6"
- "8"
- "9"
- "10"
- "12"

os:
- osx
- linux
- windows

install:
- travis_retry npm install
Expand All @@ -18,8 +19,8 @@ script:
- uname -a
- node --version
- npm --version
- npm run ci-lint
- npm run ci-test
- if [ "$TRAVIS_OS_NAME" != "windows" ]; then npm run ci-lint; fi
- if [ "$TRAVIS_OS_NAME" = "windows" ]; then npm run test; else npm run ci-test; fi
- npm run check

after_success:
Expand Down
130 changes: 126 additions & 4 deletions Readme.md
Expand Up @@ -6,13 +6,12 @@ The API of this library is inspired by the [XMLHttpRequest-2 FormData Interface]

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

[![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)
[![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)
[![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)
[![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)
[![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)
[![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)

[![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)
[![Dependency Status](https://img.shields.io/david/form-data/form-data.svg)](https://david-dm.org/form-data/form-data)
[![bitHound Overall Score](https://www.bithound.io/github/form-data/form-data/badges/score.svg)](https://www.bithound.io/github/form-data/form-data)

## Install

Expand Down Expand Up @@ -185,6 +184,107 @@ form.submit({
});
```

### Methods

- [_Void_ append( **String** _field_, **Mixed** _value_ [, **Mixed** _options_] )](https://github.com/form-data/form-data#void-append-string-field-mixed-value--mixed-options-).
- [_Headers_ getHeaders( [**Headers** _userHeaders_] )](https://github.com/form-data/form-data#array-getheaders-array-userheaders-)
- [_String_ getBoundary()](https://github.com/form-data/form-data#string-getboundary)
- [_Void_ setBoundary()](https://github.com/form-data/form-data#void-setboundary)
- [_Buffer_ getBuffer()](https://github.com/form-data/form-data#buffer-getbuffer)
- [_Integer_ getLengthSync()](https://github.com/form-data/form-data#integer-getlengthsync)
- [_Integer_ getLength( **function** _callback_ )](https://github.com/form-data/form-data#integer-getlength-function-callback-)
- [_Boolean_ hasKnownLength()](https://github.com/form-data/form-data#boolean-hasknownlength)
- [_Request_ submit( _params_, **function** _callback_ )](https://github.com/form-data/form-data#request-submit-params-function-callback-)
- [_String_ toString()](https://github.com/form-data/form-data#string-tostring)

#### _Void_ append( **String** _field_, **Mixed** _value_ [, **Mixed** _options_] )
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.
```javascript
var form = new FormData();
form.append( 'my_string', 'my value' );
form.append( 'my_integer', 1 );
form.append( 'my_boolean', true );
form.append( 'my_buffer', new Buffer(10) );
form.append( 'my_array_as_json', JSON.stringify( ['bird','cute'] ) )
```

You may provide a string for options, or an object.
```javascript
// Set filename by providing a string for options
form.append( 'my_file', fs.createReadStream('/foo/bar.jpg'), 'bar.jpg' );

// provide an object.
form.append( 'my_file', fs.createReadStream('/foo/bar.jpg'), {filename: 'bar.jpg', contentType: 'image/jpeg', knownLength: 19806} );
```

#### _Headers_ getHeaders( [**Headers** _userHeaders_] )
This method adds the correct `content-type` header to the provided array of `userHeaders`.

#### _String_ getBoundary()
Return the boundary of the formData. By default, the boundary consists of 26 `-` followed by 24 numbers
for example:
```javascript
--------------------------515890814546601021194782
```

#### _Void_ setBoundary(String _boundary_)
Set the boundary string, overriding the default behavior described above.

_Note: The boundary must be unique and may not appear in the data._

#### _Buffer_ getBuffer()
Return the full formdata request package, as a Buffer. You can insert this Buffer in e.g. Axios to send multipart data.
```javascript
var form = new FormData();
form.append( 'my_buffer', Buffer.from([0x4a,0x42,0x20,0x52,0x6f,0x63,0x6b,0x73]) );
form.append( 'my_file', fs.readFileSync('/foo/bar.jpg') );

axios.post( 'https://example.com/path/to/api',
form.getBuffer(),
form.getHeaders()
)
```
**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.

#### _Integer_ getLengthSync()
Same as `getLength` but synchronous.

_Note: getLengthSync __doesn't__ calculate streams length._

#### _Integer_ getLength( **function** _callback_ )
Returns the `Content-Length` async. The callback is used to handle errors and continue once the length has been calculated
```javascript
this.getLength(function(err, length) {
if (err) {
this._error(err);
return;
}

// add content length
request.setHeader('Content-Length', length);

...
}.bind(this));
```

#### _Boolean_ hasKnownLength()
Checks if the length of added values is known.

#### _Request_ submit( _params_, **function** _callback_ )
Submit the form to a web application.
```javascript
var form = new FormData();
form.append( 'my_string', 'Hello World' );

form.submit( 'http://example.com/', function(err, res) {
// res – response object (http.IncomingMessage) //
res.resume();
} );
```

#### _String_ toString()
Returns the form data as a string. Don't use this if you are sending files or buffers, use `getBuffer()` instead.

### Integration with other libraries

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

#### axios

In Node.js you can post a file using [axios](https://github.com/axios/axios):
```javascript
const form = new FormData();
const stream = fs.createReadStream(PATH_TO_FILE);

form.append('image', stream);

// In Node.js environment you need to set boundary in the header field 'Content-Type' by calling method `getHeaders`
const formHeaders = form.getHeaders();

axios.post('http://example.com', form, {
headers: {
...formHeaders,
},
})
.then(response => response)
.catch(error => error)
```

## Notes

- ```getLengthSync()``` method DOESN'T calculate length for streams, use ```knownLength``` options as workaround.
- ```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```).
- ```sbumit``` will not add `content-length` if form length is unknown or not calculable.
- Starting version `2.x` FormData has dropped support for `node@0.10.x`.
- Starting version `3.x` FormData has dropped support for `node@4.x`.

## License

Expand Down
21 changes: 0 additions & 21 deletions appveyor.yml

This file was deleted.

62 changes: 62 additions & 0 deletions index.d.ts
@@ -0,0 +1,62 @@
// Definitions by: Carlos Ballesteros Velasco <https://github.com/soywiz>
// Leon Yu <https://github.com/leonyu>
// BendingBender <https://github.com/BendingBender>
// Maple Miao <https://github.com/mapleeit>

/// <reference types="node" />
import * as stream from 'stream';
import * as http from 'http';

export = FormData;

// Extracted because @types/node doesn't export interfaces.
interface ReadableOptions {
highWaterMark?: number;
encoding?: string;
objectMode?: boolean;
read?(this: stream.Readable, size: number): void;
destroy?(this: stream.Readable, error: Error | null, callback: (error: Error | null) => void): void;
autoDestroy?: boolean;
}

interface Options extends ReadableOptions {
writable?: boolean;
readable?: boolean;
dataSize?: number;
maxDataSize?: number;
pauseStreams?: boolean;
}

declare class FormData extends stream.Readable {
constructor(options?: Options);
append(key: string, value: any, options?: FormData.AppendOptions | string): void;
getHeaders(userHeaders?: FormData.Headers): FormData.Headers;
submit(
params: string | FormData.SubmitOptions,
callback?: (error: Error | null, response: http.IncomingMessage) => void
): http.ClientRequest;
getBuffer(): Buffer;
setBoundary(boundary: string): void;
getBoundary(): string;
getLength(callback: (err: Error | null, length: number) => void): void;
getLengthSync(): number;
hasKnownLength(): boolean;
}

declare namespace FormData {
interface Headers {
[key: string]: any;
}

interface AppendOptions {
header?: string | Headers;
knownLength?: number;
filename?: string;
filepath?: string;
contentType?: string;
}

interface SubmitOptions extends http.RequestOptions {
protocol?: 'https:' | 'http:';
}
}
49 changes: 45 additions & 4 deletions lib/form_data.js
Expand Up @@ -26,7 +26,7 @@ util.inherits(FormData, CombinedStream);
*/
function FormData(options) {
if (!(this instanceof FormData)) {
return new FormData();
return new FormData(options);
}

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

if (filename) {
Expand Down Expand Up @@ -306,6 +306,10 @@ FormData.prototype.getHeaders = function(userHeaders) {
return formHeaders;
};

FormData.prototype.setBoundary = function(boundary) {
this._boundary = boundary;
};

FormData.prototype.getBoundary = function() {
if (!this._boundary) {
this._generateBoundary();
Expand All @@ -314,6 +318,32 @@ FormData.prototype.getBoundary = function() {
return this._boundary;
};

FormData.prototype.getBuffer = function() {
var dataBuffer = new Buffer.alloc( 0 );
var boundary = this.getBoundary();

// Create the form content. Add Line breaks to the end of data.
for (var i = 0, len = this._streams.length; i < len; i++) {
if (typeof this._streams[i] !== 'function') {

// Add content to the buffer.
if(Buffer.isBuffer(this._streams[i])) {
dataBuffer = Buffer.concat( [dataBuffer, this._streams[i]]);
}else {
dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(this._streams[i])]);
}

// Add break after content.
if (typeof this._streams[i] !== 'string' || this._streams[i].substring( 2, boundary.length + 2 ) !== boundary) {
dataBuffer = Buffer.concat( [dataBuffer, Buffer.from(FormData.LINE_BREAK)] );
}
}
}

// Add the footer and return the Buffer object.
return Buffer.concat( [dataBuffer, Buffer.from(this._lastBoundary())] );
};

FormData.prototype._generateBoundary = function() {
// This generates a 50 character boundary similar to those used by Firefox.
// They are optimized for boyer-moore parsing.
Expand Down Expand Up @@ -439,8 +469,19 @@ FormData.prototype.submit = function(params, cb) {

this.pipe(request);
if (cb) {
request.on('error', cb);
request.on('response', cb.bind(this, null));
var onResponse;

var callback = function (error, responce) {
request.removeListener('error', callback);
request.removeListener('response', onResponse);

return cb.call(this, error, responce);
};

onResponse = callback.bind(this, null);

request.on('error', callback);
request.on('response', onResponse);
}
}.bind(this));

Expand Down

0 comments on commit b7b7dee

Please sign in to comment.