// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { URL } from "./url.ts"; import { requiredArguments } from "./util.ts"; // Returns whether o is iterable. // @internal export function isIterable( o: T ): o is T & Iterable<[P, K]> { // checks for null and undefined if (o == null) { return false; } return ( typeof ((o as unknown) as Iterable<[P, K]>)[Symbol.iterator] === "function" ); } export class URLSearchParams { private params: Array<[string, string]> = []; private url: URL | null = null; constructor(init: string | string[][] | Record = "") { if (typeof init === "string") { this._handleStringInitialization(init); return; } if (Array.isArray(init) || isIterable(init)) { this._handleArrayInitialization(init); return; } if (Object(init) !== init) { return; } if (init instanceof URLSearchParams) { this.params = init.params; return; } // Overload: record for (const key of Object.keys(init)) { this.append(key, init[key]); } } private updateSteps(): void { if (this.url === null) { return; } let query: string | null = this.toString(); if (query === "") { query = null; } // eslint-disable-next-line @typescript-eslint/no-explicit-any (this.url as any)._parts.query = query; } append(name: string, value: string): void { requiredArguments("URLSearchParams.append", arguments.length, 2); this.params.push([String(name), String(value)]); this.updateSteps(); } delete(name: string): void { requiredArguments("URLSearchParams.delete", arguments.length, 1); name = String(name); let i = 0; while (i < this.params.length) { if (this.params[i][0] === name) { this.params.splice(i, 1); } else { i++; } } this.updateSteps(); } getAll(name: string): string[] { requiredArguments("URLSearchParams.getAll", arguments.length, 1); name = String(name); const values = []; for (const entry of this.params) { if (entry[0] === name) { values.push(entry[1]); } } return values; } get(name: string): string | null { requiredArguments("URLSearchParams.get", arguments.length, 1); name = String(name); for (const entry of this.params) { if (entry[0] === name) { return entry[1]; } } return null; } has(name: string): boolean { requiredArguments("URLSearchParams.has", arguments.length, 1); name = String(name); return this.params.some((entry): boolean => entry[0] === name); } set(name: string, value: string): void { requiredArguments("URLSearchParams.set", arguments.length, 2); // If there are any name-value pairs whose name is name, in list, // set the value of the first such name-value pair to value // and remove the others. name = String(name); value = String(value); let found = false; let i = 0; while (i < this.params.length) { if (this.params[i][0] === name) { if (!found) { this.params[i][1] = value; found = true; i++; } else { this.params.splice(i, 1); } } else { i++; } } // Otherwise, append a new name-value pair whose name is name // and value is value, to list. if (!found) { this.append(name, value); } this.updateSteps(); } sort(): void { this.params = this.params.sort((a, b): number => a[0] === b[0] ? 0 : a[0] > b[0] ? 1 : -1 ); this.updateSteps(); } forEach( callbackfn: (value: string, key: string, parent: this) => void, // eslint-disable-next-line @typescript-eslint/no-explicit-any thisArg?: any ): void { requiredArguments("URLSearchParams.forEach", arguments.length, 1); if (typeof thisArg !== "undefined") { callbackfn = callbackfn.bind(thisArg); } for (const [key, value] of this.entries()) { callbackfn(value, key, this); } } *keys(): IterableIterator { for (const entry of this.params) { yield entry[0]; } } *values(): IterableIterator { for (const entry of this.params) { yield entry[1]; } } *entries(): IterableIterator<[string, string]> { yield* this.params; } *[Symbol.iterator](): IterableIterator<[string, string]> { yield* this.params; } toString(): string { return this.params .map( (tuple): string => `${encodeURIComponent(tuple[0])}=${encodeURIComponent(tuple[1])}` ) .join("&"); } private _handleStringInitialization(init: string): void { // Overload: USVString // If init is a string and starts with U+003F (?), // remove the first code point from init. if (init.charCodeAt(0) === 0x003f) { init = init.slice(1); } for (const pair of init.split("&")) { // Empty params are ignored if (pair.length === 0) { continue; } const position = pair.indexOf("="); const name = pair.slice(0, position === -1 ? pair.length : position); const value = pair.slice(name.length + 1); this.append(decodeURIComponent(name), decodeURIComponent(value)); } } private _handleArrayInitialization( init: string[][] | Iterable<[string, string]> ): void { // Overload: sequence> for (const tuple of init) { // If pair does not contain exactly two items, then throw a TypeError. if (tuple.length !== 2) { throw new TypeError( "URLSearchParams.constructor tuple array argument must only contain pair elements" ); } this.append(tuple[0], tuple[1]); } } }