Skip to content

Commit 82b60e0

Browse files
committed
Optimize code and fix bug / security issue
1 parent 51c93e0 commit 82b60e0

File tree

4 files changed

+92
-60
lines changed

4 files changed

+92
-60
lines changed

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,28 @@ benchmark time (avg) (min … max) p75 p99
3030
-------------------------------------------------------- -----------------------------
3131
• basic
3232
-------------------------------------------------------- -----------------------------
33-
faster-qs 603.21 ns/iter (547.73 ns … 1.14 µs) 607.07 ns 1.14 µs 1.14 µs
34-
qs 6.72 µs/iter (5.28 µs … 440.78 µs) 5.86 µs 16.99 µs 25.17 µs
35-
fast-querystring 490.43 ns/iter (393.92 ns … 1.02 µs) 455.21 ns 974.32 ns 1.02 µs
36-
node:querystring 479.66 ns/iter (426.49 ns … 897.91 ns) 477.84 ns 814.14 ns 897.91 ns
33+
faster-qs 583.93 ns/iter (533.32 ns … 1.22 µs) 568.81 ns 1.22 µs 1.22 µs
34+
qs 5.96 µs/iter (5.25 µs … 294.51 µs) 5.61 µs 13.49 µs 16.99 µs
35+
fast-querystring 408.39 ns/iter (381.55 ns … 1 µs) 403.67 ns 600.15 ns 1 µs
36+
node:querystring 450.88 ns/iter (425.73 ns … 604.37 ns) 456.71 ns 532.03 ns 604.37 ns
3737

3838
summary for basic
3939
faster-qs
40-
1.26x slower than node:querystring
41-
1.23x slower than fast-querystring
42-
11.14x faster than qs
40+
1.43x slower than fast-querystring
41+
1.3x slower than node:querystring
42+
10.2x faster than qs
4343

4444
• deep object
4545
-------------------------------------------------------- -----------------------------
46-
faster-qs 4.46 µs/iter (4.29 µs … 4.9 µs) 4.55 µs 4.9 µs 4.9 µs
47-
qs 17.48 µs/iter (15.15 µs … 803.78 µs) 16.15 µs 33.99 µs 43.18 µs
48-
fast-querystring 1.21 µs/iter (1.14 µs … 1.48 µs) 1.22 µs 1.48 µs 1.48 µs
49-
node:querystring 1.65 µs/iter (1.54 µs … 2.05 µs) 1.65 µs 2.05 µs 2.05 µs
46+
faster-qs 3.37 µs/iter (3.26 µs … 3.89 µs) 3.34 µs 3.89 µs 3.89 µs
47+
qs 16.91 µs/iter (15.2 µs … 274.57 µs) 16.08 µs 34.54 µs 51.02 µs
48+
fast-querystring 1.2 µs/iter (1.16 µs … 1.36 µs) 1.2 µs 1.36 µs 1.36 µs
49+
node:querystring 1.63 µs/iter (1.57 µs … 1.93 µs) 1.62 µs 1.93 µs 1.93 µs
5050

5151
summary for deep object
5252
faster-qs
53-
3.7x slower than fast-querystring
54-
2.71x slower than node:querystring
55-
3.92x faster than qs
53+
2.82x slower than fast-querystring
54+
2.07x slower than node:querystring
55+
5.01x faster than qs
5656

5757
```

__tests__/index.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import chai from 'chai'
2-
import qs from 'qs'
32
import parse from '../index.mjs'
43

54
const expect = (payload, target) =>
@@ -46,6 +45,9 @@ describe('parse', () => {
4645
expect('a[a][a][][b]&a[a][a][][]&a[a][a][][][c]', {
4746
a: { a: { a: [ { b: '' }, [ '' ], [ { c: '' } ] ] } },
4847
})
48+
expect('a=1&a[a]=1', {
49+
a: '1',
50+
})
4951
})
5052

5153
it('drop insecure key', () => {
@@ -58,5 +60,8 @@ describe('parse', () => {
5860
expect('a[][__proto__]=1', {
5961
a: [{}],
6062
})
63+
expect('a[]=1&a[length]=1e100&a[]=2', {
64+
a: ['1', '2'],
65+
})
6166
})
6267
})

benchmark/node.mjs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ group('Array extend', () => {
1616
bench('Array.push spread', () => { const a = []; a.push(...[1]) })
1717
})
1818

19-
group('typecheck', () => {
20-
bench('typeof', () => typeof 1 === 'number')
21-
bench('isArray', () => Array.isArray(1))
19+
group('isArray', () => {
20+
const val = 0
21+
bench('isArray', () => Array.isArray(val))
22+
bench('constructor', () => val?.constructor === Array)
23+
bench('instanceof', () => val instanceof Array)
24+
bench('typeof', () => typeof val === 'object' && val.constructor === Array)
2225
})
2326

2427
group('recursive vs iterative', () => {
@@ -66,4 +69,23 @@ group('loop keys vs entries', () => {
6669
})
6770
})
6871

72+
group('includes vs indexOf', () => {
73+
bench('includes', () => [1, 2, 3].includes(3))
74+
bench('indexOf', () => [1, 2, 3].indexOf(3) !== -1)
75+
})
76+
77+
group('Set vs Array', () => {
78+
const set = new Set([1, 2, 3])
79+
const arr = [1, 2, 3]
80+
bench('Set', () => set.has(3))
81+
bench('Array', () => arr.includes(3))
82+
})
83+
84+
group('Key exists', () => {
85+
const obj = {}
86+
bench('get', () => obj['a'])
87+
bench('in', () => 'a' in obj)
88+
bench('hasOwnProperty', () => obj.hasOwnProperty('a'))
89+
})
90+
6991
await run()

index.mjs

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { parse as parser } from 'fast-querystring'
22

3+
const BAD_KEYS = new Set([
4+
'length',
5+
...Object.getOwnPropertyNames(Object.prototype),
6+
])
7+
38
const isPosInt = (str) => {
49
const n = Number(str)
510
return Number.isInteger(n) && n >= 0
@@ -8,67 +13,67 @@ const isPosInt = (str) => {
813
const resolvePath = (o, path, v=null, d) => {
914
let next = path
1015
let cur = o
11-
let l, r, k
12-
for (; d > 0 || !next; d--) {
16+
for (let l, r, k; d > 0 || !next; d--) {
1317
l = next.indexOf('[')
1418
r = next.indexOf(']')
1519
if (l === -1 || r === -1 || l > r) return { [next]: v }
1620
k = next.slice(l + 1, r) || cur?.length || 0
1721
next = next.slice(r + 1)
1822
if (isPosInt(k)) {
19-
let i = Number(k)
20-
if (!cur) cur = []
21-
if (!o) o = cur
22-
if (!Array.isArray(cur)) {
23-
if (!k && !i) i = Object.keys(cur).length
24-
cur = cur[i] = {}
23+
if (!cur) {
24+
cur = []
25+
o = cur
26+
}
27+
if (!(cur instanceof Array)) {
28+
if (!k) k = Object.keys(cur).length
29+
cur = cur[k] = {}
2530
continue
2631
}
27-
const extend = i - cur.length + 1
32+
k = Number(k)
33+
const extend = k - cur.length + 1
2834
if (extend > 0) {
2935
cur.push.apply(cur, Array(extend))
3036
}
31-
if (next === '[]' && typeof v !== 'string') {
32-
cur[i] = [...cur[i] ?? [], ...v]
33-
return o
34-
} else if (next) {
35-
if (next.startsWith('[]')) {
36-
cur = cur[k] ??= []
37-
} else {
38-
if (!cur[i]) cur[i] = {}
39-
else if (Array.isArray(cur[i]))
40-
cur[i] = { ...cur[i] }
41-
cur = cur[i]
37+
if (!next) {
38+
if (typeof v !== 'string') { // join array
39+
cur.splice(k, 1)
40+
cur.push.apply(cur, v)
41+
} else { // set index
42+
cur[k] = v
4243
}
43-
} else {
44-
cur[i] = v
4544
return o
4645
}
47-
} else if (!{}[k]) {
48-
if (!cur) cur = {}
49-
if (!o) o = cur
50-
if (next) {
51-
if (next.startsWith('[]')) {
52-
cur = cur[k] ??= []
53-
} else {
54-
if (!cur[k]) cur[k] = {}
55-
else if (Array.isArray(cur[k]))
56-
cur[k] = { ...cur[k] }
57-
cur = cur[k]
58-
}
59-
} else {
46+
} else if (!BAD_KEYS.has(k)) {
47+
if (!cur) {
48+
cur = {}
49+
o = cur
50+
}
51+
if (typeof cur === 'string') {
52+
break
53+
}
54+
if (!next) {
6055
cur[k] = v
6156
return o
6257
}
6358
} else {
6459
break
6560
}
61+
// resolve next cursor
62+
if (next.startsWith('[]')) {
63+
cur = cur[k] ??= []
64+
} else {
65+
cur[k] ??= {}
66+
if (cur[k] instanceof Array)
67+
cur[k] = { ...cur[k] }
68+
cur = cur[k]
69+
}
6670
}
67-
if (Array.isArray(cur)) {
68-
if (next) cur.push({ [next]: v })
69-
else cur.push(v)
70-
} else if (typeof cur === 'object') {
71-
if (next) cur[next] = v
71+
if (next) {
72+
if (cur instanceof Array) {
73+
cur.push({ [next]: v })
74+
} else if (typeof cur === 'object') {
75+
cur[next] = v
76+
}
7277
}
7378
return o
7479
}
@@ -83,9 +88,9 @@ export default (str, depth=5) => {
8388
if (l > 0 && k.indexOf(']') > l) {
8489
const key = k.slice(0, l)
8590
const path = k.slice(l)
86-
if (path === '[]') {
91+
if (path === '[]') { // optimize 1d array
8792
if (key in o) {
88-
if (!Array.isArray(o[key]))
93+
if (!(o[key] instanceof Array))
8994
o[key] = [o[key]]
9095
o[key] = [].concat(o[key], v)
9196
} else o[key] = v

0 commit comments

Comments
 (0)