diff --git a/js/form_data.ts b/js/form_data.ts index 6f0cadfef1..163c33b748 100644 --- a/js/form_data.ts +++ b/js/form_data.ts @@ -3,6 +3,7 @@ import * as domTypes from "./dom_types"; import * as blob from "./blob"; import * as file from "./file"; import { DomIterableMixin } from "./mixins/dom_iterable"; +import { requiredArguments } from "./util"; const dataSymbol = Symbol("data"); @@ -18,6 +19,8 @@ class FormDataBase { append(name: string, value: string): void; append(name: string, value: blob.DenoBlob, filename?: string): void; append(name: string, value: string | blob.DenoBlob, filename?: string): void { + requiredArguments("FormData.append", arguments.length, 2); + name = String(name); if (value instanceof blob.DenoBlob) { const dfile = new file.DenoFile([value], filename || name); this[dataSymbol].push([name, dfile]); @@ -31,6 +34,8 @@ class FormDataBase { * formData.delete('name'); */ delete(name: string): void { + requiredArguments("FormData.delete", arguments.length, 1); + name = String(name); let i = 0; while (i < this[dataSymbol].length) { if (this[dataSymbol][i][0] === name) { @@ -47,6 +52,8 @@ class FormDataBase { * formData.getAll('name'); */ getAll(name: string): domTypes.FormDataEntryValue[] { + requiredArguments("FormData.getAll", arguments.length, 1); + name = String(name); const values = []; for (const entry of this[dataSymbol]) { if (entry[0] === name) { @@ -63,6 +70,8 @@ class FormDataBase { * formData.get('name'); */ get(name: string): domTypes.FormDataEntryValue | null { + requiredArguments("FormData.get", arguments.length, 1); + name = String(name); for (const entry of this[dataSymbol]) { if (entry[0] === name) { return entry[1]; @@ -78,23 +87,53 @@ class FormDataBase { * formData.has('name'); */ has(name: string): boolean { + requiredArguments("FormData.has", arguments.length, 1); + name = String(name); return this[dataSymbol].some(entry => entry[0] === name); } /** Sets a new value for an existing key inside a `FormData` object, or * adds the key/value if it does not already exist. + * ref: https://xhr.spec.whatwg.org/#dom-formdata-set * * formData.set('name', 'value'); */ set(name: string, value: string): void; set(name: string, value: blob.DenoBlob, filename?: string): void; set(name: string, value: string | blob.DenoBlob, filename?: string): void { - this.delete(name); - if (value instanceof blob.DenoBlob) { - const dfile = new file.DenoFile([value], filename || name); - this[dataSymbol].push([name, dfile]); - } else { - this[dataSymbol].push([name, String(value)]); + requiredArguments("FormData.set", arguments.length, 2); + name = String(name); + + // If there are any entries in the context object’s entry list whose name + // is name, replace the first such entry with entry and remove the others + let found = false; + let i = 0; + while (i < this[dataSymbol].length) { + if (this[dataSymbol][i][0] === name) { + if (!found) { + if (value instanceof blob.DenoBlob) { + const dfile = new file.DenoFile([value], filename || name); + this[dataSymbol][i][1] = dfile; + } else { + this[dataSymbol][i][1] = String(value); + } + found = true; + } else { + this[dataSymbol].splice(i, 1); + continue; + } + } + i++; + } + + // Otherwise, append entry to the context object’s entry list. + if (!found) { + if (value instanceof blob.DenoBlob) { + const dfile = new file.DenoFile([value], filename || name); + this[dataSymbol].push([name, dfile]); + } else { + this[dataSymbol].push([name, String(value)]); + } } } } diff --git a/js/form_data_test.ts b/js/form_data_test.ts index f3fbdd8724..109220f631 100644 --- a/js/form_data_test.ts +++ b/js/form_data_test.ts @@ -96,3 +96,73 @@ test(function formDataParamsForEachSuccess() { }); assertEqual(callNum, init.length); }); + +test(function formDataParamsArgumentsCheck() { + const methodRequireOneParam = ["delete", "getAll", "get", "has", "forEach"]; + + const methodRequireTwoParams = ["append", "set"]; + + methodRequireOneParam.forEach(method => { + const formData = new FormData(); + let hasThrown = 0; + let errMsg = ""; + try { + formData[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEqual(hasThrown, 2); + assertEqual( + errMsg, + `FormData.${method} requires at least 1 argument, but only 0 present` + ); + }); + + methodRequireTwoParams.forEach(method => { + const formData = new FormData(); + let hasThrown = 0; + let errMsg = ""; + + try { + formData[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEqual(hasThrown, 2); + assertEqual( + errMsg, + `FormData.${method} requires at least 2 arguments, but only 0 present` + ); + + hasThrown = 0; + errMsg = ""; + try { + formData[method]("foo"); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEqual(hasThrown, 2); + assertEqual( + errMsg, + `FormData.${method} requires at least 2 arguments, but only 1 present` + ); + }); +}); diff --git a/js/headers.ts b/js/headers.ts index ca99d5789b..7cc38181f8 100644 --- a/js/headers.ts +++ b/js/headers.ts @@ -1,6 +1,7 @@ // Copyright 2018 the Deno authors. All rights reserved. MIT license. import * as domTypes from "./dom_types"; import { DomIterableMixin } from "./mixins/dom_iterable"; +import { requiredArguments } from "./util"; // From node-fetch // Copyright (c) 2016 David Frank. MIT License. @@ -75,6 +76,7 @@ class HeadersBase { // ref: https://fetch.spec.whatwg.org/#concept-headers-append append(name: string, value: string): void { + requiredArguments("Headers.append", arguments.length, 2); const [newname, newvalue] = this._normalizeParams(name, value); this._validateName(newname); this._validateValue(newvalue); @@ -84,12 +86,14 @@ class HeadersBase { } delete(name: string): void { + requiredArguments("Headers.delete", arguments.length, 1); const [newname] = this._normalizeParams(name); this._validateName(newname); this[headerMap].delete(newname); } get(name: string): string | null { + requiredArguments("Headers.get", arguments.length, 1); const [newname] = this._normalizeParams(name); this._validateName(newname); const value = this[headerMap].get(newname); @@ -97,12 +101,14 @@ class HeadersBase { } has(name: string): boolean { + requiredArguments("Headers.has", arguments.length, 1); const [newname] = this._normalizeParams(name); this._validateName(newname); return this[headerMap].has(newname); } set(name: string, value: string): void { + requiredArguments("Headers.set", arguments.length, 2); const [newname, newvalue] = this._normalizeParams(name, value); this._validateName(newname); this._validateValue(newvalue); diff --git a/js/headers_test.ts b/js/headers_test.ts index 53c8ef089d..9591b24f82 100644 --- a/js/headers_test.ts +++ b/js/headers_test.ts @@ -228,3 +228,73 @@ test(function headerIllegalReject() { // 'o k' is valid value but invalid name new Headers({ "He-y": "o k" }); }); + +test(function headerParamsArgumentsCheck() { + const methodRequireOneParam = ["delete", "get", "has", "forEach"]; + + const methodRequireTwoParams = ["append", "set"]; + + methodRequireOneParam.forEach(method => { + const headers = new Headers(); + let hasThrown = 0; + let errMsg = ""; + try { + headers[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEqual(hasThrown, 2); + assertEqual( + errMsg, + `Headers.${method} requires at least 1 argument, but only 0 present` + ); + }); + + methodRequireTwoParams.forEach(method => { + const headers = new Headers(); + let hasThrown = 0; + let errMsg = ""; + + try { + headers[method](); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEqual(hasThrown, 2); + assertEqual( + errMsg, + `Headers.${method} requires at least 2 arguments, but only 0 present` + ); + + hasThrown = 0; + errMsg = ""; + try { + headers[method]("foo"); + hasThrown = 1; + } catch (err) { + errMsg = err.message; + if (err instanceof TypeError) { + hasThrown = 2; + } else { + hasThrown = 3; + } + } + assertEqual(hasThrown, 2); + assertEqual( + errMsg, + `Headers.${method} requires at least 2 arguments, but only 1 present` + ); + }); +}); diff --git a/js/mixins/dom_iterable.ts b/js/mixins/dom_iterable.ts index 2daf80b715..6a2a9fcfea 100644 --- a/js/mixins/dom_iterable.ts +++ b/js/mixins/dom_iterable.ts @@ -1,5 +1,6 @@ import { DomIterable } from "../dom_types"; import { globalEval } from "../global_eval"; +import { requiredArguments } from "../util"; // if we import it directly from "globals" it will break the unit tests so we // have to grab a reference to the global scope a different way @@ -52,6 +53,11 @@ export function DomIterableMixin( // tslint:disable-next-line:no-any thisArg?: any ): void { + requiredArguments( + `${this.constructor.name}.forEach`, + arguments.length, + 1 + ); callbackfn = callbackfn.bind(thisArg == null ? window : Object(thisArg)); for (const [key, value] of (this as any)[dataSymbol]) { callbackfn(value, key, this);