UNPKG

@proton/ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 130+ exchanges

179 lines (171 loc) 7.08 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var net = require('net'); var tls = require('tls'); var assert = require('assert'); var createDebug = require('debug'); var index = require('../agent-base/index.js'); var parseProxyResponse = require('./parse-proxy-response.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } var net__namespace = /*#__PURE__*/_interopNamespace(net); var tls__namespace = /*#__PURE__*/_interopNamespace(tls); var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert); var createDebug__default = /*#__PURE__*/_interopDefaultLegacy(createDebug); const debug = createDebug__default["default"]('https-proxy-agent'); /** * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to * the specified "HTTP(s) proxy server" in order to proxy HTTPS requests. * * Outgoing HTTP requests are first tunneled through the proxy server using the * `CONNECT` HTTP request method to establish a connection to the proxy server, * and then the proxy server connects to the destination target and issues the * HTTP request from the proxy server. * * `https:` requests have their socket connection upgraded to TLS once * the connection to the proxy server has been established. */ class HttpsProxyAgent extends index.Agent { constructor(proxy, opts) { super(opts); this.options = { path: undefined }; this.proxy = typeof proxy === 'string' ? new URL(proxy) : proxy; this.proxyHeaders = opts?.headers ?? {}; debug('Creating new HttpsProxyAgent instance: %o', this.proxy.href); // Trim off the brackets from IPv6 addresses const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, ''); const port = this.proxy.port ? parseInt(this.proxy.port, 10) : this.secureProxy ? 443 : 80; this.connectOpts = { // Attempt to negotiate http/1.1 for proxy servers that support http/2 ALPNProtocols: ['http/1.1'], ...(opts ? omit(opts, 'headers') : null), host, port, }; } get secureProxy() { return isHTTPS(this.proxy.protocol); } /** * Called when the node-core HTTP client library is creating a * new HTTP request. */ async connect(req, opts) { const { proxy, secureProxy } = this; if (!opts.host) { throw new TypeError('No "host" provided'); } // Create a socket connection to the proxy server. let socket; if (secureProxy) { debug('Creating `tls.Socket`: %o', this.connectOpts); socket = tls__namespace.connect(this.connectOpts); } else { debug('Creating `net.Socket`: %o', this.connectOpts); socket = net__namespace.connect(this.connectOpts); } const headers = typeof this.proxyHeaders === 'function' ? this.proxyHeaders() : { ...this.proxyHeaders }; const host = net__namespace.isIPv6(opts.host) ? `[${opts.host}]` : opts.host; let payload = `CONNECT ${host}:${opts.port} HTTP/1.1\r\n`; // Inject the `Proxy-Authorization` header if necessary. if (proxy.username || proxy.password) { const auth = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`; headers['Proxy-Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}`; } headers.Host = `${host}:${opts.port}`; if (!headers['Proxy-Connection']) { headers['Proxy-Connection'] = this.keepAlive ? 'Keep-Alive' : 'close'; } for (const name of Object.keys(headers)) { payload += `${name}: ${headers[name]}\r\n`; } const proxyResponsePromise = parseProxyResponse.parseProxyResponse(socket); socket.write(`${payload}\r\n`); const { connect, buffered } = await proxyResponsePromise; req.emit('proxyConnect', connect); // @ts-ignore this.emit('proxyConnect', connect, req); if (connect.statusCode === 200) { req.once('socket', resume); if (opts.secureEndpoint) { // The proxy is connecting to a TLS server, so upgrade // this socket connection to a TLS connection. debug('Upgrading socket connection to TLS'); const servername = opts.servername || opts.host; return tls__namespace.connect({ ...omit(opts, 'host', 'path', 'port'), socket, servername: net__namespace.isIP(servername) ? undefined : servername, }); } return socket; } // Some other status code that's not 200... need to re-play the HTTP // header "data" events onto the socket once the HTTP machinery is // attached so that the node core `http` can parse and handle the // error status code. // Close the original socket, and a new "fake" socket is returned // instead, so that the proxy doesn't get the HTTP request // written to it (which may contain `Authorization` headers or other // sensitive data). // // See: https://hackerone.com/reports/541502 socket.destroy(); const fakeSocket = new net__namespace.Socket({ writable: false }); fakeSocket.readable = true; // Need to wait for the "socket" event to re-play the "data" events. req.once('socket', (s) => { debug('Replaying proxy buffer for failed request'); assert__default["default"](s.listenerCount('data') > 0); // Replay the "buffered" Buffer onto the fake `socket`, since at // this point the HTTP module machinery has been hooked up for // the user. s.push(buffered); s.push(null); }); return fakeSocket; } } HttpsProxyAgent.protocols = ['http', 'https']; function resume(socket) { socket.resume(); } function isHTTPS(protocol) { return typeof protocol === 'string' ? /^https:?$/i.test(protocol) : false; } function omit(obj, ...keys) { const ret = {}; let key; for (key in obj) { if (!keys.includes(key)) { ret[key] = obj[key]; } } return ret; } exports.HttpsProxyAgent = HttpsProxyAgent;