diff --git a/node/README.md b/node/README.md index 37084ed25095..1c561be4f4ad 100644 --- a/node/README.md +++ b/node/README.md @@ -27,7 +27,7 @@ deno standard library as it's a compatiblity module. - [x] path - [ ] perf_hooks - [x] process _partly_ -- [ ] querystring +- [x] querystring - [ ] readline - [ ] repl - [ ] stream diff --git a/node/module.ts b/node/module.ts index 3520d804b99f..01f1f04774d3 100644 --- a/node/module.ts +++ b/node/module.ts @@ -27,6 +27,7 @@ import * as nodePath from "./path.ts"; import * as nodeTimers from "./timers.ts"; import * as nodeOs from "./os.ts"; import * as nodeEvents from "./events.ts"; +import * as nodeQueryString from "./querystring.ts"; import * as path from "../path/mod.ts"; import { assert } from "../testing/asserts.ts"; @@ -593,6 +594,10 @@ nativeModulePolyfill.set("os", createNativeModule("os", nodeOs)); nativeModulePolyfill.set("path", createNativeModule("path", nodePath)); nativeModulePolyfill.set("timers", createNativeModule("timers", nodeTimers)); nativeModulePolyfill.set("util", createNativeModule("util", nodeUtil)); +nativeModulePolyfill.set( + "querystring", + createNativeModule("querystring", nodeQueryString) +); function loadNativeModule( _filename: string, diff --git a/node/querystring.ts b/node/querystring.ts new file mode 100644 index 000000000000..a5b2e7f05627 --- /dev/null +++ b/node/querystring.ts @@ -0,0 +1,70 @@ +interface ParseOptions { + decodeURIComponent?: (string: string) => string; + maxKeys?: number; +} + +export function parse( + str: string, + sep = "&", + eq = "=", + { decodeURIComponent = unescape, maxKeys = 1000 }: ParseOptions = {} +): { [key: string]: string[] | string } { + const entries = str + .split(sep) + .map(entry => entry.split(eq).map(decodeURIComponent)); + const final: { [key: string]: string[] | string } = {}; + + let i = 0; + while (true) { + if ((Object.keys(final).length === maxKeys && !!maxKeys) || !entries[i]) { + break; + } + + const [key, val] = entries[i]; + if (final[key]) { + if (Array.isArray(final[key])) { + (final[key] as string[]).push(val); + } else { + final[key] = [final[key] as string, val]; + } + } else { + final[key] = val; + } + + i++; + } + + return final; +} + +interface StringifyOptions { + encodeURIComponent?: (string: string) => string; +} + +export function stringify( + obj: object, + sep = "&", + eq = "=", + { encodeURIComponent = escape }: StringifyOptions = {} +): string { + const final = []; + + for (const entry of Object.entries(obj)) { + if (Array.isArray(entry[1])) { + for (const val of entry[1]) { + final.push(encodeURIComponent(entry[0]) + eq + encodeURIComponent(val)); + } + } else if (typeof entry[1] !== "object" && entry[1] !== undefined) { + final.push(entry.map(encodeURIComponent).join(eq)); + } else { + final.push(encodeURIComponent(entry[0]) + eq); + } + } + + return final.join(sep); +} + +export const decode = parse; +export const encode = stringify; +export const unescape = decodeURIComponent; +export const escape = encodeURIComponent; diff --git a/node/querystring_test.ts b/node/querystring_test.ts new file mode 100644 index 000000000000..d8cb1ec35a34 --- /dev/null +++ b/node/querystring_test.ts @@ -0,0 +1,30 @@ +const { test } = Deno; +import { assertEquals } from "../testing/asserts.ts"; +import { stringify, parse } from "./querystring.ts"; + +test({ + name: "stringify", + fn() { + assertEquals( + stringify({ + a: "hello", + b: 5, + c: true, + d: ["foo", "bar"] + }), + "a=hello&b=5&c=true&d=foo&d=bar" + ); + } +}); + +test({ + name: "parse", + fn() { + assertEquals(parse("a=hello&b=5&c=true&d=foo&d=bar"), { + a: "hello", + b: "5", + c: "true", + d: ["foo", "bar"] + }); + } +});