source: node_modules/undici/lib/proxy-agent.js@ 65b6638

main
Last change on this file since 65b6638 was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

  • Property mode set to 100644
File size: 5.5 KB
Line 
1'use strict'
2
3const { kProxy, kClose, kDestroy, kInterceptors } = require('./core/symbols')
4const { URL } = require('url')
5const Agent = require('./agent')
6const Pool = require('./pool')
7const DispatcherBase = require('./dispatcher-base')
8const { InvalidArgumentError, RequestAbortedError } = require('./core/errors')
9const buildConnector = require('./core/connect')
10
11const kAgent = Symbol('proxy agent')
12const kClient = Symbol('proxy client')
13const kProxyHeaders = Symbol('proxy headers')
14const kRequestTls = Symbol('request tls settings')
15const kProxyTls = Symbol('proxy tls settings')
16const kConnectEndpoint = Symbol('connect endpoint function')
17
18function defaultProtocolPort (protocol) {
19 return protocol === 'https:' ? 443 : 80
20}
21
22function buildProxyOptions (opts) {
23 if (typeof opts === 'string') {
24 opts = { uri: opts }
25 }
26
27 if (!opts || !opts.uri) {
28 throw new InvalidArgumentError('Proxy opts.uri is mandatory')
29 }
30
31 return {
32 uri: opts.uri,
33 protocol: opts.protocol || 'https'
34 }
35}
36
37function defaultFactory (origin, opts) {
38 return new Pool(origin, opts)
39}
40
41class ProxyAgent extends DispatcherBase {
42 constructor (opts) {
43 super(opts)
44 this[kProxy] = buildProxyOptions(opts)
45 this[kAgent] = new Agent(opts)
46 this[kInterceptors] = opts.interceptors && opts.interceptors.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent)
47 ? opts.interceptors.ProxyAgent
48 : []
49
50 if (typeof opts === 'string') {
51 opts = { uri: opts }
52 }
53
54 if (!opts || !opts.uri) {
55 throw new InvalidArgumentError('Proxy opts.uri is mandatory')
56 }
57
58 const { clientFactory = defaultFactory } = opts
59
60 if (typeof clientFactory !== 'function') {
61 throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
62 }
63
64 this[kRequestTls] = opts.requestTls
65 this[kProxyTls] = opts.proxyTls
66 this[kProxyHeaders] = opts.headers || {}
67
68 const resolvedUrl = new URL(opts.uri)
69 const { origin, port, host, username, password } = resolvedUrl
70
71 if (opts.auth && opts.token) {
72 throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token')
73 } else if (opts.auth) {
74 /* @deprecated in favour of opts.token */
75 this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}`
76 } else if (opts.token) {
77 this[kProxyHeaders]['proxy-authorization'] = opts.token
78 } else if (username && password) {
79 this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}`
80 }
81
82 const connect = buildConnector({ ...opts.proxyTls })
83 this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
84 this[kClient] = clientFactory(resolvedUrl, { connect })
85 this[kAgent] = new Agent({
86 ...opts,
87 connect: async (opts, callback) => {
88 let requestedHost = opts.host
89 if (!opts.port) {
90 requestedHost += `:${defaultProtocolPort(opts.protocol)}`
91 }
92 try {
93 const { socket, statusCode } = await this[kClient].connect({
94 origin,
95 port,
96 path: requestedHost,
97 signal: opts.signal,
98 headers: {
99 ...this[kProxyHeaders],
100 host
101 }
102 })
103 if (statusCode !== 200) {
104 socket.on('error', () => {}).destroy()
105 callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`))
106 }
107 if (opts.protocol !== 'https:') {
108 callback(null, socket)
109 return
110 }
111 let servername
112 if (this[kRequestTls]) {
113 servername = this[kRequestTls].servername
114 } else {
115 servername = opts.servername
116 }
117 this[kConnectEndpoint]({ ...opts, servername, httpSocket: socket }, callback)
118 } catch (err) {
119 callback(err)
120 }
121 }
122 })
123 }
124
125 dispatch (opts, handler) {
126 const { host } = new URL(opts.origin)
127 const headers = buildHeaders(opts.headers)
128 throwIfProxyAuthIsSent(headers)
129 return this[kAgent].dispatch(
130 {
131 ...opts,
132 headers: {
133 ...headers,
134 host
135 }
136 },
137 handler
138 )
139 }
140
141 async [kClose] () {
142 await this[kAgent].close()
143 await this[kClient].close()
144 }
145
146 async [kDestroy] () {
147 await this[kAgent].destroy()
148 await this[kClient].destroy()
149 }
150}
151
152/**
153 * @param {string[] | Record<string, string>} headers
154 * @returns {Record<string, string>}
155 */
156function buildHeaders (headers) {
157 // When using undici.fetch, the headers list is stored
158 // as an array.
159 if (Array.isArray(headers)) {
160 /** @type {Record<string, string>} */
161 const headersPair = {}
162
163 for (let i = 0; i < headers.length; i += 2) {
164 headersPair[headers[i]] = headers[i + 1]
165 }
166
167 return headersPair
168 }
169
170 return headers
171}
172
173/**
174 * @param {Record<string, string>} headers
175 *
176 * Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers
177 * Nevertheless, it was changed and to avoid a security vulnerability by end users
178 * this check was created.
179 * It should be removed in the next major version for performance reasons
180 */
181function throwIfProxyAuthIsSent (headers) {
182 const existProxyAuth = headers && Object.keys(headers)
183 .find((key) => key.toLowerCase() === 'proxy-authorization')
184 if (existProxyAuth) {
185 throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor')
186 }
187}
188
189module.exports = ProxyAgent
Note: See TracBrowser for help on using the repository browser.