@nxtedition/lib 15.0.34 → 15.0.36
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/package.json +2 -8
- package/lib/undici/index.js +0 -237
- package/lib/undici/interceptor/abort.js +0 -65
- package/lib/undici/interceptor/cache.js +0 -202
- package/lib/undici/interceptor/catch.js +0 -63
- package/lib/undici/interceptor/content.js +0 -141
- package/lib/undici/interceptor/log.js +0 -79
- package/lib/undici/interceptor/proxy.js +0 -200
- package/lib/undici/interceptor/redirect.js +0 -185
- package/lib/undici/interceptor/response-body-retry.js +0 -163
- package/lib/undici/interceptor/response-retry.js +0 -89
- package/lib/undici/interceptor/response-status-retry.js +0 -104
- package/lib/undici/interceptor/signal.js +0 -51
- package/lib/undici/utils.js +0 -183
- package/proxy.js +0 -106
- package/undici.js +0 -1
|
@@ -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)
|