Skip to content

Commit da8a24c

Browse files
committed
feat: add support to customize the query string parser
1 parent 3b85adc commit da8a24c

File tree

22 files changed

+378
-311
lines changed

22 files changed

+378
-311
lines changed

benchmarks/adonisjs.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,19 @@
77
* file that was distributed with this source code.
88
*/
99

10-
import proxyaddr from 'proxy-addr'
1110
import { createServer } from 'node:http'
1211
import Encryption from '@adonisjs/encryption'
1312
import { Application } from '@adonisjs/application'
1413

14+
import { defineConfig } from '../index.js'
1515
import { Server } from '../src/server/main.js'
1616

1717
const app = new Application(new URL('./', import.meta.url), { environment: 'web' })
1818
await app.init()
1919

2020
const encryption = new Encryption({ secret: 'averylongrandom32charslongsecret' })
2121

22-
const server = new Server(app, encryption, {
23-
etag: false,
24-
jsonpCallbackName: 'callback',
25-
cookie: {},
26-
subdomainOffset: 2,
27-
generateRequestId: false,
28-
trustProxy: proxyaddr.compile('loopback'),
29-
allowMethodSpoofing: false,
30-
})
22+
const server = new Server(app, encryption, defineConfig({}))
3123

3224
server.use([], [], {})
3325

src/define_config.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,19 @@ export function defineConfig(config: Partial<ServerConfig>): ServerConfig {
3131
sameSite: false,
3232
},
3333
qs: {
34-
depth: 5,
35-
parameterLimit: 1000,
36-
allowSparse: false,
37-
arrayLimit: 20,
38-
comma: true,
34+
parse: {
35+
depth: 5,
36+
parameterLimit: 1000,
37+
allowSparse: false,
38+
arrayLimit: 20,
39+
comma: true,
40+
},
41+
stringify: {
42+
encode: true,
43+
encodeValuesOnly: false,
44+
arrayFormat: 'indices' as const,
45+
skipNulls: false,
46+
},
3947
},
4048
...config,
4149
}

src/qs.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* @adonisjs/http-server
3+
*
4+
* (c) AdonisJS
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
import { parse, stringify } from 'qs'
11+
import { QSParserConfig } from './types/qs.js'
12+
13+
/**
14+
* Query string parser used to parse and stringify query
15+
* strings.
16+
*/
17+
export class Qs {
18+
#config: QSParserConfig
19+
20+
constructor(config: QSParserConfig) {
21+
this.#config = config
22+
}
23+
24+
parse(value: string) {
25+
return parse(value, this.#config.parse)
26+
}
27+
28+
stringify(value: any) {
29+
return stringify(value, this.#config.stringify)
30+
}
31+
}

src/redirect.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
* file that was distributed with this source code.
88
*/
99

10-
import qs from 'qs'
1110
import { parse } from 'node:url'
1211
import encodeUrl from 'encodeurl'
1312
import type { IncomingMessage } from 'node:http'
1413

1514
import debug from './debug.js'
15+
import type { Qs } from './qs.js'
1616
import type { Response } from './response.js'
1717
import type { Router } from './router/main.js'
1818
import type { MakeUrlOptions } from './types/route.js'
@@ -39,18 +39,20 @@ export class Redirect {
3939
#request: IncomingMessage
4040
#response: Response
4141
#router: Router
42+
#qs: Qs
4243

43-
constructor(request: IncomingMessage, response: Response, router: Router) {
44+
constructor(request: IncomingMessage, response: Response, router: Router, qs: Qs) {
4445
this.#request = request
4546
this.#response = response
4647
this.#router = router
48+
this.#qs = qs
4749
}
4850

4951
/**
5052
* Sends response by setting require headers
5153
*/
5254
#sendResponse(url: string, query: Record<string, any>) {
53-
const stringified = qs.stringify(query)
55+
const stringified = this.#qs.stringify(query)
5456

5557
url = stringified ? `${url}?${stringified}` : url
5658
debug('redirecting to url "%s"', url)
@@ -126,7 +128,7 @@ export class Redirect {
126128
* Parse query string from the referrer url
127129
*/
128130
if (this.#forwardQueryString) {
129-
query = qs.parse(url.query || '')
131+
query = this.#qs.parse(url.query || '')
130132
}
131133

132134
/**
@@ -163,7 +165,7 @@ export class Redirect {
163165
* Extract query string from the current URL
164166
*/
165167
if (this.#forwardQueryString) {
166-
query = qs.parse(parse(this.#request.url!).query || '')
168+
query = this.#qs.parse(parse(this.#request.url!).query || '')
167169
}
168170

169171
/**

src/request.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
* file that was distributed with this source code.
88
*/
99

10-
import qs from 'qs'
1110
import cuid from 'cuid'
1211
import fresh from 'fresh'
1312
import typeIs from 'type-is'
1413
import accepts from 'accepts'
1514
import { isIP } from 'node:net'
16-
import encodeUrl from 'encodeurl'
1715
import proxyaddr from 'proxy-addr'
1816
import { safeEqual } from '@poppinss/utils'
1917
import lodash from '@poppinss/utils/lodash'
@@ -22,6 +20,7 @@ import type Encryption from '@adonisjs/encryption'
2220
import { parse, UrlWithStringQuery } from 'node:url'
2321
import { ServerResponse, IncomingMessage, IncomingHttpHeaders } from 'node:http'
2422

23+
import type { Qs } from './qs.js'
2524
import { trustProxy } from './helpers.js'
2625
import { CookieParser } from './cookies/parser.js'
2726
import { RequestConfig } from './types/request.js'
@@ -37,6 +36,11 @@ import type { HttpContext } from './http_context/main.js'
3736
* using `request.request` property.
3837
*/
3938
export class Request extends Macroable {
39+
/**
40+
* Query string parser
41+
*/
42+
#qsParser: Qs
43+
4044
/**
4145
* Encryption module to verify signed URLs and unsign/decrypt
4246
* cookies
@@ -102,10 +106,12 @@ export class Request extends Macroable {
102106
public request: IncomingMessage,
103107
public response: ServerResponse,
104108
encryption: Encryption,
105-
config: RequestConfig
109+
config: RequestConfig,
110+
qsParser: Qs
106111
) {
107112
super()
108113

114+
this.#qsParser = qsParser
109115
this.#config = config
110116
this.#encryption = encryption
111117
this.parsedUrl = parse(this.request.url!, false)
@@ -117,7 +123,7 @@ export class Request extends Macroable {
117123
*/
118124
#parseQueryString() {
119125
if (this.parsedUrl.query) {
120-
this.updateQs(qs.parse(this.parsedUrl.query, this.#config.qs))
126+
this.updateQs(this.#qsParser.parse(this.parsedUrl.query))
121127
this.#originalRequestData = { ...this.#requestData }
122128
}
123129
}
@@ -925,10 +931,10 @@ export class Request extends Macroable {
925931
return false
926932
}
927933

928-
const queryString = qs.stringify(rest)
934+
const queryString = this.#qsParser.stringify(rest)
929935

930936
return queryString
931-
? safeEqual(signedUrl, `${this.url()}?${encodeUrl(queryString)}`)
937+
? safeEqual(signedUrl, `${this.url()}?${queryString}`)
932938
: safeEqual(signedUrl, this.url())
933939
}
934940

src/response.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type Encryption from '@adonisjs/encryption'
2323
import contentDisposition from 'content-disposition'
2424
import { ServerResponse, IncomingMessage, OutgoingHttpHeaders } from 'node:http'
2525

26+
import type { Qs } from './qs.js'
2627
import { Redirect } from './redirect.js'
2728
import type { Router } from './router/main.js'
2829
import type { HttpContext } from './http_context/main.js'
@@ -43,6 +44,11 @@ const CACHEABLE_HTTP_METHODS = ['GET', 'HEAD']
4344
* streamlining the process of writing response body and automatically setting up appropriate headers.
4445
*/
4546
export class Response extends Macroable {
47+
/**
48+
* Query string parser
49+
*/
50+
#qs: Qs
51+
4652
/**
4753
* Outgoing headers
4854
*/
@@ -106,9 +112,12 @@ export class Response extends Macroable {
106112
public response: ServerResponse,
107113
encryption: Encryption,
108114
config: ResponseConfig,
109-
router: Router
115+
router: Router,
116+
qs: Qs
110117
) {
111118
super()
119+
120+
this.#qs = qs
112121
this.#config = config
113122
this.#router = router
114123
this.#cookieSerializer = new CookieSerializer(encryption)
@@ -881,7 +890,7 @@ export class Response extends Macroable {
881890
forwardQueryString: boolean = false,
882891
statusCode: number = 302
883892
): Redirect | void {
884-
const handler = new Redirect(this.request, this, this.#router)
893+
const handler = new Redirect(this.request, this, this.#router, this.#qs)
885894

886895
if (forwardQueryString) {
887896
handler.withQs()

src/router/lookup_store/main.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type Encryption from '@adonisjs/encryption'
1111

12+
import type { Qs } from '../../qs.js'
1213
import { UrlBuilder } from './url_builder.js'
1314
import { RouteFinder } from './route_finder.js'
1415
import type { RouteJSON } from '../../types/route.js'
@@ -22,10 +23,20 @@ export class LookupStore {
2223
* List of routes grouped by domain
2324
*/
2425
#routes: { [domain: string]: RouteJSON[] } = {}
26+
27+
/**
28+
* Encryption for making URLs
29+
*/
2530
#encryption: Encryption
2631

27-
constructor(encryption: Encryption) {
32+
/**
33+
* Query string parser for making URLs
34+
*/
35+
#qsParser: Qs
36+
37+
constructor(encryption: Encryption, qsParser: Qs) {
2838
this.#encryption = encryption
39+
this.#qsParser = qsParser
2940
}
3041

3142
/**
@@ -50,7 +61,7 @@ export class LookupStore {
5061
*/
5162
builderForDomain(domain: string) {
5263
const routes = this.#routes[domain]
53-
return new UrlBuilder(this.#encryption, new RouteFinder(routes || []))
64+
return new UrlBuilder(this.#encryption, new RouteFinder(routes || []), this.#qsParser)
5465
}
5566

5667
/**

src/router/lookup_store/url_builder.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
* file that was distributed with this source code.
88
*/
99

10-
import { stringify } from 'qs'
11-
import encodeUrl from 'encodeurl'
1210
import { RuntimeException } from '@poppinss/utils'
1311
import type Encryption from '@adonisjs/encryption'
12+
13+
import type { Qs } from '../../qs.js'
1414
import type { RouteFinder } from './route_finder.js'
1515

1616
/**
@@ -27,6 +27,11 @@ import type { RouteFinder } from './route_finder.js'
2727
* ```
2828
*/
2929
export class UrlBuilder {
30+
/**
31+
* Query string parser
32+
*/
33+
#qsParser: Qs
34+
3035
/**
3136
* The parameters to apply on the route
3237
*/
@@ -58,7 +63,8 @@ export class UrlBuilder {
5863
*/
5964
#routeFinder: RouteFinder
6065

61-
constructor(encryption: Encryption, routeFinder: RouteFinder) {
66+
constructor(encryption: Encryption, routeFinder: RouteFinder, qsParser: Qs) {
67+
this.#qsParser = qsParser
6268
this.#encryption = encryption
6369
this.#routeFinder = routeFinder
6470
}
@@ -131,8 +137,8 @@ export class UrlBuilder {
131137
*/
132138
#suffixQueryString(url: string, qs?: Record<string, any>): string {
133139
if (qs) {
134-
const queryString = stringify(qs)
135-
url = queryString ? `${url}?${encodeUrl(queryString)}` : url
140+
const queryString = this.#qsParser.stringify(qs)
141+
url = queryString ? `${url}?${queryString}` : url
136142
}
137143

138144
return url

src/router/main.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type Encryption from '@adonisjs/encryption'
1212
import { RuntimeException } from '@poppinss/utils'
1313
import type { Application } from '@adonisjs/application'
1414

15+
import type { Qs } from '../qs.js'
1516
import { Route } from './route.js'
1617
import { RouteGroup } from './group.js'
1718
import { BriskRoute } from './brisk.js'
@@ -99,9 +100,10 @@ export class Router<
99100
constructor(
100101
app: Application,
101102
encryption: Encryption,
102-
middlewareStore: MiddlewareStore<NamedMiddleware>
103+
middlewareStore: MiddlewareStore<NamedMiddleware>,
104+
qsParser: Qs
103105
) {
104-
super(encryption)
106+
super(encryption, qsParser)
105107
this.#app = app
106108
this.#middlewareStore = middlewareStore
107109
}

0 commit comments

Comments
 (0)