Fix TLS crash with HTTP proxy
This commit is contained in:
parent
4592f062c4
commit
3fafcb15a1
|
@ -142,7 +142,6 @@
|
||||||
"got": "^13.0.0",
|
"got": "^13.0.0",
|
||||||
"got-ssrf": "^3.0.0",
|
"got-ssrf": "^3.0.0",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
"hpagent": "^1.0.0",
|
|
||||||
"http-problem-details": "^0.1.5",
|
"http-problem-details": "^0.1.5",
|
||||||
"ioredis": "^5.2.3",
|
"ioredis": "^5.2.3",
|
||||||
"ip-anonymize": "^0.1.0",
|
"ip-anonymize": "^0.1.0",
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
// Copy of un-maintained hpagent package with https://github.com/delvedor/hpagent/pull/114 fix
|
||||||
|
|
||||||
|
import http from 'http'
|
||||||
|
import https from 'https'
|
||||||
|
import { Socket, TcpNetConnectOpts } from 'net'
|
||||||
|
import { URL } from 'url'
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
keepAlive: boolean
|
||||||
|
keepAliveMsecs: number
|
||||||
|
maxSockets: number
|
||||||
|
maxFreeSockets: number
|
||||||
|
scheduling: 'lifo'
|
||||||
|
proxy: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpProxyAgent extends http.Agent {
|
||||||
|
private readonly proxy: URL
|
||||||
|
private readonly keepAlive: boolean
|
||||||
|
|
||||||
|
constructor (options: Options) {
|
||||||
|
const { proxy, ...opts } = options
|
||||||
|
|
||||||
|
super(opts)
|
||||||
|
|
||||||
|
this.keepAlive = options.keepAlive
|
||||||
|
|
||||||
|
this.proxy = typeof proxy === 'string'
|
||||||
|
? new URL(proxy)
|
||||||
|
: proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
createConnection (options: TcpNetConnectOpts, callback?: (err: Error, socket: Socket) => void) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'CONNECT',
|
||||||
|
host: this.proxy.hostname,
|
||||||
|
port: this.proxy.port,
|
||||||
|
path: `${options.host}:${options.port}`,
|
||||||
|
setHost: false,
|
||||||
|
headers: { connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
|
||||||
|
agent: false,
|
||||||
|
timeout: options.timeout || 0,
|
||||||
|
servername: undefined as string
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.proxy.username || this.proxy.password) {
|
||||||
|
const base64 = Buffer.from(
|
||||||
|
`${decodeURIComponent(this.proxy.username || '')}:${decodeURIComponent(this.proxy.password || '')}`
|
||||||
|
).toString('base64')
|
||||||
|
|
||||||
|
requestOptions.headers['proxy-authorization'] = `Basic ${base64}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.proxy.protocol === 'https:') {
|
||||||
|
requestOptions.servername = this.proxy.hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = (this.proxy.protocol === 'http:' ? http : https).request(requestOptions)
|
||||||
|
request.once('connect', (response, socket, head) => {
|
||||||
|
request.removeAllListeners()
|
||||||
|
socket.removeAllListeners()
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
callback(null, socket)
|
||||||
|
} else {
|
||||||
|
socket.destroy()
|
||||||
|
callback(new Error(`Bad response: ${response.statusCode}`), null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
request.once('timeout', () => {
|
||||||
|
request.destroy(new Error('Proxy timeout'))
|
||||||
|
})
|
||||||
|
|
||||||
|
request.once('error', err => {
|
||||||
|
request.removeAllListeners()
|
||||||
|
callback(err, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
request.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpsProxyAgent extends https.Agent {
|
||||||
|
private readonly proxy: URL
|
||||||
|
private readonly keepAlive: boolean
|
||||||
|
|
||||||
|
constructor (options: Options) {
|
||||||
|
const { proxy, ...opts } = options
|
||||||
|
|
||||||
|
super(opts)
|
||||||
|
|
||||||
|
this.keepAlive = options.keepAlive
|
||||||
|
|
||||||
|
this.proxy = typeof proxy === 'string'
|
||||||
|
? new URL(proxy)
|
||||||
|
: proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
createConnection (options: TcpNetConnectOpts, callback?: (err: Error, socket: Socket) => void) {
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'CONNECT',
|
||||||
|
host: this.proxy.hostname,
|
||||||
|
port: this.proxy.port,
|
||||||
|
path: `${options.host}:${options.port}`,
|
||||||
|
setHost: false,
|
||||||
|
headers: { connection: this.keepAlive ? 'keep-alive' : 'close', host: `${options.host}:${options.port}` },
|
||||||
|
agent: false,
|
||||||
|
timeout: options.timeout || 0,
|
||||||
|
servername: undefined as string
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.proxy.username || this.proxy.password) {
|
||||||
|
const base64 = Buffer.from(
|
||||||
|
`${decodeURIComponent(this.proxy.username || '')}:${decodeURIComponent(this.proxy.password || '')}
|
||||||
|
`).toString('base64')
|
||||||
|
|
||||||
|
requestOptions.headers['proxy-authorization'] = `Basic ${base64}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Necessary for the TLS check with the proxy to succeed.
|
||||||
|
if (this.proxy.protocol === 'https:') {
|
||||||
|
requestOptions.servername = this.proxy.hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = (this.proxy.protocol === 'http:' ? http : https).request(requestOptions)
|
||||||
|
request.once('connect', (response, socket, head) => {
|
||||||
|
request.removeAllListeners()
|
||||||
|
socket.removeAllListeners()
|
||||||
|
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
try {
|
||||||
|
// FIXME: typings doesn't include createConnection type in HTTP agent
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
const secureSocket = super.createConnection({ ...options, socket })
|
||||||
|
callback(null, secureSocket)
|
||||||
|
} catch (err) {
|
||||||
|
socket.destroy()
|
||||||
|
callback(err, null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
socket.destroy()
|
||||||
|
callback(new Error(`Bad response: ${response.statusCode}`), null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
request.once('timeout', () => {
|
||||||
|
request.destroy(new Error('Proxy timeout'))
|
||||||
|
})
|
||||||
|
|
||||||
|
request.once('error', err => {
|
||||||
|
request.removeAllListeners()
|
||||||
|
callback(err, null)
|
||||||
|
})
|
||||||
|
|
||||||
|
request.end()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,9 @@
|
||||||
function getProxy () {
|
export function getProxy () {
|
||||||
return process.env.HTTPS_PROXY ||
|
return process.env.HTTPS_PROXY ||
|
||||||
process.env.HTTP_PROXY ||
|
process.env.HTTP_PROXY ||
|
||||||
undefined
|
undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function isProxyEnabled () {
|
export function isProxyEnabled () {
|
||||||
return !!getProxy()
|
return !!getProxy()
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
getProxy,
|
|
||||||
isProxyEnabled
|
|
||||||
}
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { createWriteStream } from 'fs'
|
||||||
import { remove } from 'fs-extra/esm'
|
import { remove } from 'fs-extra/esm'
|
||||||
import got, { CancelableRequest, OptionsInit, OptionsOfTextResponseBody, OptionsOfUnknownResponseBody, RequestError, Response } from 'got'
|
import got, { CancelableRequest, OptionsInit, OptionsOfTextResponseBody, OptionsOfUnknownResponseBody, RequestError, Response } from 'got'
|
||||||
import { gotSsrf } from 'got-ssrf'
|
import { gotSsrf } from 'got-ssrf'
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'
|
import { HttpProxyAgent, HttpsProxyAgent } from '../helpers/hpagent.js'
|
||||||
import { ACTIVITY_PUB, BINARY_CONTENT_TYPES, PEERTUBE_VERSION, REQUEST_TIMEOUTS, WEBSERVER } from '../initializers/constants.js'
|
import { ACTIVITY_PUB, BINARY_CONTENT_TYPES, PEERTUBE_VERSION, REQUEST_TIMEOUTS, WEBSERVER } from '../initializers/constants.js'
|
||||||
import { pipelinePromise } from './core-utils.js'
|
import { pipelinePromise } from './core-utils.js'
|
||||||
import { logger, loggerTagsFactory } from './logger.js'
|
import { logger, loggerTagsFactory } from './logger.js'
|
||||||
|
|
|
@ -6386,11 +6386,6 @@ homedir-polyfill@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
parse-passwd "^1.0.0"
|
parse-passwd "^1.0.0"
|
||||||
|
|
||||||
hpagent@^1.0.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-1.2.0.tgz#0ae417895430eb3770c03443456b8d90ca464903"
|
|
||||||
integrity sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==
|
|
||||||
|
|
||||||
html-to-text@9.0.5, html-to-text@^9.0.5:
|
html-to-text@9.0.5, html-to-text@^9.0.5:
|
||||||
version "9.0.5"
|
version "9.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-9.0.5.tgz#6149a0f618ae7a0db8085dca9bbf96d32bb8368d"
|
resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-9.0.5.tgz#6149a0f618ae7a0db8085dca9bbf96d32bb8368d"
|
||||||
|
|
Loading…
Reference in New Issue