qs 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -4
- package/lib/parse.js +13 -7
- package/lib/stringify.js +24 -14
- package/lib/utils.js +12 -6
- package/package.json +2 -3
- package/test/parse.js +26 -1
- package/test/stringify.js +33 -11
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A querystring parsing and stringifying library with some added security.
|
|
|
6
6
|
|
|
7
7
|
Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf)
|
|
8
8
|
|
|
9
|
-
The **qs** module was
|
|
9
|
+
The **qs** module was originally created and maintained by [TJ Holowaychuk](https://github.com/visionmedia/node-querystring).
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
@@ -17,7 +17,11 @@ var obj = Qs.parse('a=c'); // { a: 'c' }
|
|
|
17
17
|
var str = Qs.stringify(obj); // 'a=c'
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
### Objects
|
|
20
|
+
### Parsing Objects
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
Qs.parse(string, [depth], [delimiter]);
|
|
24
|
+
```
|
|
21
25
|
|
|
22
26
|
**qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`.
|
|
23
27
|
For example, the string `'foo[bar]=baz'` converts to:
|
|
@@ -30,6 +34,13 @@ For example, the string `'foo[bar]=baz'` converts to:
|
|
|
30
34
|
}
|
|
31
35
|
```
|
|
32
36
|
|
|
37
|
+
URI encoded strings work too:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
Qs.parse('a%5Bb%5D=c');
|
|
41
|
+
// { a: { b: 'c' } }
|
|
42
|
+
```
|
|
43
|
+
|
|
33
44
|
You can also nest your objects, like `'foo[bar][baz]=foobarbaz'`:
|
|
34
45
|
|
|
35
46
|
```javascript
|
|
@@ -72,7 +83,14 @@ Qs.parse('a[b][c][d][e][f][g][h][i]=j', 1);
|
|
|
72
83
|
|
|
73
84
|
The depth limit mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number.
|
|
74
85
|
|
|
75
|
-
|
|
86
|
+
An optional delimiter can also be passed:
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
Qs.parse('a=b;c=d', ';');
|
|
90
|
+
// { a: 'b', c: 'd' }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Parsing Arrays
|
|
76
94
|
|
|
77
95
|
**qs** can also parse arrays using a similar `[]` notation:
|
|
78
96
|
|
|
@@ -97,6 +115,15 @@ Qs.parse('a[1]=b&a[15]=c');
|
|
|
97
115
|
// { a: ['b', 'c'] }
|
|
98
116
|
```
|
|
99
117
|
|
|
118
|
+
Note that an empty string is also a value, and will be preserved:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
Qs.parse('a[]=&a[]=b');
|
|
122
|
+
// { a: ['', 'b'] }
|
|
123
|
+
Qs.parse('a[0]=b&a[1]=&a[2]=c');
|
|
124
|
+
// { a: ['b', '', 'c'] }
|
|
125
|
+
```
|
|
126
|
+
|
|
100
127
|
**qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will
|
|
101
128
|
instead be converted to an object with the index as the key:
|
|
102
129
|
|
|
@@ -117,4 +144,48 @@ You can also create arrays of objects:
|
|
|
117
144
|
```javascript
|
|
118
145
|
Qs.parse('a[][b]=c');
|
|
119
146
|
// { a: [{ b: 'c' }] }
|
|
120
|
-
```
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Stringifying
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
Qs.stringify(object, [delimiter]);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
When stringifying, **qs** always URI encodes output. Objects are stringified as you would expect:
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
Qs.stringify({ a: 'b' });
|
|
159
|
+
// 'a=b'
|
|
160
|
+
Qs.stringify({ a: { b: 'c' } });
|
|
161
|
+
// 'a%5Bb%5D=c'
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Examples beyond this point will be shown as though the output is not URI encoded for clarity. Please note that the return values in these cases *will* be URI encoded during real usage.
|
|
165
|
+
|
|
166
|
+
When arrays are stringified, they are always given explicit indices:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
Qs.stringify({ a: ['b', 'c', 'd'] });
|
|
170
|
+
// 'a[0]=b&a[1]=c&a[2]=d'
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Empty strings and null values will omit the value, but the equals sign (=) remains in place:
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
Qs.stringify({ a: '' });
|
|
177
|
+
// 'a='
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Properties that are set to `undefined` will be omitted entirely:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
Qs.stringify({ a: null, b: undefined });
|
|
184
|
+
// 'a='
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The delimiter may be overridden with stringify as well:
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
Qs.stringify({ a: 'b', c: 'd' }, ';');
|
|
191
|
+
// 'a=b;c=d'
|
package/lib/parse.js
CHANGED
|
@@ -6,16 +6,19 @@ var Utils = require('./utils');
|
|
|
6
6
|
// Declare internals
|
|
7
7
|
|
|
8
8
|
var internals = {
|
|
9
|
+
delimiter: '&',
|
|
9
10
|
depth: 5,
|
|
10
11
|
arrayLimit: 20,
|
|
11
12
|
parametersLimit: 1000
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
internals.parseValues = function (str) {
|
|
16
|
+
internals.parseValues = function (str, delimiter) {
|
|
17
|
+
|
|
18
|
+
delimiter = typeof delimiter === 'undefined' ? internals.delimiter : delimiter;
|
|
16
19
|
|
|
17
20
|
var obj = {};
|
|
18
|
-
var parts = str.split(
|
|
21
|
+
var parts = str.split(delimiter, internals.parametersLimit);
|
|
19
22
|
|
|
20
23
|
for (var i = 0, il = parts.length; i < il; ++i) {
|
|
21
24
|
var part = parts[i];
|
|
@@ -79,8 +82,6 @@ internals.parseKeys = function (key, val, depth) {
|
|
|
79
82
|
return;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
|
-
depth = typeof depth === 'undefined' ? internals.depth : depth;
|
|
83
|
-
|
|
84
85
|
// The regex chunks
|
|
85
86
|
|
|
86
87
|
var parent = /^([^\[\]]*)/;
|
|
@@ -124,7 +125,7 @@ internals.parseKeys = function (key, val, depth) {
|
|
|
124
125
|
};
|
|
125
126
|
|
|
126
127
|
|
|
127
|
-
module.exports = function (str, depth) {
|
|
128
|
+
module.exports = function (str, depth, delimiter) {
|
|
128
129
|
|
|
129
130
|
if (str === '' ||
|
|
130
131
|
str === null ||
|
|
@@ -133,11 +134,16 @@ module.exports = function (str, depth) {
|
|
|
133
134
|
return {};
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
|
|
137
|
+
if (typeof depth !== 'number') {
|
|
138
|
+
delimiter = depth;
|
|
139
|
+
depth = internals.depth;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
var tempObj = typeof str === 'string' ? internals.parseValues(str, delimiter) : Utils.clone(str);
|
|
137
143
|
var obj = {};
|
|
138
144
|
|
|
139
145
|
// Iterate over the keys and setup the new object
|
|
140
|
-
|
|
146
|
+
//
|
|
141
147
|
for (var key in tempObj) {
|
|
142
148
|
if (tempObj.hasOwnProperty(key)) {
|
|
143
149
|
var newObj = internals.parseKeys(key, tempObj[key], depth);
|
package/lib/stringify.js
CHANGED
|
@@ -3,26 +3,35 @@
|
|
|
3
3
|
|
|
4
4
|
// Declare internals
|
|
5
5
|
|
|
6
|
-
var internals = {
|
|
6
|
+
var internals = {
|
|
7
|
+
delimiter: '&'
|
|
8
|
+
};
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
internals.stringify = function (obj, prefix) {
|
|
10
12
|
|
|
11
|
-
if (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if (Buffer.isBuffer(obj)) {
|
|
14
|
+
obj = obj.toString();
|
|
15
|
+
}
|
|
16
|
+
else if (obj instanceof Date) {
|
|
17
|
+
obj = obj.toISOString();
|
|
18
|
+
}
|
|
19
|
+
else if (obj === null) {
|
|
20
|
+
obj = '';
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
if (obj ===
|
|
18
|
-
|
|
23
|
+
if (typeof obj === 'string' ||
|
|
24
|
+
typeof obj === 'number' ||
|
|
25
|
+
typeof obj === 'boolean') {
|
|
26
|
+
|
|
27
|
+
return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)];
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
var values = [];
|
|
22
31
|
|
|
23
32
|
for (var key in obj) {
|
|
24
33
|
if (obj.hasOwnProperty(key)) {
|
|
25
|
-
values = values.concat(internals.stringify(obj[key], prefix + '[' +
|
|
34
|
+
values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']'));
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
37
|
|
|
@@ -30,16 +39,17 @@ internals.stringify = function (obj, prefix) {
|
|
|
30
39
|
};
|
|
31
40
|
|
|
32
41
|
|
|
33
|
-
module.exports = function (obj) {
|
|
42
|
+
module.exports = function (obj, delimiter) {
|
|
43
|
+
|
|
44
|
+
delimiter = typeof delimiter === 'undefined' ? internals.delimiter : delimiter;
|
|
34
45
|
|
|
35
46
|
var keys = [];
|
|
36
|
-
var value = JSON.parse(JSON.stringify(obj));
|
|
37
47
|
|
|
38
|
-
for (var key in
|
|
39
|
-
if (
|
|
40
|
-
keys = keys.concat(internals.stringify(
|
|
48
|
+
for (var key in obj) {
|
|
49
|
+
if (obj.hasOwnProperty(key)) {
|
|
50
|
+
keys = keys.concat(internals.stringify(obj[key], key));
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
|
|
44
|
-
return keys.join(
|
|
54
|
+
return keys.join(delimiter);
|
|
45
55
|
};
|
package/lib/utils.js
CHANGED
|
@@ -10,7 +10,10 @@ exports.arrayToObject = function (source) {
|
|
|
10
10
|
|
|
11
11
|
var obj = {};
|
|
12
12
|
for (var i = 0, il = source.length; i < il; ++i) {
|
|
13
|
-
|
|
13
|
+
if (typeof source[i] !== 'undefined') {
|
|
14
|
+
|
|
15
|
+
obj[i] = source[i];
|
|
16
|
+
}
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
return obj;
|
|
@@ -50,8 +53,13 @@ exports.merge = function (target, source) {
|
|
|
50
53
|
|
|
51
54
|
if (Array.isArray(source)) {
|
|
52
55
|
for (var i = 0, il = source.length; i < il; ++i) {
|
|
53
|
-
if (source[i] !== undefined) {
|
|
54
|
-
obj[i]
|
|
56
|
+
if (typeof source[i] !== 'undefined') {
|
|
57
|
+
if (typeof obj[i] === 'object') {
|
|
58
|
+
obj[i] = exports.merge(obj[i], source[i]);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
obj[i] = source[i];
|
|
62
|
+
}
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
@@ -110,9 +118,7 @@ exports.compact = function (obj) {
|
|
|
110
118
|
compacted[key] = [];
|
|
111
119
|
|
|
112
120
|
for (var i = 0, l = obj[key].length; i < l; i++) {
|
|
113
|
-
if (obj[key]
|
|
114
|
-
obj[key][i]) {
|
|
115
|
-
|
|
121
|
+
if (typeof obj[key][i] !== 'undefined') {
|
|
116
122
|
compacted[key].push(obj[key][i]);
|
|
117
123
|
}
|
|
118
124
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A querystring parser that supports nesting and arrays, with a depth limit",
|
|
5
5
|
"homepage": "https://github.com/hapijs/qs",
|
|
6
6
|
"main": "index.js",
|
|
7
|
-
"dependencies": {
|
|
8
|
-
},
|
|
7
|
+
"dependencies": {},
|
|
9
8
|
"devDependencies": {
|
|
10
9
|
"lab": "3.x.x"
|
|
11
10
|
},
|
package/test/parse.js
CHANGED
|
@@ -138,6 +138,13 @@ describe('#parse', function () {
|
|
|
138
138
|
expect(Qs.parse('foo[bad]=baz&foo[]=bar')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar' } });
|
|
139
139
|
expect(Qs.parse('foo[]=bar&foo[bad]=baz')).to.deep.equal({ foo: { '0': 'bar', bad: 'baz' } });
|
|
140
140
|
expect(Qs.parse('foo[bad]=baz&foo[]=bar&foo[]=foo')).to.deep.equal({ foo: { bad: 'baz', '0': 'bar', '1': 'foo' } });
|
|
141
|
+
expect(Qs.parse('foo[0][a]=a&foo[0][b]=b&foo[1][a]=aa&foo[1][b]=bb')).to.deep.equal({foo: [ {a: 'a', b: 'b'}, {a: 'aa', b: 'bb'} ]});
|
|
142
|
+
done();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('correctly prunes undefined values when converting an array to an object', function (done) {
|
|
146
|
+
|
|
147
|
+
expect(Qs.parse('a[2]=b&a[99999999]=c')).to.deep.equal({ a: { '2': 'b', '99999999': 'c' } });
|
|
141
148
|
done();
|
|
142
149
|
});
|
|
143
150
|
|
|
@@ -178,6 +185,13 @@ describe('#parse', function () {
|
|
|
178
185
|
done();
|
|
179
186
|
});
|
|
180
187
|
|
|
188
|
+
it('allows for empty strings in arrays', function (done) {
|
|
189
|
+
|
|
190
|
+
expect(Qs.parse('a[]=b&a[]=&a[]=c')).to.deep.equal({ a: ['b', '', 'c'] });
|
|
191
|
+
expect(Qs.parse('a[0]=b&a[1]=&a[2]=c&a[19]=')).to.deep.equal({ a: ['b', '', 'c', ''] });
|
|
192
|
+
done();
|
|
193
|
+
});
|
|
194
|
+
|
|
181
195
|
it('should compact sparse arrays', function (done) {
|
|
182
196
|
|
|
183
197
|
expect(Qs.parse('a[10]=1&a[2]=2')).to.deep.equal({ a: ['2', '1'] });
|
|
@@ -223,8 +237,19 @@ describe('#parse', function () {
|
|
|
223
237
|
it('should not throw when a native prototype has an enumerable property', { parallel: false }, function (done) {
|
|
224
238
|
|
|
225
239
|
Object.prototype.crash = '';
|
|
226
|
-
|
|
240
|
+
Array.prototype.crash = '';
|
|
241
|
+
expect(Qs.parse.bind(null, 'a=b')).to.not.throw();
|
|
242
|
+
expect(Qs.parse('a=b')).to.deep.equal({ a: 'b' });
|
|
243
|
+
expect(Qs.parse.bind(null, 'a[][b]=c')).to.not.throw();
|
|
244
|
+
expect(Qs.parse('a[][b]=c')).to.deep.equal({ a: [{ b: 'c' }] });
|
|
227
245
|
delete Object.prototype.crash;
|
|
246
|
+
delete Array.prototype.crash;
|
|
247
|
+
done();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('parses a string with an alternative delimiter', function (done) {
|
|
251
|
+
|
|
252
|
+
expect(Qs.parse('a=b;c=d', ';')).to.deep.equal({ a: 'b', c: 'd' });
|
|
228
253
|
done();
|
|
229
254
|
});
|
|
230
255
|
});
|
package/test/stringify.js
CHANGED
|
@@ -30,33 +30,33 @@ describe('#stringify', function () {
|
|
|
30
30
|
|
|
31
31
|
it('stringifies a nested object', function (done) {
|
|
32
32
|
|
|
33
|
-
expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a
|
|
34
|
-
expect(Qs.stringify({ a: { b: { c: { d: 'e' } } } })).to.equal('a
|
|
33
|
+
expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a%5Bb%5D=c');
|
|
34
|
+
expect(Qs.stringify({ a: { b: { c: { d: 'e' } } } })).to.equal('a%5Bb%5D%5Bc%5D%5Bd%5D=e');
|
|
35
35
|
done();
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
it('stringifies an array value', function (done) {
|
|
39
39
|
|
|
40
|
-
expect(Qs.stringify({ a: ['b', 'c', 'd'] })).to.equal('a
|
|
40
|
+
expect(Qs.stringify({ a: ['b', 'c', 'd'] })).to.equal('a%5B0%5D=b&a%5B1%5D=c&a%5B2%5D=d');
|
|
41
41
|
done();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
it('stringifies a nested array value', function (done) {
|
|
45
45
|
|
|
46
|
-
expect(Qs.stringify({ a: { b: ['c', 'd'] } })).to.equal('a
|
|
46
|
+
expect(Qs.stringify({ a: { b: ['c', 'd'] } })).to.equal('a%5Bb%5D%5B0%5D=c&a%5Bb%5D%5B1%5D=d');
|
|
47
47
|
done();
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
it('stringifies an object inside an array', function (done) {
|
|
51
51
|
|
|
52
|
-
expect(Qs.stringify({ a: [{ b: 'c' }] })).to.equal('a
|
|
53
|
-
expect(Qs.stringify({ a: [{ b: { c: [1] } }] })).to.equal('a
|
|
52
|
+
expect(Qs.stringify({ a: [{ b: 'c' }] })).to.equal('a%5B0%5D%5Bb%5D=c');
|
|
53
|
+
expect(Qs.stringify({ a: [{ b: { c: [1] } }] })).to.equal('a%5B0%5D%5Bb%5D%5Bc%5D%5B0%5D=1');
|
|
54
54
|
done();
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
it('stringifies a complicated object', function (done) {
|
|
58
58
|
|
|
59
|
-
expect(Qs.stringify({ a: { b: 'c', d: 'e' } })).to.equal('a
|
|
59
|
+
expect(Qs.stringify({ a: { b: 'c', d: 'e' } })).to.equal('a%5Bb%5D=c&a%5Bd%5D=e');
|
|
60
60
|
done();
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -64,15 +64,15 @@ describe('#stringify', function () {
|
|
|
64
64
|
|
|
65
65
|
expect(Qs.stringify({ a: '' })).to.equal('a=');
|
|
66
66
|
expect(Qs.stringify({ a: '', b: '' })).to.equal('a=&b=');
|
|
67
|
-
expect(Qs.stringify({ a: null })).to.equal('a');
|
|
68
|
-
expect(Qs.stringify({ a: { b: null } })).to.equal('a
|
|
67
|
+
expect(Qs.stringify({ a: null })).to.equal('a=');
|
|
68
|
+
expect(Qs.stringify({ a: { b: null } })).to.equal('a%5Bb%5D=');
|
|
69
69
|
done();
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it('drops keys with a value of undefined', function (done) {
|
|
73
73
|
|
|
74
74
|
expect(Qs.stringify({ a: undefined })).to.equal('');
|
|
75
|
-
expect(Qs.stringify({ a: { b: undefined, c: null } })).to.equal('a
|
|
75
|
+
expect(Qs.stringify({ a: { b: undefined, c: null } })).to.equal('a%5Bc%5D=');
|
|
76
76
|
done();
|
|
77
77
|
});
|
|
78
78
|
|
|
@@ -100,8 +100,30 @@ describe('#stringify', function () {
|
|
|
100
100
|
|
|
101
101
|
Object.prototype.crash = 'test';
|
|
102
102
|
expect(Qs.stringify({ a: 'b'})).to.equal('a=b');
|
|
103
|
-
expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a
|
|
103
|
+
expect(Qs.stringify({ a: { b: 'c' } })).to.equal('a%5Bb%5D=c');
|
|
104
104
|
delete Object.prototype.crash;
|
|
105
105
|
done();
|
|
106
106
|
});
|
|
107
|
+
|
|
108
|
+
it('stringifies boolean values', function (done) {
|
|
109
|
+
|
|
110
|
+
expect(Qs.stringify({ a: true })).to.equal('a=true');
|
|
111
|
+
expect(Qs.stringify({ a: { b: true } })).to.equal('a%5Bb%5D=true');
|
|
112
|
+
expect(Qs.stringify({ b: false })).to.equal('b=false');
|
|
113
|
+
expect(Qs.stringify({ b: { c: false } })).to.equal('b%5Bc%5D=false');
|
|
114
|
+
done();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('stringifies buffer values', function (done) {
|
|
118
|
+
|
|
119
|
+
expect(Qs.stringify({ a: new Buffer('test') })).to.equal('a=test');
|
|
120
|
+
expect(Qs.stringify({ a: { b: new Buffer('test') } })).to.equal('a%5Bb%5D=test');
|
|
121
|
+
done();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('stringifies an object using an alternative delimiter', function (done) {
|
|
125
|
+
|
|
126
|
+
expect(Qs.stringify({ a: 'b', c: 'd' }, ';')).to.equal('a=b;c=d');
|
|
127
|
+
done();
|
|
128
|
+
});
|
|
107
129
|
});
|