Skip to content

Commit c8fc506

Browse files
committed
Add decode and encode operations for URL query strings
These allow converting between URL query strings and JSON. They are based on the qs NPM package.
1 parent ae1b12c commit c8fc506

File tree

7 files changed

+266
-7
lines changed

7 files changed

+266
-7
lines changed

package-lock.json

+4-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
"process": "^0.11.10",
149149
"protobufjs": "^6.11.2",
150150
"qr-image": "^3.2.0",
151+
"qs": "^6.10.3",
151152
"scryptsy": "^2.1.0",
152153
"snackbarjs": "^1.1.0",
153154
"sortablejs": "^1.14.0",

src/core/config/Categories.json

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
"From HTML Entity",
3838
"URL Encode",
3939
"URL Decode",
40+
"Query String Encode",
41+
"Query String Decode",
4042
"Escape Unicode Characters",
4143
"Unescape Unicode Characters",
4244
"Normalise Unicode",
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* @author Benjamin Altpeter [[email protected]]
3+
* @copyright Crown Copyright 2022
4+
* @license Apache-2.0
5+
*/
6+
7+
import qs from "qs";
8+
import Operation from "../Operation.mjs";
9+
10+
/**
11+
* Query String Decode operation
12+
*/
13+
class QueryStringDecode extends Operation {
14+
/**
15+
* QueryStringDecode constructor
16+
*/
17+
constructor() {
18+
super();
19+
20+
this.name = "Query String Decode";
21+
this.module = "URL";
22+
this.description =
23+
"Converts URL query strings into a JSON representation.<br><br>e.g. <code>a=b&c=1</code> becomes <code>{&quot;a&quot;: &quot;b&quot;, &quot;c&quot;: &quot;1&quot;}</code>";
24+
this.infoURL = "https://wikipedia.org/wiki/Query_string";
25+
this.inputType = "string";
26+
this.outputType = "JSON";
27+
this.args = [
28+
{
29+
name: "Depth",
30+
type: "number",
31+
value: 5,
32+
},
33+
{
34+
name: "Parameter limit",
35+
type: "number",
36+
value: 1000,
37+
},
38+
{
39+
name: "Delimiter",
40+
type: "string",
41+
value: "&",
42+
},
43+
{
44+
name: "Allow dot notation (<code>a.b=c</code>)?",
45+
type: "boolean",
46+
value: false,
47+
},
48+
{
49+
name: "Allow comma arrays (<code>a=b,c</code>)?",
50+
type: "boolean",
51+
value: false,
52+
},
53+
];
54+
}
55+
56+
/**
57+
* @param {string} input
58+
* @param {Object[]} args
59+
* @returns {JSON}
60+
*/
61+
run(input, args) {
62+
const [depth, parameterLimit, delimiter, allowDots, comma] = args;
63+
return qs.parse(input, {
64+
depth,
65+
delimiter,
66+
parameterLimit,
67+
allowDots,
68+
comma,
69+
ignoreQueryPrefix: true,
70+
});
71+
}
72+
}
73+
74+
export default QueryStringDecode;
+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* @author Benjamin Altpeter [[email protected]]
3+
* @copyright Crown Copyright 2022
4+
* @license Apache-2.0
5+
*/
6+
7+
import qs from "qs";
8+
import Operation from "../Operation.mjs";
9+
10+
/**
11+
* Query String Encode operation
12+
*/
13+
class QueryStringEncode extends Operation {
14+
/**
15+
* QueryStringEncode constructor
16+
*/
17+
constructor() {
18+
super();
19+
20+
this.name = "Query String Encode";
21+
this.module = "URL";
22+
this.description =
23+
"Converts JSON objects into a URL query string representation.<br><br>e.g. <code>{&quot;a&quot;: &quot;b&quot;, &quot;c&quot;: 1}</code> becomes <code>a=b&c=1</code>";
24+
this.infoURL = "https://wikipedia.org/wiki/Query_string";
25+
this.inputType = "JSON";
26+
this.outputType = "string";
27+
this.args = [
28+
{
29+
name: "Array format",
30+
type: "option",
31+
value: ["brackets", "indices", "repeat", "comma"],
32+
defaultIndex: 0,
33+
},
34+
{
35+
name: "Object format",
36+
type: "option",
37+
value: ["brackets", "dots"],
38+
defaultIndex: 0,
39+
},
40+
{
41+
name: "Delimiter",
42+
type: "string",
43+
value: "&",
44+
},
45+
];
46+
}
47+
48+
/**
49+
* @param {JSON} input
50+
* @param {Object[]} args
51+
* @returns {string}
52+
*/
53+
run(input, args) {
54+
const [arrayFormat, objectFormat, delimiter] = args;
55+
return qs.stringify(input, {
56+
arrayFormat,
57+
delimiter,
58+
allowDots: objectFormat === "dots",
59+
encode: false,
60+
});
61+
}
62+
}
63+
64+
export default QueryStringEncode;

tests/operations/index.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import "./tests/CBORDecode.mjs";
107107
import "./tests/JA3Fingerprint.mjs";
108108
import "./tests/JA3SFingerprint.mjs";
109109
import "./tests/HASSH.mjs";
110+
import "./tests/QueryString.mjs";
110111

111112

112113
// Cannot test operations that use the File type yet
+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/**
2+
* Query String tests.
3+
*
4+
* @author Benjamin Altpeter [[email protected]]
5+
* @copyright Crown Copyright 2022
6+
* @license Apache-2.0
7+
*/
8+
9+
import TestRegister from "../../lib/TestRegister.mjs";
10+
11+
/**
12+
* Small helper for JSON.stringify() with the correct settings.
13+
* @param {any} obj An object to stringify
14+
* @returns A stringified version of the object, indented by four spaces.
15+
*/
16+
const json = (obj) => JSON.stringify(obj, null, 4);
17+
18+
TestRegister.addTests([
19+
{
20+
name: "Query String Decode simple example (defaults)",
21+
input: "?a=b&c=1&d=e;f=g",
22+
expectedOutput: json({
23+
a: "b",
24+
c: "1",
25+
d: "e;f=g",
26+
}),
27+
recipeConfig: [
28+
{ op: "Query String Decode", args: [5, 1000, "&", false, false] },
29+
],
30+
},
31+
{
32+
name: "Query String Decode arrays and objects (defaults)",
33+
input: "a[]=b&a[2]=b&c[d]=e&f=g,h&i.j=k",
34+
expectedOutput: json({
35+
a: ["b", "b"],
36+
c: {
37+
d: "e",
38+
},
39+
f: "g,h",
40+
"i.j": "k",
41+
}),
42+
recipeConfig: [
43+
{ op: "Query String Decode", args: [5, 1000, "&", false, false] },
44+
],
45+
},
46+
{
47+
name: "Query String Decode arrays and objects (extended)",
48+
input: "a[]=b&a[2]=b&c[d]=e&f=g,h&i.j=k",
49+
expectedOutput: json({
50+
a: ["b", "b"],
51+
c: {
52+
d: "e",
53+
},
54+
f: ["g", "h"],
55+
i: {
56+
j: "k",
57+
},
58+
}),
59+
recipeConfig: [
60+
{ op: "Query String Decode", args: [5, 1000, "&", true, true] },
61+
],
62+
},
63+
{
64+
name: "Query String Decode delimiter",
65+
input: "a=b;c=d",
66+
expectedOutput: json({
67+
a: "b",
68+
c: "d",
69+
}),
70+
recipeConfig: [
71+
{ op: "Query String Decode", args: [5, 1000, ";", false, false] },
72+
],
73+
},
74+
{
75+
name: "Query String Decode depth (default)",
76+
input: "a[b][c][d][e][f][g][h]=5",
77+
expectedOutput: json({
78+
a: {
79+
b: {
80+
c: {
81+
d: {
82+
e: {
83+
f: {
84+
"[g][h]": "5",
85+
},
86+
},
87+
},
88+
},
89+
},
90+
},
91+
}),
92+
recipeConfig: [
93+
{ op: "Query String Decode", args: [5, 1000, "&", false, false] },
94+
],
95+
},
96+
{
97+
name: "Query String Decode depth (higher)",
98+
input: "a[b][c][d][e][f][g][h]=5",
99+
expectedOutput: json({
100+
a: {
101+
b: {
102+
c: {
103+
d: {
104+
e: {
105+
f: {
106+
g: {
107+
h: "5",
108+
},
109+
},
110+
},
111+
},
112+
},
113+
},
114+
},
115+
}),
116+
recipeConfig: [
117+
{ op: "Query String Decode", args: [7, 1000, "&", false, false] },
118+
],
119+
},
120+
]);

0 commit comments

Comments
 (0)