Skip to content

Commit

Permalink
fix: [#1484] Throw error in FormData.append when value parameter type…
Browse files Browse the repository at this point in the history
… is incorrect (#1484)

* fix: throw error when append parameter type incorrect

* fix: lint

* chore: [#1484] Some additional fixes

* chore: [#1484] Some additional fixes

---------

Co-authored-by: David Ortner <[email protected]>
  • Loading branch information
btea and capricorn86 authored Aug 29, 2024
1 parent 5b23cc1 commit df69f9e
Show file tree
Hide file tree
Showing 12 changed files with 58 additions and 24 deletions.
2 changes: 1 addition & 1 deletion packages/happy-dom/src/fetch/Request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ export default class Request implements Request {

if (contentType?.startsWith('application/x-www-form-urlencoded')) {
const parameters = new URLSearchParams(await this.text());
const formData = new FormData();
const formData = new window.FormData();

for (const [key, value] of parameters) {
formData.append(key, value);
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/src/fetch/Response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export default class Response implements Response {

if (contentType?.startsWith('application/x-www-form-urlencoded')) {
const parameters = new URLSearchParams(await this.text());
const formData = new FormData();
const formData = new window.FormData();

for (const [key, value] of parameters) {
formData.append(key, value);
Expand Down
3 changes: 2 additions & 1 deletion packages/happy-dom/src/fetch/multipart/MultipartReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const CHARACTER_CODE = {
* https://github.com/node-fetch/node-fetch/blob/main/src/utils/multipart-parser.js (MIT)
*/
export default class MultipartReader {
private formData = new FormData();
private formData: FormData;
private boundary: Uint8Array;
private boundaryIndex = 0;
private state = MultiparParserStateEnum.boundary;
Expand Down Expand Up @@ -50,6 +50,7 @@ export default class MultipartReader {
const boundaryHeader = `--${boundary}`;
this.window = window;
this.boundary = new Uint8Array(boundaryHeader.length);
this.formData = new window.FormData();

for (let i = 0, max = boundaryHeader.length; i < max; i++) {
this.boundary[i] = boundaryHeader.charCodeAt(i);
Expand Down
4 changes: 2 additions & 2 deletions packages/happy-dom/src/file/Blob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ export default class Blob {
/**
* Constructor.
*
* @param bits Bits.
* @param [bits] Bits.
* @param [options] Options.
* @param [options.type] MIME type.
*/
constructor(
bits: (ArrayBuffer | ArrayBufferView | Blob | Buffer | string)[],
bits?: (ArrayBuffer | ArrayBufferView | Blob | Buffer | string)[],
options?: { type?: string }
) {
const buffers = [];
Expand Down
8 changes: 8 additions & 0 deletions packages/happy-dom/src/file/File.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ export default class File extends Blob {
name: string,
options?: { type?: string; lastModified?: number }
) {
if (arguments.length < 2) {
throw new TypeError(
"Failed to construct 'File': 2 arguments required, but only " +
arguments.length +
' present.'
);
}

super(bits, options);

this.name = name.replace(/\//g, ':');
Expand Down
21 changes: 15 additions & 6 deletions packages/happy-dom/src/form-data/FormData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as PropertySymbol from '../PropertySymbol.js';
import File from '../file/File.js';
import HTMLInputElement from '../nodes/html-input-element/HTMLInputElement.js';
import HTMLFormElement from '../nodes/html-form-element/HTMLFormElement.js';
import BrowserWindow from '../window/BrowserWindow.js';

type FormDataEntry = {
name: string;
Expand All @@ -15,6 +16,9 @@ type FormDataEntry = {
* @see https://developer.mozilla.org/en-US/docs/Web/API/FormData
*/
export default class FormData implements Iterable<[string, string | File]> {
// Injected by WindowClassExtender
protected declare [PropertySymbol.window]: BrowserWindow;

#entries: FormDataEntry[] = [];

/**
Expand Down Expand Up @@ -96,6 +100,11 @@ export default class FormData implements Iterable<[string, string | File]> {
* @param [filename] Filename.
*/
public append(name: string, value: string | Blob | File, filename?: string): void {
if (filename && !(value instanceof Blob)) {
throw new this[PropertySymbol.window].TypeError(
'Failed to execute "append" on "FormData": parameter 2 is not of type "Blob".'
);
}
this.#entries.push({
name,
value: this.#parseValue(value, filename)
Expand Down Expand Up @@ -232,12 +241,6 @@ export default class FormData implements Iterable<[string, string | File]> {
* @returns Parsed value.
*/
#parseValue(value: string | Blob | File, filename?: string): string | File {
if (value instanceof Blob && !(value instanceof File)) {
const file = new File([], 'blob', { type: value.type });
file[PropertySymbol.buffer] = value[PropertySymbol.buffer];
return file;
}

if (value instanceof File) {
if (filename) {
const file = new File([], filename, { type: value.type, lastModified: value.lastModified });
Expand All @@ -247,6 +250,12 @@ export default class FormData implements Iterable<[string, string | File]> {
return value;
}

if (value instanceof Blob) {
const file = new File([], 'blob', { type: value.type });
file[PropertySymbol.buffer] = value[PropertySymbol.buffer];
return file;
}

return String(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import HTMLSelectElement from '../html-select-element/HTMLSelectElement.js';
import HTMLButtonElement from '../html-button-element/HTMLButtonElement.js';
import IBrowserFrame from '../../browser/types/IBrowserFrame.js';
import BrowserFrameNavigator from '../../browser/utilities/BrowserFrameNavigator.js';
import FormData from '../../form-data/FormData.js';
import BrowserWindow from '../../window/BrowserWindow.js';
import THTMLFormControlElement from './THTMLFormControlElement.js';
import QuerySelector from '../../query-selector/QuerySelector.js';
Expand Down Expand Up @@ -584,7 +583,7 @@ export default class HTMLFormElement extends HTMLElement {
}

const method = submitter?.formMethod || this.method;
const formData = new FormData(this);
const formData = new this[PropertySymbol.window].FormData(this);
let targetFrame: IBrowserFrame;

switch (submitter?.formTarget || this.target) {
Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/test/fetch/Fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3285,7 +3285,7 @@ describe('Fetch', () => {

it('Supports POST request with body as FormData.', async () => {
const window = new Window({ url: 'https://localhost:8080/' });
const formData = new FormData();
const formData = new window.FormData();

vi.spyOn(Math, 'random').mockImplementation(() => 0.8);

Expand Down
8 changes: 4 additions & 4 deletions packages/happy-dom/test/fetch/Request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ describe('Request', () => {

describe('formData()', () => {
it('Returns FormData for FormData object (multipart)', async () => {
const formData = new FormData();
const formData = new window.FormData();
formData.append('some', 'test');
const request = new window.Request(TEST_URL, { method: 'POST', body: formData });
const formDataResponse = await request.formData();
Expand Down Expand Up @@ -710,7 +710,7 @@ describe('Request', () => {
});

it('Returns FormData for multipart text fields.', async () => {
const formData = new FormData();
const formData = new window.FormData();

vi.spyOn(Math, 'random').mockImplementation(() => 0.8);

Expand All @@ -733,7 +733,7 @@ describe('Request', () => {
});

it('Returns FormData for multipart files.', async () => {
const formData = new FormData();
const formData = new window.FormData();
const imageBuffer = await FS.promises.readFile(
Path.join(__dirname, 'data', 'test-image.jpg')
);
Expand Down Expand Up @@ -773,7 +773,7 @@ describe('Request', () => {

it('Supports window.happyDOM?.waitUntilComplete().', async () => {
await new Promise((resolve) => {
const formData = new FormData();
const formData = new window.FormData();
formData.append('some', 'test');
const request = new window.Request(TEST_URL, { method: 'POST', body: formData });
let isAsyncComplete = false;
Expand Down
13 changes: 8 additions & 5 deletions packages/happy-dom/test/fetch/Response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ describe('Response', () => {

describe('formData()', () => {
it('Returns FormData for FormData object (multipart)', async () => {
const formData = new FormData();
const formData = new window.FormData();
formData.append('some', 'test');
const response = new window.Response(formData);
const formDataResponse = await response.formData();
Expand Down Expand Up @@ -362,7 +362,7 @@ describe('Response', () => {
});

it('Returns FormData for multipart text fields.', async () => {
const formData = new FormData();
const formData = new window.FormData();

vi.spyOn(Math, 'random').mockImplementation(() => 0.8);

Expand All @@ -385,7 +385,7 @@ describe('Response', () => {
});

it('Returns FormData for multipart files.', async () => {
const formData = new FormData();
const formData = new window.FormData();
const imageBuffer = await FS.promises.readFile(
Path.join(__dirname, 'data', 'test-image.jpg')
);
Expand Down Expand Up @@ -461,13 +461,16 @@ describe('Response', () => {

it('Supports window.happyDOM?.waitUntilComplete() for multipart content.', async () => {
await new Promise((resolve) => {
const response = new window.Response(new FormData());
const response = new window.Response(new window.FormData());
let isAsyncComplete = false;

vi.spyOn(MultipartFormDataParser, 'streamToFormData').mockImplementation(
(): Promise<{ formData: FormData; buffer: Buffer }> =>
new Promise((resolve) =>
setTimeout(() => resolve({ formData: new FormData(), buffer: Buffer.from([]) }), 10)
setTimeout(
() => resolve({ formData: new window.FormData(), buffer: Buffer.from([]) }),
10
)
)
);

Expand Down
2 changes: 1 addition & 1 deletion packages/happy-dom/test/fetch/SyncFetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2029,7 +2029,7 @@ describe('SyncFetch', () => {

const body =
'------HappyDOMFormDataBoundary0.ssssssssst\r\nContent-Disposition: form-data; name="key1"\r\n\r\nvalue1\r\n------HappyDOMFormDataBoundary0.ssssssssst\r\nContent-Disposition: form-data; name="key2"\r\n\r\nvalue2\r\n';
const formData = new FormData();
const formData = new window.FormData();
let requestArgs: string | null = null;

vi.spyOn(Math, 'random').mockImplementation(() => 0.8);
Expand Down
14 changes: 14 additions & 0 deletions packages/happy-dom/test/form-data/FormData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Window from '../../src/window/Window.js';
import Document from '../../src/nodes/document/Document.js';
import File from '../../src/file/File.js';
import { beforeEach, describe, it, expect } from 'vitest';
import Blob from '../../src/file/Blob.js';

describe('FormData', () => {
let window: Window;
Expand Down Expand Up @@ -140,11 +141,24 @@ describe('FormData', () => {
describe('append()', () => {
it('Appends a value.', () => {
const formData = new window.FormData();
const blob = new Blob();
const file = new File([], 'filename');

formData.append('key1', 'value1');
formData.append('key1', 'value2');
formData.append('key2', blob);
formData.append('key3', file);

expect(formData.getAll('key1')).toEqual(['value1', 'value2']);
expect(formData.getAll('key2')).toEqual([new File([], 'blob')]);
expect(formData.getAll('key3')).toEqual([file]);
});

it('Throws an error if a filename is provided and the value is not an instance of a Blob.', () => {
const formData = new window.FormData();
expect(() => formData.append('key1', 'value1', 'filename')).toThrow(
'Failed to execute "append" on "FormData": parameter 2 is not of type "Blob".'
);
});
});

Expand Down

0 comments on commit df69f9e

Please sign in to comment.