blob: 10f156a434db4c4965a902cfae5237690257572a [file] [log] [blame]
Leo Repp58b9f112021-11-22 11:57:47 +01001'use strict';
2var strictUriEncode = require('strict-uri-encode');
3var objectAssign = require('object-assign');
4var decodeComponent = require('decode-uri-component');
5
6function encoderForArrayFormat(opts) {
7 switch (opts.arrayFormat) {
8 case 'index':
9 return function (key, value, index) {
10 return value === null ? [
11 encode(key, opts),
12 '[',
13 index,
14 ']'
15 ].join('') : [
16 encode(key, opts),
17 '[',
18 encode(index, opts),
19 ']=',
20 encode(value, opts)
21 ].join('');
22 };
23
24 case 'bracket':
25 return function (key, value) {
26 return value === null ? encode(key, opts) : [
27 encode(key, opts),
28 '[]=',
29 encode(value, opts)
30 ].join('');
31 };
32
33 default:
34 return function (key, value) {
35 return value === null ? encode(key, opts) : [
36 encode(key, opts),
37 '=',
38 encode(value, opts)
39 ].join('');
40 };
41 }
42}
43
44function parserForArrayFormat(opts) {
45 var result;
46
47 switch (opts.arrayFormat) {
48 case 'index':
49 return function (key, value, accumulator) {
50 result = /\[(\d*)\]$/.exec(key);
51
52 key = key.replace(/\[\d*\]$/, '');
53
54 if (!result) {
55 accumulator[key] = value;
56 return;
57 }
58
59 if (accumulator[key] === undefined) {
60 accumulator[key] = {};
61 }
62
63 accumulator[key][result[1]] = value;
64 };
65
66 case 'bracket':
67 return function (key, value, accumulator) {
68 result = /(\[\])$/.exec(key);
69 key = key.replace(/\[\]$/, '');
70
71 if (!result) {
72 accumulator[key] = value;
73 return;
74 } else if (accumulator[key] === undefined) {
75 accumulator[key] = [value];
76 return;
77 }
78
79 accumulator[key] = [].concat(accumulator[key], value);
80 };
81
82 default:
83 return function (key, value, accumulator) {
84 if (accumulator[key] === undefined) {
85 accumulator[key] = value;
86 return;
87 }
88
89 accumulator[key] = [].concat(accumulator[key], value);
90 };
91 }
92}
93
94function encode(value, opts) {
95 if (opts.encode) {
96 return opts.strict ? strictUriEncode(value) : encodeURIComponent(value);
97 }
98
99 return value;
100}
101
102function keysSorter(input) {
103 if (Array.isArray(input)) {
104 return input.sort();
105 } else if (typeof input === 'object') {
106 return keysSorter(Object.keys(input)).sort(function (a, b) {
107 return Number(a) - Number(b);
108 }).map(function (key) {
109 return input[key];
110 });
111 }
112
113 return input;
114}
115
116function extract(str) {
117 var queryStart = str.indexOf('?');
118 if (queryStart === -1) {
119 return '';
120 }
121 return str.slice(queryStart + 1);
122}
123
124function parse(str, opts) {
125 opts = objectAssign({arrayFormat: 'none'}, opts);
126
127 var formatter = parserForArrayFormat(opts);
128
129 // Create an object with no prototype
130 // https://github.com/sindresorhus/query-string/issues/47
131 var ret = Object.create(null);
132
133 if (typeof str !== 'string') {
134 return ret;
135 }
136
137 str = str.trim().replace(/^[?#&]/, '');
138
139 if (!str) {
140 return ret;
141 }
142
143 str.split('&').forEach(function (param) {
144 var parts = param.replace(/\+/g, ' ').split('=');
145 // Firefox (pre 40) decodes `%3D` to `=`
146 // https://github.com/sindresorhus/query-string/pull/37
147 var key = parts.shift();
148 var val = parts.length > 0 ? parts.join('=') : undefined;
149
150 // missing `=` should be `null`:
151 // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
152 val = val === undefined ? null : decodeComponent(val);
153
154 formatter(decodeComponent(key), val, ret);
155 });
156
157 return Object.keys(ret).sort().reduce(function (result, key) {
158 var val = ret[key];
159 if (Boolean(val) && typeof val === 'object' && !Array.isArray(val)) {
160 // Sort object keys, not values
161 result[key] = keysSorter(val);
162 } else {
163 result[key] = val;
164 }
165
166 return result;
167 }, Object.create(null));
168}
169
170exports.extract = extract;
171exports.parse = parse;
172
173exports.stringify = function (obj, opts) {
174 var defaults = {
175 encode: true,
176 strict: true,
177 arrayFormat: 'none'
178 };
179
180 opts = objectAssign(defaults, opts);
181
182 if (opts.sort === false) {
183 opts.sort = function () {};
184 }
185
186 var formatter = encoderForArrayFormat(opts);
187
188 return obj ? Object.keys(obj).sort(opts.sort).map(function (key) {
189 var val = obj[key];
190
191 if (val === undefined) {
192 return '';
193 }
194
195 if (val === null) {
196 return encode(key, opts);
197 }
198
199 if (Array.isArray(val)) {
200 var result = [];
201
202 val.slice().forEach(function (val2) {
203 if (val2 === undefined) {
204 return;
205 }
206
207 result.push(formatter(key, val2, result.length));
208 });
209
210 return result.join('&');
211 }
212
213 return encode(key, opts) + '=' + encode(val, opts);
214 }).filter(function (x) {
215 return x.length > 0;
216 }).join('&') : '';
217};
218
219exports.parseUrl = function (str, opts) {
220 return {
221 url: str.split('?')[0] || '',
222 query: parse(extract(str), opts)
223 };
224};