@nxtedition/lib 15.0.33 → 15.0.35

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.
@@ -1,141 +0,0 @@
1
- const crypto = require('node:crypto')
2
- const stream = require('node:stream')
3
- const assert = require('node:assert')
4
- const { findHeader } = require('../utils')
5
- const { isStream } = require('../../../stream')
6
-
7
- class Handler {
8
- constructor(opts, { handler }) {
9
- this.handler = handler
10
- this.md5 = null
11
- this.length = null
12
-
13
- this.hasher = null
14
- this.pos = 0
15
- }
16
-
17
- onConnect(abort) {
18
- return this.handler.onConnect?.(abort)
19
- }
20
-
21
- onUpgrade(statusCode, rawHeaders, socket) {
22
- return this.handler.onUpgrade?.(statusCode, rawHeaders, socket)
23
- }
24
-
25
- onBodySent(chunk) {
26
- return this.handler.onBodySent?.(chunk)
27
- }
28
-
29
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
30
- this.md5 = findHeader(rawHeaders, 'content-md5')
31
- this.length = findHeader(rawHeaders, 'content-length')
32
-
33
- this.hasher = this.md5 != null ? crypto.createHash('md5') : null
34
-
35
- return this.handler.onHeaders?.(statusCode, rawHeaders, resume, statusMessage)
36
- }
37
-
38
- onData(chunk) {
39
- this.pos += chunk.length
40
- this.hasher?.update(chunk)
41
- return this.handler.onData?.(chunk)
42
- }
43
-
44
- onComplete(rawTrailers) {
45
- const hash = this.hasher?.digest('base64')
46
- if (this.md5 != null && hash !== this.md5) {
47
- this.handler.onError?.(
48
- Object.assign(new Error('Request Content-Length mismatch'), {
49
- expected: this.md5,
50
- actual: hash,
51
- }),
52
- )
53
- }
54
- if (this.length != null && this.pos !== Number(this.length)) {
55
- return this.handler.onError?.(
56
- Object.assign(new Error('Request Content-Length mismatch'), {
57
- expected: Number(this.length),
58
- actual: this.pos,
59
- }),
60
- )
61
- }
62
- return this.handler.onComplete?.(rawTrailers)
63
- }
64
-
65
- onError(err) {
66
- this.handler.onError?.(err)
67
- }
68
- }
69
-
70
- module.exports = (dispatch) => (opts, handler) => {
71
- const md5 = opts.headers?.['content-md5'] ?? opts.headers?.['Content-MD5']
72
- const length = opts.headers?.['content-lenght'] ?? opts.headers?.['Content-Length']
73
-
74
- if (md5 == null && length == null) {
75
- return dispatch(opts, new Handler(opts, { handler }))
76
- }
77
-
78
- if (isStream(opts.body)) {
79
- const hasher = md5 ? crypto.createHash('md5') : null
80
- let pos = 0
81
-
82
- opts = {
83
- ...opts,
84
- body: stream.pipeline(
85
- opts.body,
86
- new stream.Transform({
87
- transform(chunk, encoding, callback) {
88
- pos += chunk.length
89
- hasher?.update(chunk)
90
- callback(null)
91
- },
92
- final(callback) {
93
- const hash = hasher?.digest('base64')
94
- if (md5 != null && hash !== md5) {
95
- callback(
96
- Object.assign(new Error('Request Content-MD5 mismatch'), {
97
- expected: md5,
98
- actual: hash,
99
- }),
100
- )
101
- } else if (length != null && pos !== Number(length)) {
102
- callback(
103
- Object.assign(new Error('Request Content-Length mismatch'), {
104
- expected: Number(length),
105
- actual: pos,
106
- }),
107
- )
108
- } else {
109
- callback(null)
110
- }
111
- },
112
- }),
113
- () => {},
114
- ),
115
- }
116
- } else if (opts.body instanceof Buffer || typeof opts.body === 'string') {
117
- const buf = Buffer.from(opts.body)
118
- const hasher = md5 ? crypto.createHash('md5') : null
119
-
120
- const hash = hasher?.update(buf).digest('base64')
121
- const pos = buf.length
122
-
123
- if (md5 != null && hash !== md5) {
124
- throw Object.assign(new Error('Request Content-MD5 mismatch'), {
125
- expected: md5,
126
- actual: hash,
127
- })
128
- }
129
-
130
- if (length != null && pos !== Number(length)) {
131
- throw Object.assign(new Error('Request Content-Length mismatch'), {
132
- expected: Number(length),
133
- actual: pos,
134
- })
135
- }
136
- } else {
137
- assert(false, 'not implemented')
138
- }
139
-
140
- return dispatch(opts, new Handler(opts, { handler }))
141
- }
@@ -1,79 +0,0 @@
1
- const { parseHeaders } = require('../../../http.js')
2
- const xuid = require('xuid')
3
- const { performance } = require('perf_hooks')
4
-
5
- class Handler {
6
- constructor(opts, { handler }) {
7
- this.handler = handler
8
- this.opts = opts.id ? opts : { ...opts, id: xuid() }
9
- this.abort = null
10
- this.aborted = false
11
- this.logger = opts.logger.child({ ureq: { id: opts.id } })
12
- this.pos = 0
13
- this.startTime = 0
14
- }
15
-
16
- onConnect(abort) {
17
- this.abort = abort
18
- this.startTime = performance.now()
19
- this.logger.debug({ ureq: this.opts }, 'upstream request started')
20
- this.handler.onConnect?.((reason) => {
21
- this.aborted = true
22
- this.abort(reason)
23
- })
24
- }
25
-
26
- onUpgrade(statusCode, rawHeaders, socket) {
27
- this.logger.debug({ ureq: this.opts }, 'upstream request upgraded')
28
- socket.on('close', () => {
29
- this.logger.debug({ ureq: this.opts }, 'upstream request socket closed')
30
- })
31
- return this.handler.onUpgrade?.(statusCode, rawHeaders, socket)
32
- }
33
-
34
- onBodySent(chunk) {
35
- return this.handler.onBodySent?.(chunk)
36
- }
37
-
38
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
39
- this.logger.debug(
40
- {
41
- ures: { statusCode, headers: parseHeaders(rawHeaders) },
42
- elapsedTime: this.startTime - performance.now(),
43
- },
44
- 'upstream request response',
45
- )
46
- return this.handler.onHeaders?.(statusCode, rawHeaders, resume, statusMessage)
47
- }
48
-
49
- onData(chunk) {
50
- this.pos += chunk.length
51
- return this.handler.onData?.(chunk)
52
- }
53
-
54
- onComplete(rawTrailers) {
55
- this.logger.debug(
56
- { bytesRead: this.pos, elapsedTime: this.startTime - performance.now() },
57
- 'upstream request completed',
58
- )
59
- return this.handler.onComplete?.(rawTrailers)
60
- }
61
-
62
- onError(err) {
63
- if (this.aborted) {
64
- this.logger.debug(
65
- { bytesRead: this.pos, elapsedTime: this.startTime - performance.now(), err },
66
- 'upstream request aborted',
67
- )
68
- } else {
69
- this.logger.error(
70
- { bytesRead: this.pos, elapsedTime: this.startTime - performance.now(), err },
71
- 'upstream request failed',
72
- )
73
- }
74
- return this.handler.onError?.(err)
75
- }
76
- }
77
-
78
- module.exports = (dispatch) => (opts, handler) =>
79
- opts.logger ? dispatch(opts, new Handler(opts, { handler })) : dispatch(opts, handler)
@@ -1,200 +0,0 @@
1
- const createError = require('http-errors')
2
- const net = require('net')
3
-
4
- class Handler {
5
- constructor(opts, { handler }) {
6
- this.handler = handler
7
- this.opts = opts
8
- }
9
-
10
- onConnect(abort) {
11
- return this.handler.onConnect?.(abort)
12
- }
13
-
14
- onUpgrade(statusCode, rawHeaders, socket) {
15
- return this.handler.onUpgrade?.(
16
- statusCode,
17
- reduceHeaders(
18
- {
19
- headers: rawHeaders,
20
- httpVersion: this.opts.proxy.httpVersion ?? this.opts.proxy.req?.httpVersion,
21
- socket: null,
22
- proxyName: this.opts.proxy.name,
23
- },
24
- (acc, key, val) => {
25
- acc.push(key, val)
26
- return acc
27
- },
28
- [],
29
- ),
30
- socket,
31
- )
32
- }
33
-
34
- onBodySent(chunk) {
35
- return this.handler.onBodySent?.(chunk)
36
- }
37
-
38
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
39
- return this.handler.onHeaders?.(
40
- statusCode,
41
- reduceHeaders(
42
- {
43
- headers: rawHeaders,
44
- httpVersion: this.opts.proxy.httpVersion ?? this.opts.proxy.req?.httpVersion,
45
- socket: null,
46
- proxyName: this.opts.proxy.name,
47
- },
48
- (acc, key, val) => {
49
- acc.push(key, val)
50
- return acc
51
- },
52
- [],
53
- ),
54
- resume,
55
- statusMessage,
56
- )
57
- }
58
-
59
- onData(chunk) {
60
- return this.handler.onData?.(chunk)
61
- }
62
-
63
- onComplete(rawTrailers) {
64
- return this.handler.onComplete?.(rawTrailers)
65
- }
66
-
67
- onError(err) {
68
- return this.handler.onError?.(err)
69
- }
70
- }
71
-
72
- module.exports = (dispatch) => (opts, handler) => {
73
- if (!opts.proxy) {
74
- return dispatch(opts, handler)
75
- }
76
-
77
- const headers = reduceHeaders(
78
- {
79
- headers: opts.headers ?? {},
80
- httpVersion: opts.proxy.httpVersion ?? opts.proxy.req?.httpVersion,
81
- socket: opts.proxy.socket ?? opts.proxy.req?.socket,
82
- proxyName: opts.proxy.name,
83
- },
84
- (obj, key, val) => {
85
- obj[key] = val
86
- return obj
87
- },
88
- {},
89
- )
90
-
91
- opts = { ...opts, headers }
92
-
93
- return dispatch(opts, new Handler(opts, { handler }))
94
- }
95
-
96
- // This expression matches hop-by-hop headers.
97
- // These headers are meaningful only for a single transport-level connection,
98
- // and must not be retransmitted by proxies or cached.
99
- const HOP_EXPR =
100
- /^(te|host|upgrade|trailers|connection|keep-alive|http2-settings|transfer-encoding|proxy-connection|proxy-authenticate|proxy-authorization)$/i
101
-
102
- function forEachHeader(headers, fn) {
103
- if (Array.isArray(headers)) {
104
- for (let n = 0; n < headers.length; n += 2) {
105
- fn(headers[n + 0].toString(), headers[n + 1].toString())
106
- }
107
- } else {
108
- for (const [key, val] of Object.entries(headers)) {
109
- fn(key, val)
110
- }
111
- }
112
- }
113
-
114
- // Removes hop-by-hop and pseudo headers.
115
- // Updates via and forwarded headers.
116
- // Only hop-by-hop headers may be set using the Connection general header.
117
- function reduceHeaders({ headers, proxyName, httpVersion, socket }, fn, acc) {
118
- let via = ''
119
- let forwarded = ''
120
- let host = ''
121
- let authority = ''
122
- let connection = ''
123
-
124
- forEachHeader(headers, (key, val) => {
125
- const len = key.length
126
- if (len === 3 && !via && key.toLowerCase() === 'via') {
127
- via = val
128
- } else if (len === 4 && !host && key.toLowerCase() === 'host') {
129
- host = val
130
- } else if (len === 9 && !forwarded && key.toLowerCase() === 'forwarded') {
131
- forwarded = val
132
- } else if (len === 10 && !connection && key.toLowerCase() === 'connection') {
133
- connection = val
134
- } else if (len === 10 && !authority && key.toLowerCase() === ':authority') {
135
- authority = val
136
- }
137
- })
138
-
139
- let remove = []
140
- if (connection && !HOP_EXPR.test(connection)) {
141
- remove = connection.split(/,\s*/)
142
- }
143
-
144
- forEachHeader(headers, (key, val) => {
145
- if (key.charAt(0) !== ':' && !remove.includes(key) && !HOP_EXPR.test(key)) {
146
- acc = fn(acc, key, val)
147
- }
148
- })
149
-
150
- if (socket) {
151
- const forwardedHost = authority || host
152
- acc = fn(
153
- acc,
154
- 'forwarded',
155
- (forwarded ? forwarded + ', ' : '') +
156
- [
157
- socket.localAddress && `by=${printIp(socket.localAddress, socket.localPort)}`,
158
- socket.remoteAddress && `for=${printIp(socket.remoteAddress, socket.remotePort)}`,
159
- `proto=${socket.encrypted ? 'https' : 'http'}`,
160
- forwardedHost && `host="${forwardedHost}"`,
161
- ].join(';'),
162
- )
163
- } else if (forwarded) {
164
- // The forwarded header should not be included in response.
165
- throw new createError.BadGateway()
166
- }
167
-
168
- if (proxyName) {
169
- if (via) {
170
- if (via.split(',').some((name) => name.endsWith(proxyName))) {
171
- throw new createError.LoopDetected()
172
- }
173
- via += ', '
174
- } else {
175
- via = ''
176
- }
177
- via += `${httpVersion ?? 'HTTP/1.1'} ${proxyName}`
178
- }
179
-
180
- if (via) {
181
- acc = fn(acc, 'via', via)
182
- }
183
-
184
- return acc
185
- }
186
-
187
- function printIp(address, port) {
188
- const isIPv6 = net.isIPv6(address)
189
- let str = `${address}`
190
- if (isIPv6) {
191
- str = `[${str}]`
192
- }
193
- if (port) {
194
- str = `${str}:${port}`
195
- }
196
- if (isIPv6 || port) {
197
- str = `"${str}"`
198
- }
199
- return str
200
- }
@@ -1,185 +0,0 @@
1
- const assert = require('assert')
2
- const { findHeader } = require('../utils')
3
- const { isDisturbed, parseURL } = require('../utils')
4
-
5
- const redirectableStatusCodes = [300, 301, 302, 303, 307, 308]
6
-
7
- class Handler {
8
- constructor(opts, { dispatch, handler }) {
9
- this.dispatch = dispatch
10
- this.handler = handler
11
- this.opts = opts
12
- this.abort = null
13
- this.aborted = false
14
- this.reason = null
15
-
16
- this.count = 0
17
- this.location = null
18
-
19
- this.handler.onConnect?.((reason) => {
20
- this.aborted = true
21
- if (this.abort) {
22
- this.abort(reason)
23
- } else {
24
- this.reason = reason
25
- }
26
- })
27
- }
28
-
29
- onConnect(abort) {
30
- if (this.aborted) {
31
- abort(this.reason)
32
- } else {
33
- this.abort = abort
34
- }
35
- }
36
-
37
- onUpgrade(statusCode, headers, socket) {
38
- this.handler.onUpgrade?.(statusCode, headers, socket)
39
- }
40
-
41
- onError(error) {
42
- this.handler.onError?.(error)
43
- }
44
-
45
- onHeaders(statusCode, headers, resume, statusText) {
46
- if (redirectableStatusCodes.indexOf(statusCode) === -1) {
47
- return this.handler.onHeaders?.(statusCode, headers, resume, statusText)
48
- }
49
-
50
- if (isDisturbed(this.opts.body)) {
51
- throw new Error(`Disturbed request cannot be redirected.`)
52
- }
53
-
54
- this.location = findHeader(headers, 'location')
55
-
56
- if (!this.location) {
57
- throw new Error(`Missing redirection location.`)
58
- }
59
-
60
- this.count += 1
61
-
62
- if (typeof this.opts.follow === 'function') {
63
- if (!this.opts.follow(this.location, this.count)) {
64
- return this.handler.onHeaders?.(statusCode, headers, resume, statusText)
65
- }
66
- } else {
67
- const maxCount = this.opts.follow.count ?? 0
68
-
69
- if (this.count >= maxCount) {
70
- throw new Error(`Max redirections reached: ${maxCount}.`)
71
- }
72
- }
73
-
74
- const { origin, pathname, search } = parseURL(
75
- new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin)),
76
- )
77
- const path = search ? `${pathname}${search}` : pathname
78
-
79
- // Remove headers referring to the original URL.
80
- // By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers.
81
- // https://tools.ietf.org/html/rfc7231#section-6.4
82
- this.opts = {
83
- ...this.opts,
84
- headers: cleanRequestHeaders(
85
- this.opts.headers,
86
- statusCode === 303,
87
- this.opts.origin !== origin,
88
- ),
89
- path,
90
- origin,
91
- query: null,
92
- }
93
-
94
- // https://tools.ietf.org/html/rfc7231#section-6.4.4
95
- // In case of HTTP 303, always replace method to be either HEAD or GET
96
- if (statusCode === 303 && this.opts.method !== 'HEAD') {
97
- this.opts = { ...this.opts, method: 'GET', body: null }
98
- }
99
- }
100
-
101
- onData(chunk) {
102
- if (this.location) {
103
- /*
104
- https://tools.ietf.org/html/rfc7231#section-6.4
105
-
106
- TLDR: undici always ignores 3xx response bodies.
107
-
108
- Redirection is used to serve the requested resource from another URL, so it is assumes that
109
- no body is generated (and thus can be ignored). Even though generating a body is not prohibited.
110
-
111
- For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually
112
- (which means it's optional and not mandated) contain just an hyperlink to the value of
113
- the Location response header, so the body can be ignored safely.
114
-
115
- For status 300, which is "Multiple Choices", the spec mentions both generating a Location
116
- response header AND a response body with the other possible location to follow.
117
- Since the spec explicitily chooses not to specify a format for such body and leave it to
118
- servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it.
119
- */
120
- } else {
121
- return this.handler.onData?.(chunk)
122
- }
123
- }
124
-
125
- onComplete(trailers) {
126
- if (this.location) {
127
- /*
128
- https://tools.ietf.org/html/rfc7231#section-6.4
129
-
130
- TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections
131
- and neither are useful if present.
132
-
133
- See comment on onData method above for more detailed informations.
134
- */
135
-
136
- this.location = null
137
-
138
- this.dispatch(this.opts, this)
139
- } else {
140
- this.handler.onComplete?.(trailers)
141
- }
142
- }
143
-
144
- onBodySent(chunk) {
145
- if (this.handler.onBodySent) {
146
- this.handler.onBodySent?.(chunk)
147
- }
148
- }
149
- }
150
-
151
- // https://tools.ietf.org/html/rfc7231#section-6.4.4
152
- function shouldRemoveHeader(header, removeContent, unknownOrigin) {
153
- return (
154
- (header.length === 4 && header.toString().toLowerCase() === 'host') ||
155
- (removeContent && header.toString().toLowerCase().indexOf('content-') === 0) ||
156
- (unknownOrigin &&
157
- header.length === 13 &&
158
- header.toString().toLowerCase() === 'authorization') ||
159
- (unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie')
160
- )
161
- }
162
-
163
- // https://tools.ietf.org/html/rfc7231#section-6.4
164
- function cleanRequestHeaders(headers, removeContent, unknownOrigin) {
165
- const ret = []
166
- if (Array.isArray(headers)) {
167
- for (let i = 0; i < headers.length; i += 2) {
168
- if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) {
169
- ret.push(headers[i], headers[i + 1])
170
- }
171
- }
172
- } else if (headers && typeof headers === 'object') {
173
- for (const key of Object.keys(headers)) {
174
- if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) {
175
- ret.push(key, headers[key])
176
- }
177
- }
178
- } else {
179
- assert(headers == null, 'headers must be an object or an array')
180
- }
181
- return ret
182
- }
183
-
184
- module.exports = (dispatch) => (opts, handler) =>
185
- opts.follow ? dispatch(opts, new Handler(opts, { handler, dispatch })) : dispatch(opts, handler)