Skip to content

Commit cd8b2ee

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 2efd075 commit cd8b2ee

File tree

7 files changed

+314
-8
lines changed

7 files changed

+314
-8
lines changed

package-lock.json

+52-8
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
@@ -152,6 +152,7 @@
152152
"process": "^0.11.10",
153153
"protobufjs": "^6.11.3",
154154
"qr-image": "^3.2.0",
155+
"qs": "^6.10.3",
155156
"reflect-metadata": "^0.1.13",
156157
"scryptsy": "^2.1.0",
157158
"snackbarjs": "^1.1.0",

src/core/config/Categories.json

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
"From HTML Entity",
4040
"URL Encode",
4141
"URL Decode",
42+
"Query String Encode",
43+
"Query String Decode",
4244
"Escape Unicode Characters",
4345
"Unescape Unicode Characters",
4446
"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
@@ -130,6 +130,7 @@ import "./tests/FletcherChecksum.mjs";
130130
import "./tests/CMAC.mjs";
131131
import "./tests/AESKeyWrap.mjs";
132132
import "./tests/Rabbit.mjs";
133+
import "./tests/QueryString.mjs";
133134

134135
// Cannot test operations that use the File type yet
135136
// import "./tests/SplitColourChannels.mjs";
+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)