-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathparseUrlParameters.js
More file actions
148 lines (131 loc) · 5.08 KB
/
parseUrlParameters.js
File metadata and controls
148 lines (131 loc) · 5.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/
/**
* Concatenate the given parameters key path to form proper URL query param key
*
* @param {string[]} parametersKeysPath the keys path to concatenate
* @return {string} the concatenated keys path
*/
const concatenateParametersKeyPath = (parametersKeysPath) => {
if (parametersKeysPath.length === 0) {
return 'no parameters keys';
}
const [mainKey, ...otherKeys] = parametersKeysPath;
return `${mainKey}${otherKeys.map((key) => `[${key}]`).join('')}`;
};
/**
* Error to be used when building parameters tree from keys path
*/
class ParameterBuildingError extends Error {
/**
* Constructor
*
* @param {string} message the global error message
* @param {string[]} parametersKeysPath the parameters keys path where the error occurred
*/
constructor(message, parametersKeysPath) {
super(`${message} - ${concatenateParametersKeyPath(parametersKeysPath)}`);
this._originalMessage = message;
this._parametersKeysPath = parametersKeysPath;
}
/**
* Return the orignal message of the error, without concatenated parameters key path
*
* @return {string} the original message
*/
get originalMessage() {
return this._originalMessage;
}
/**
* Return the parameters keys path of the error
*
* @return {string[]} the parameters keys path
*/
get parametersKeyPath() {
return this._parametersKeysPath;
}
}
/**
* Build a parameter object or array from a parameters keys path
*
* For example, a parameter `key1[key2][]=value` translates to keys path ['key1', 'key2', ''] and will lead to {key1: {key2: [value]}}
*
* @param {object|array} parentParameter the parameter's object or array up to the current key
* @param {array} nestedKeys the keys path to build from the current point
* @param {string} value the value of the parameter represented by the key path
* @return {void}
*/
const buildParameterFromNestedKeys = (parentParameter, nestedKeys, value) => {
const currentKey = nestedKeys.shift();
/*
* Protect against prototype polluting assignment
* https://codeql.github.com/codeql-query-help/javascript/js-prototype-polluting-assignment/
*/
if (currentKey === '__proto__' || currentKey === 'constructor' || currentKey === 'prototype') {
throw new Error(`Unauthorized parameters key ${currentKey}`);
}
if (currentKey === '') {
// Parameter must be an array and the value is a new item in that array
if (!Array.isArray(parentParameter)) {
throw new ParameterBuildingError('Expected node in parameters tree to be an array', [currentKey]);
}
parentParameter.push(value);
} else if (currentKey) {
// Parameter must be an object and the value is a property in that array
if (Array.isArray(parentParameter) || typeof parentParameter !== 'object' || parentParameter === null) {
throw new ParameterBuildingError('Expected node in parameters tree to be an object', [currentKey]);
}
if (nestedKeys.length > 0) {
// We still have nested keys to fill
if (!(currentKey in parentParameter)) {
parentParameter[currentKey] = nestedKeys[0] === '' ? [] : {};
}
try {
buildParameterFromNestedKeys(parentParameter[currentKey], nestedKeys, value);
} catch (e) {
if (e instanceof ParameterBuildingError) {
throw new ParameterBuildingError(e.originalMessage, [currentKey, ...e.parametersKeyPath]);
}
throw e;
}
} else {
if (Array.isArray(parentParameter[currentKey])) {
throw new ParameterBuildingError('Node in parameters tree is an array but no more nested keys', [currentKey]);
} else if (typeof parentParameter[currentKey] === 'object' && parentParameter[currentKey] !== null) {
throw new ParameterBuildingError('Node in parameters tree is an object but no more nested keys', [currentKey]);
}
parentParameter[currentKey] = value;
}
}
};
/**
* Extract the parameters tree from the given URL parameters (any value after the "&" in a URL)
*
* @param {URLSearchParams} urlSearchParams the URL search parameters string
* @param {object} [parameters] the existing parameters tree object (will be modified in place)
* @return {object} the parameter tree
*/
exports.parseUrlParameters = (urlSearchParams, parameters) => {
if (urlSearchParams.size === 0) {
return {};
}
if (!parameters) {
parameters = {};
}
for (const [key, value] of urlSearchParams.entries()) {
const [firstKey, ...dirtyKeys] = key.split('[');
const nestedKeys = [firstKey, ...dirtyKeys.map((key) => key.slice(0, -1))];
buildParameterFromNestedKeys(parameters, nestedKeys, value);
}
return parameters;
};