Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for allowing POST request with formdata-node using response.body stream as body #1719

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/index.js
Expand Up @@ -403,7 +403,7 @@ function fixResponseChunkedTransferBadEnding(request, errorCallback) {
);
}

previousChunk = buf;
previousChunk = Buffer.from(buf);
};

socket.prependListener('close', onSocketClose);
Expand Down
68 changes: 68 additions & 0 deletions test/main.js
Expand Up @@ -1520,6 +1520,74 @@ describe('node-fetch', () => {
expect(json.body).to.contain('my_field=');
});

it('should allow POST request with formdata-node using response.body stream as body', async () => {
// Define a class that wraps a stream and its size
class BlobFromStream {
#stream;

constructor(stream, size) {
this.#stream = stream;
this.size = size;
}

// A method to retrieve the stream
stream() {
return this.#stream;
}

// A getter to return the string "Blob"
get [Symbol.toStringTag]() {
return 'Blob';
}
}

// Define the URL where the file to be uploaded can be downloaded from
const downloadUrl = `${base}64kb_file`;

// Use fetch() to get the file from the URL
const response = await fetch(downloadUrl);

// Extract the file size from the Content-Length header in the response
const contentLength = Number(response.headers.get('Content-Length'));

// Create a new FormDataNode object to construct the HTTP form data
const form = new FormDataNode();

// Append the file to the form data using the BlobFromStream class to create a Blob object
form.append(
'file', // field name for the file
new BlobFromStream(response.body, contentLength), // the file Blob object
`64kb_file.bin` // the filename to use for the file
);

// Define the URL where the file will be uploaded
const uploadUrl = `${base}multipart`;

// Define the options for the POST request
const options = {
method: 'POST',
body: form
};

// Use fetch() to upload the file to the server using the URL and options
const res = await fetch(uploadUrl, options);

// Extract the JSON response from the server
const json = await res.json();

// Check if the method used in the response is POST
expect(json.method).to.equal('POST');

// Check if the content-type header in the response starts with 'multipart/form-data; boundary='
expect(json.headers['content-type']).to.startWith('multipart/form-data; boundary=');

// Check if the length of the uploaded file matches the size of the downloaded file
expect(json.file.length).to.equal(contentLength);

// Check if the response body contains the filename of the uploaded file
expect(json.body).to.contain('file=64kb_file.bin');
});

it('should allow POST request with form-data as body and custom headers', async () => {
const form = new FormData();
form.append('a', '1');
Expand Down
15 changes: 12 additions & 3 deletions test/utils/server.js
Expand Up @@ -469,11 +469,13 @@ export default class TestServer {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
const bb = busboy({headers: request.headers});
let fileLength = 0;
bb.on('file', async (fieldName, file, info) => {
body += `${fieldName}=${info.filename}`;
// consume file data
// eslint-disable-next-line no-empty, no-unused-vars
for await (const c of file) {}
for await (const chunk of file) {
fileLength += chunk.length;
}
});
bb.on('field', (fieldName, value) => {
body += `${fieldName}=${value}`;
Expand All @@ -483,7 +485,8 @@ export default class TestServer {
method: request.method,
url: request.url,
headers: request.headers,
body
body,
file: {size: fileLength}
}));
});
request.pipe(bb);
Expand All @@ -494,5 +497,11 @@ export default class TestServer {
res.setHeader('Content-Type', 'text/plain');
res.end('ok');
}

if (p === '/64kb_file') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(Buffer.alloc(64 * 1024, '_'));
}
}
}