Skip to content

Commit

Permalink
docs(node/querystring): add docs (denoland#1531)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hirotaka Tagawa / wafuwafu13 authored and traceypooh committed Nov 14, 2021
1 parent a5578f0 commit 12ab856
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 138 deletions.
253 changes: 141 additions & 112 deletions node/querystring.ts
Expand Up @@ -2,7 +2,106 @@
import { Buffer } from "./buffer.ts";
import { ERR_INVALID_URI } from "./_errors.ts";

export interface ParsedUrlQuery {
/**
* Alias of querystring.parse()
* @legacy
*/
export const decode = parse;

/**
* Alias of querystring.stringify()
* @legacy
*/
export const encode = stringify;

const hexTable = new Array(256);
for (let i = 0; i < 256; ++i) {
hexTable[i] = "%" + ((i < 16 ? "0" : "") + i.toString(16)).toUpperCase();
}

function encodeStr(
str: string,
noEscapeTable: Int8Array,
hexTable: string[],
): string {
const len = str.length;
if (len === 0) return "";

let out = "";
let lastPos = 0;

for (let i = 0; i < len; i++) {
let c = str.charCodeAt(i);
// ASCII
if (c < 0x80) {
if (noEscapeTable[c] === 1) continue;
if (lastPos < i) out += str.slice(lastPos, i);
lastPos = i + 1;
out += hexTable[c];
continue;
}

if (lastPos < i) out += str.slice(lastPos, i);

// Multi-byte characters ...
if (c < 0x800) {
lastPos = i + 1;
out += hexTable[0xc0 | (c >> 6)] + hexTable[0x80 | (c & 0x3f)];
continue;
}
if (c < 0xd800 || c >= 0xe000) {
lastPos = i + 1;
out += hexTable[0xe0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3f)] +
hexTable[0x80 | (c & 0x3f)];
continue;
}
// Surrogate pair
++i;

// This branch should never happen because all URLSearchParams entries
// should already be converted to USVString. But, included for
// completion's sake anyway.
if (i >= len) throw new ERR_INVALID_URI();

const c2 = str.charCodeAt(i) & 0x3ff;

lastPos = i + 1;
c = 0x10000 + (((c & 0x3ff) << 10) | c2);
out += hexTable[0xf0 | (c >> 18)] +
hexTable[0x80 | ((c >> 12) & 0x3f)] +
hexTable[0x80 | ((c >> 6) & 0x3f)] +
hexTable[0x80 | (c & 0x3f)];
}
if (lastPos === 0) return str;
if (lastPos < len) return out + str.slice(lastPos);
return out;
}

/**
* replaces encodeURIComponent()
* @see https://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
*/
function qsEscape(str: unknown): string {
if (typeof str !== "string") {
if (typeof str === "object") {
str = String(str);
} else {
str += "";
}
}
return encodeStr(str as string, noEscape, hexTable);
}

/**
* Performs URL percent-encoding on the given `str` in a manner that is optimized for the specific requirements of URL query strings.
* Used by `querystring.stringify()` and is generally not expected to be used directly.
* It is exported primarily to allow application code to provide a replacement percent-encoding implementation if necessary by assigning `querystring.escape` to an alternative function.
* @legacy
* @see Tested in `test-querystring-escape.js`
*/
export const escape = qsEscape;

interface ParsedUrlQuery {
[key: string]: string | string[] | undefined;
}

Expand All @@ -13,10 +112,25 @@ interface ParseOptions {
maxKeys?: number;
}

export const hexTable = new Array(256);
for (let i = 0; i < 256; ++i) {
hexTable[i] = "%" + ((i < 16 ? "0" : "") + i.toString(16)).toUpperCase();
}
// deno-fmt-ignore
const isHexTable = new Int8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ...
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ... 256
]);

function charCodes(str: string): number[] {
const ret = new Array(str.length);
Expand Down Expand Up @@ -56,32 +170,16 @@ function addKeyVal(
}
}

// deno-fmt-ignore
const isHexTable = new Int8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32 - 47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64 - 79
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 - 95
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96 - 111
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 112 - 127
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 128 ...
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ... 256
]);

/**
* Parses a URL query string into a collection of key and value pairs.
* @param str The URL query string to parse
* @param sep The substring used to delimit key and value pairs in the query string. Default: '&'.
* @param eq The substring used to delimit keys and values in the query string. Default: '='.
* @param options The parse options
* @param options.decodeURIComponent The function to use when decoding percent-encoded characters in the query string. Default: `querystring.unescape()`.
* @param options.maxKeys Specifies the maximum number of keys to parse. Specify `0` to remove key counting limitations. Default: `1000`.
* @legacy
* @see Tested in test-querystring.js
*/
export function parse(
str: string,
Expand Down Expand Up @@ -272,79 +370,6 @@ const noEscape = new Int8Array([
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 112 - 127
]);

/**
* replaces encodeURIComponent()
* @see https://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4
*/
function qsEscape(str: unknown): string {
if (typeof str !== "string") {
if (typeof str === "object") {
str = String(str);
} else {
str += "";
}
}
return encodeStr(str as string, noEscape, hexTable);
}

function encodeStr(
str: string,
noEscapeTable: Int8Array,
hexTable: string[],
): string {
const len = str.length;
if (len === 0) return "";

let out = "";
let lastPos = 0;

for (let i = 0; i < len; i++) {
let c = str.charCodeAt(i);
// ASCII
if (c < 0x80) {
if (noEscapeTable[c] === 1) continue;
if (lastPos < i) out += str.slice(lastPos, i);
lastPos = i + 1;
out += hexTable[c];
continue;
}

if (lastPos < i) out += str.slice(lastPos, i);

// Multi-byte characters ...
if (c < 0x800) {
lastPos = i + 1;
out += hexTable[0xc0 | (c >> 6)] + hexTable[0x80 | (c & 0x3f)];
continue;
}
if (c < 0xd800 || c >= 0xe000) {
lastPos = i + 1;
out += hexTable[0xe0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3f)] +
hexTable[0x80 | (c & 0x3f)];
continue;
}
// Surrogate pair
++i;

// This branch should never happen because all URLSearchParams entries
// should already be converted to USVString. But, included for
// completion's sake anyway.
if (i >= len) throw new ERR_INVALID_URI();

const c2 = str.charCodeAt(i) & 0x3ff;

lastPos = i + 1;
c = 0x10000 + (((c & 0x3ff) << 10) | c2);
out += hexTable[0xf0 | (c >> 18)] +
hexTable[0x80 | ((c >> 12) & 0x3f)] +
hexTable[0x80 | ((c >> 6) & 0x3f)] +
hexTable[0x80 | (c & 0x3f)];
}
if (lastPos === 0) return str;
if (lastPos < len) return out + str.slice(lastPos);
return out;
}

// deno-lint-ignore no-explicit-any
function stringifyPrimitive(v: any): string {
if (typeof v === "string") {
Expand Down Expand Up @@ -395,6 +420,9 @@ function encodeStringified(v: any, encode: (string: string) => string): string {
* @param sep The substring used to delimit key and value pairs in the query string. Default: '&'.
* @param eq The substring used to delimit keys and values in the query string. Default: '='.
* @param options The stringify options
* @param options.encodeURIComponent The function to use when converting URL-unsafe characters to percent-encoding in the query string. Default: `querystring.escape()`.
* @legacy
* @see Tested in `test-querystring.js`
*/
export function stringify(
// deno-lint-ignore no-explicit-any
Expand Down Expand Up @@ -464,14 +492,6 @@ const unhexTable = new Int8Array([
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // ... 255
]);

function qsUnescape(s: string): string {
try {
return decodeURIComponent(s);
} catch {
return unescapeBuffer(s).toString();
}
}

/**
* A safe fast alternative to decodeURIComponent
*/
Expand Down Expand Up @@ -517,17 +537,26 @@ export function unescapeBuffer(s: string, decodeSpaces = false): Buffer {
return hasHex ? out.slice(0, outIndex) : out;
}

/** Alias of querystring.parse() */
export const decode = parse;
/** Alias of querystring.stringify() */
export const encode = stringify;
function qsUnescape(s: string): string {
try {
return decodeURIComponent(s);
} catch {
return unescapeBuffer(s).toString();
}
}

/**
* Performs decoding of URL percent-encoded characters on the given `str`.
* Used by `querystring.parse()` and is generally not expected to be used directly.
* It is exported primarily to allow application code to provide a replacement decoding implementation if necessary by assigning `querystring.unescape` to an alternative function.
* @legacy
* @see Tested in `test-querystring-escape.js`
*/
export const unescape = qsUnescape;
export const escape = qsEscape;

export default {
parse,
stringify,
hexTable,
decode,
encode,
unescape,
Expand Down
26 changes: 0 additions & 26 deletions node/querystring_test.ts
Expand Up @@ -17,19 +17,6 @@ Deno.test({
},
});

Deno.test({
name: "stringify with escape",
fn() {
assertEquals(
stringify({
a: "hello!",
b: "こんにちは",
}),
"a=hello%EF%BC%81&b=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF",
);
},
});

Deno.test({
name: "parse",
fn() {
Expand All @@ -41,16 +28,3 @@ Deno.test({
});
},
});

Deno.test({
name: "parse escaped string",
fn() {
assertEquals(
parse("a=hello%EF%BC%81&b=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF"),
{
a: "hello!",
b: "こんにちは",
},
);
},
});

0 comments on commit 12ab856

Please sign in to comment.