From 0379e01a9f1b07005260056ed44d25e9c8a097ac Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Tue, 4 Feb 2020 19:15:03 -0800 Subject: [PATCH] Refactor to TypeScript (#95) * Refactor to TypeScript * Fix Node 6 * Prettier * Fix "end" event on non-200 status codes Fixes #87. Closes #93. * Prettier * Prettier * Better --- .gitignore | 3 +- index.d.ts | 22 ---- index.js | 239 ------------------------------------ package.json | 34 +++-- src/agent.ts | 215 ++++++++++++++++++++++++++++++++ src/index.ts | 39 ++++++ src/parse-proxy-response.ts | 82 +++++++++++++ test/test.js | 166 ++++++++++++++----------- tsconfig.json | 18 +++ 9 files changed, 477 insertions(+), 341 deletions(-) delete mode 100644 index.d.ts delete mode 100644 index.js create mode 100644 src/agent.ts create mode 100644 src/index.ts create mode 100644 src/parse-proxy-response.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index c650246c..e1d74da5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /node_modules /yarn.lock -/?.js +/?.?s +/dist diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index cec35d85..00000000 --- a/index.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -declare module 'https-proxy-agent' { - import * as https from 'https'; - - namespace HttpsProxyAgent { - interface HttpsProxyAgentOptions { - host: string; - port: number | string; - secureProxy?: boolean; - headers?: { - [key: string]: string; - }; - [key: string]: any; - } - } - - // HttpsProxyAgent doesnt *actually* extend https.Agent, but for my purposes I want it to pretend that it does - class HttpsProxyAgent extends https.Agent { - constructor(opts: HttpsProxyAgent.HttpsProxyAgentOptions | string); - } - - export = HttpsProxyAgent; -} diff --git a/index.js b/index.js deleted file mode 100644 index aa3021e2..00000000 --- a/index.js +++ /dev/null @@ -1,239 +0,0 @@ -/** - * Module dependencies. - */ - -var net = require('net'); -var tls = require('tls'); -var url = require('url'); -var assert = require('assert'); -var Agent = require('agent-base'); -var inherits = require('util').inherits; -var debug = require('debug')('https-proxy-agent'); - -/** - * Module exports. - */ - -module.exports = HttpsProxyAgent; - -/** - * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to the - * specified "HTTP(s) proxy server" in order to proxy HTTPS requests. - * - * @api public - */ - -function HttpsProxyAgent(opts) { - if (!(this instanceof HttpsProxyAgent)) return new HttpsProxyAgent(opts); - if ('string' == typeof opts) opts = url.parse(opts); - if (!opts) - throw new Error( - 'an HTTP(S) proxy server `host` and `port` must be specified!' - ); - debug('creating new HttpsProxyAgent instance: %o', opts); - Agent.call(this, opts); - - var proxy = Object.assign({}, opts); - - // if `true`, then connect to the proxy server over TLS. defaults to `false`. - this.secureProxy = proxy.protocol - ? /^https:?$/i.test(proxy.protocol) - : false; - - // prefer `hostname` over `host`, and set the `port` if needed - proxy.host = proxy.hostname || proxy.host; - proxy.port = +proxy.port || (this.secureProxy ? 443 : 80); - - // ALPN is supported by Node.js >= v5. - // attempt to negotiate http/1.1 for proxy servers that support http/2 - if (this.secureProxy && !('ALPNProtocols' in proxy)) { - proxy.ALPNProtocols = ['http 1.1']; - } - - if (proxy.host && proxy.path) { - // if both a `host` and `path` are specified then it's most likely the - // result of a `url.parse()` call... we need to remove the `path` portion so - // that `net.connect()` doesn't attempt to open that as a unix socket file. - delete proxy.path; - delete proxy.pathname; - } - - this.proxy = proxy; -} -inherits(HttpsProxyAgent, Agent); - -/** - * Called when the node-core HTTP client library is creating a new HTTP request. - * - * @api public - */ - -HttpsProxyAgent.prototype.callback = function connect(req, opts, fn) { - var proxy = this.proxy; - - // create a socket connection to the proxy server - var socket; - if (this.secureProxy) { - socket = tls.connect(proxy); - } else { - socket = net.connect(proxy); - } - - // we need to buffer any HTTP traffic that happens with the proxy before we get - // the CONNECT response, so that if the response is anything other than an "200" - // response code, then we can re-play the "data" events on the socket once the - // HTTP parser is hooked up... - var buffers = []; - var buffersLength = 0; - - function read() { - var b = socket.read(); - if (b) ondata(b); - else socket.once('readable', read); - } - - function cleanup() { - socket.removeListener('end', onend); - socket.removeListener('error', onerror); - socket.removeListener('close', onclose); - socket.removeListener('readable', read); - } - - function onclose(err) { - debug('onclose had error %o', err); - } - - function onend() { - debug('onend'); - } - - function onerror(err) { - cleanup(); - fn(err); - } - - function ondata(b) { - buffers.push(b); - buffersLength += b.length; - var buffered = Buffer.concat(buffers, buffersLength); - var str = buffered.toString('ascii'); - - if (!~str.indexOf('\r\n\r\n')) { - // keep buffering - debug('have not received end of HTTP headers yet...'); - read(); - return; - } - - var firstLine = str.substring(0, str.indexOf('\r\n')); - var statusCode = +firstLine.split(' ')[1]; - debug('got proxy server response: %o', firstLine); - - if (200 == statusCode) { - // 200 Connected status code! - var sock = socket; - - // nullify the buffered data since we won't be needing it - buffers = buffered = null; - - if (opts.secureEndpoint) { - // since the proxy is connecting to an SSL server, we have - // to upgrade this socket connection to an SSL connection - debug( - 'upgrading proxy-connected socket to TLS connection: %o', - opts.host - ); - opts.socket = socket; - opts.servername = opts.servername || opts.host; - opts.host = null; - opts.hostname = null; - opts.port = null; - sock = tls.connect(opts); - } - - cleanup(); - req.once('socket', resume); - fn(null, sock); - } else { - // 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 - cleanup(); - - // the original socket is closed, and a new closed 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(); - socket = new net.Socket(); - socket.readable = true; - - // save a reference to the concat'd Buffer for the `onsocket` callback - buffers = buffered; - - // need to wait for the "socket" event to re-play the "data" events - req.once('socket', onsocket); - - fn(null, socket); - } - } - - function onsocket(socket) { - debug('replaying proxy buffer for failed request'); - assert(socket.listenerCount('data') > 0); - - // replay the "buffers" Buffer onto the `socket`, since at this point - // the HTTP module machinery has been hooked up for the user - socket.push(buffers); - - // nullify the cached Buffer instance - buffers = null; - } - - socket.on('error', onerror); - socket.on('close', onclose); - socket.on('end', onend); - - read(); - - var hostname = opts.host + ':' + opts.port; - var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n'; - - var headers = Object.assign({}, proxy.headers); - if (proxy.auth) { - headers['Proxy-Authorization'] = - 'Basic ' + Buffer.from(proxy.auth).toString('base64'); - } - - // the Host header should only include the port - // number when it is a non-standard port - var host = opts.host; - if (!isDefaultPort(opts.port, opts.secureEndpoint)) { - host += ':' + opts.port; - } - headers['Host'] = host; - - headers['Connection'] = 'close'; - Object.keys(headers).forEach(function(name) { - msg += name + ': ' + headers[name] + '\r\n'; - }); - - socket.write(msg + '\r\n'); -}; - -/** - * Resumes a socket. - * - * @param {(net.Socket|tls.Socket)} socket The socket to resume - * @api public - */ - -function resume(socket) { - socket.resume(); -} - -function isDefaultPort(port, secure) { - return Boolean((!secure && port === 80) || (secure && port === 443)); -} diff --git a/package.json b/package.json index 084a8708..58fcbe27 100644 --- a/package.json +++ b/package.json @@ -2,10 +2,17 @@ "name": "https-proxy-agent", "version": "4.0.0", "description": "An HTTP(s) proxy `http.Agent` implementation for HTTPS", - "main": "./index.js", - "types": "./index.d.ts", + "main": "dist/index", + "types": "dist/index", + "files": [ + "dist" + ], "scripts": { - "test": "mocha --reporter spec" + "prebuild": "rimraf dist", + "build": "tsc", + "test": "mocha --reporter spec", + "test-lint": "eslint src --ext .js,.ts", + "prepublishOnly": "npm run build" }, "repository": { "type": "git", @@ -23,14 +30,27 @@ "url": "https://github.com/TooTallNate/node-https-proxy-agent/issues" }, "dependencies": { - "agent-base": "5", + "agent-base": "6", "debug": "4" }, "devDependencies": { - "mocha": "6", - "proxy": "1" + "@types/debug": "4", + "@types/node": "^12.12.11", + "@typescript-eslint/eslint-plugin": "1.6.0", + "@typescript-eslint/parser": "1.1.0", + "eslint": "5.16.0", + "eslint-config-airbnb": "17.1.0", + "eslint-config-prettier": "4.1.0", + "eslint-import-resolver-typescript": "1.1.1", + "eslint-plugin-import": "2.16.0", + "eslint-plugin-jsx-a11y": "6.2.1", + "eslint-plugin-react": "7.12.4", + "mocha": "^6.2.2", + "proxy": "1", + "rimraf": "^3.0.0", + "typescript": "^3.5.3" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 6" } } diff --git a/src/agent.ts b/src/agent.ts new file mode 100644 index 00000000..7098c8a8 --- /dev/null +++ b/src/agent.ts @@ -0,0 +1,215 @@ +import net from 'net'; +import tls from 'tls'; +import url from 'url'; +import assert from 'assert'; +import createDebug from 'debug'; +import { OutgoingHttpHeaders } from 'http'; +import { Agent, ClientRequest, RequestOptions } from 'agent-base'; +import { HttpsProxyAgentOptions } from '.'; +import parseProxyResponse from './parse-proxy-response'; + +const debug = createDebug('https-proxy-agent: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. + * + * @api public + */ +export default class HttpsProxyAgent extends Agent { + private secureProxy: boolean; + private proxy: HttpsProxyAgentOptions; + + constructor(_opts: string | HttpsProxyAgentOptions) { + let opts: HttpsProxyAgentOptions; + if (typeof _opts === 'string') { + opts = url.parse(_opts); + } else { + opts = _opts; + } + if (!opts) { + throw new Error( + 'an HTTP(S) proxy server `host` and `port` must be specified!' + ); + } + debug('creating new HttpsProxyAgent instance: %o', opts); + super(opts); + + const proxy: HttpsProxyAgentOptions = { ...opts }; + + // If `true`, then connect to the proxy server over TLS. + // Defaults to `false`. + this.secureProxy = opts.secureProxy || isHTTPS(proxy.protocol); + + // Prefer `hostname` over `host`, and set the `port` if needed. + proxy.host = proxy.hostname || proxy.host; + if (typeof proxy.port === 'string') { + proxy.port = parseInt(proxy.port, 10); + } + if (!proxy.port && proxy.host) { + proxy.port = this.secureProxy ? 443 : 80; + } + + // ALPN is supported by Node.js >= v5. + // attempt to negotiate http/1.1 for proxy servers that support http/2 + if (this.secureProxy && !('ALPNProtocols' in proxy)) { + proxy.ALPNProtocols = ['http 1.1']; + } + + if (proxy.host && proxy.path) { + // If both a `host` and `path` are specified then it's most likely + // the result of a `url.parse()` call... we need to remove the + // `path` portion so that `net.connect()` doesn't attempt to open + // that as a Unix socket file. + delete proxy.path; + delete proxy.pathname; + } + + this.proxy = proxy; + } + + /** + * Called when the node-core HTTP client library is creating a + * new HTTP request. + * + * @api protected + */ + async callback( + req: ClientRequest, + opts: RequestOptions + ): Promise { + const { proxy, secureProxy } = this; + + // Create a socket connection to the proxy server. + let socket: net.Socket; + if (secureProxy) { + debug('Creating `tls.Socket`: %o', proxy); + socket = tls.connect(proxy as tls.ConnectionOptions); + } else { + debug('Creating `net.Socket`: %o', proxy); + socket = net.connect(proxy as net.NetConnectOpts); + } + + const headers: OutgoingHttpHeaders = { ...proxy.headers }; + const hostname = `${opts.host}:${opts.port}`; + let payload = `CONNECT ${hostname} HTTP/1.1\r\n`; + + // Inject the `Proxy-Authorization` header if necessary. + if (proxy.auth) { + headers['Proxy-Authorization'] = `Basic ${Buffer.from( + proxy.auth + ).toString('base64')}`; + } + + // The `Host` header should only include the port + // number when it is not the default port. + let { host, port, secureEndpoint } = opts; + if (!isDefaultPort(port, secureEndpoint)) { + host += `:${port}`; + } + headers.Host = host; + + headers.Connection = 'close'; + for (const name of Object.keys(headers)) { + payload += `${name}: ${headers[name]}\r\n`; + } + + const proxyResponsePromise = parseProxyResponse(socket); + + socket.write(`${payload}\r\n`); + + const { + statusCode, + buffered + } = await proxyResponsePromise; + + if (statusCode === 200) { + req.once('socket', resume); + + if (opts.secureEndpoint) { + const servername = opts.servername || opts.host; + if (!servername) { + throw new Error('Could not determine "servername"'); + } + // The proxy is connecting to a TLS server, so upgrade + // this socket connection to a TLS connection. + debug('Upgrading socket connection to TLS'); + return tls.connect({ + ...omit(opts, 'host', 'hostname', 'path', 'port'), + socket, + 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.Socket(); + fakeSocket.readable = true; + + // Need to wait for the "socket" event to re-play the "data" events. + req.once('socket', (s: net.Socket) => { + debug('replaying proxy buffer for failed request'); + assert(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; + } +} + +function resume(socket: net.Socket | tls.TLSSocket): void { + socket.resume(); +} + +function isDefaultPort(port: number, secure: boolean): boolean { + return Boolean((!secure && port === 80) || (secure && port === 443)); +} + +function isHTTPS(protocol?: string | null): boolean { + return typeof protocol === 'string' ? /^https:?$/i.test(protocol) : false; +} + +function omit( + obj: T, + ...keys: K +): { + [K2 in Exclude]: T[K2]; +} { + const ret = {} as { + [K in keyof typeof obj]: (typeof obj)[K]; + }; + let key: keyof typeof obj; + for (key in obj) { + if (!keys.includes(key)) { + ret[key] = obj[key]; + } + } + return ret; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..9ef0d5d5 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,39 @@ +import net from 'net'; +import tls from 'tls'; +import { Url } from 'url'; +import { AgentOptions } from 'agent-base'; +import { OutgoingHttpHeaders } from 'http'; +import _HttpsProxyAgent from './agent'; + +function createHttpsProxyAgent( + opts: string | createHttpsProxyAgent.HttpsProxyAgentOptions +): _HttpsProxyAgent { + return new _HttpsProxyAgent(opts); +} + +namespace createHttpsProxyAgent { + interface BaseHttpsProxyAgentOptions { + headers?: OutgoingHttpHeaders; + secureProxy?: boolean; + host?: string | null; + path?: string | null; + port?: string | number | null; + } + + export interface HttpsProxyAgentOptions + extends AgentOptions, + BaseHttpsProxyAgentOptions, + Partial< + Omit< + Url & net.NetConnectOpts & tls.ConnectionOptions, + keyof BaseHttpsProxyAgentOptions + > + > {} + + export type HttpsProxyAgent = _HttpsProxyAgent; + export const HttpsProxyAgent = _HttpsProxyAgent; + + createHttpsProxyAgent.prototype = _HttpsProxyAgent.prototype; +} + +export = createHttpsProxyAgent; diff --git a/src/parse-proxy-response.ts b/src/parse-proxy-response.ts new file mode 100644 index 00000000..6791775c --- /dev/null +++ b/src/parse-proxy-response.ts @@ -0,0 +1,82 @@ +import createDebug from 'debug'; +import { Readable } from 'stream'; + +const debug = createDebug('https-proxy-agent:parse-proxy-response'); + +export interface ProxyResponse { + statusCode: number; + buffered: Buffer; +} + +export default function parseProxyResponse( + socket: Readable +): Promise { + return new Promise((resolve, reject) => { + // we need to buffer any HTTP traffic that happens with the proxy before we get + // the CONNECT response, so that if the response is anything other than an "200" + // response code, then we can re-play the "data" events on the socket once the + // HTTP parser is hooked up... + let buffersLength = 0; + const buffers: Buffer[] = []; + + function read() { + const b = socket.read(); + if (b) ondata(b); + else socket.once('readable', read); + } + + function cleanup() { + socket.removeListener('end', onend); + socket.removeListener('error', onerror); + socket.removeListener('close', onclose); + socket.removeListener('readable', read); + } + + function onclose(err?: Error) { + debug('onclose had error %o', err); + } + + function onend() { + debug('onend'); + } + + function onerror(err: Error) { + cleanup(); + debug('onerror %o', err); + reject(err); + } + + function ondata(b: Buffer) { + buffers.push(b); + buffersLength += b.length; + + const buffered = Buffer.concat(buffers, buffersLength); + const endOfHeaders = buffered.indexOf('\r\n\r\n'); + + if (endOfHeaders === -1) { + // keep buffering + debug('have not received end of HTTP headers yet...'); + read(); + return; + } + + const firstLine = buffered.toString( + 'ascii', + 0, + buffered.indexOf('\r\n') + ); + const statusCode = +firstLine.split(' ')[1]; + debug('got proxy server response: %o', firstLine); + resolve({ + statusCode, + buffered + }); + } + + socket.on('error', onerror); + socket.on('close', onclose); + socket.on('end', onend); + + read(); + }); +} diff --git a/test/test.js b/test/test.js index c309c812..36fd26f2 100644 --- a/test/test.js +++ b/test/test.js @@ -2,26 +2,26 @@ * Module dependencies. */ -var fs = require('fs'); -var url = require('url'); -var http = require('http'); -var https = require('https'); -var assert = require('assert'); -var Proxy = require('proxy'); -var HttpsProxyAgent = require('../'); +let fs = require('fs'); +let url = require('url'); +let http = require('http'); +let https = require('https'); +let assert = require('assert'); +let Proxy = require('proxy'); +let HttpsProxyAgent = require('../'); describe('HttpsProxyAgent', function() { - var server; - var serverPort; + let server; + let serverPort; - var sslServer; - var sslServerPort; + let sslServer; + let sslServerPort; - var proxy; - var proxyPort; + let proxy; + let proxyPort; - var sslProxy; - var sslProxyPort; + let sslProxy; + let sslProxyPort; before(function(done) { // setup target HTTP server @@ -43,9 +43,9 @@ describe('HttpsProxyAgent', function() { before(function(done) { // setup target HTTPS server - var options = { - key: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.key'), - cert: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.pem') + let options = { + key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`), + cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`) }; sslServer = https.createServer(options); sslServer.listen(function() { @@ -56,9 +56,9 @@ describe('HttpsProxyAgent', function() { before(function(done) { // setup SSL HTTP proxy server - var options = { - key: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.key'), - cert: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.pem') + let options = { + key: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.key`), + cert: fs.readFileSync(`${__dirname}/ssl-cert-snakeoil.pem`) }; sslProxy = Proxy(https.createServer(options)); sslProxy.listen(function() { @@ -103,37 +103,37 @@ describe('HttpsProxyAgent', function() { }); }); it('should accept a "string" proxy argument', function() { - var agent = new HttpsProxyAgent('http://localhost:' + proxyPort); + let agent = new HttpsProxyAgent(`http://localhost:${proxyPort}`); assert.equal('localhost', agent.proxy.host); assert.equal(proxyPort, agent.proxy.port); }); it('should accept a `url.parse()` result object argument', function() { - var opts = url.parse('http://localhost:' + proxyPort); - var agent = new HttpsProxyAgent(opts); + let opts = url.parse(`http://localhost:${proxyPort}`); + let agent = new HttpsProxyAgent(opts); assert.equal('localhost', agent.proxy.host); assert.equal(proxyPort, agent.proxy.port); }); describe('secureProxy', function() { it('should default to `false`', function() { - var agent = new HttpsProxyAgent({ port: proxyPort }); + let agent = new HttpsProxyAgent({ port: proxyPort }); assert.equal(false, agent.secureProxy); }); it('should be `false` when "http:" protocol is used', function() { - var agent = new HttpsProxyAgent({ + let agent = new HttpsProxyAgent({ port: proxyPort, protocol: 'http:' }); assert.equal(false, agent.secureProxy); }); it('should be `true` when "https:" protocol is used', function() { - var agent = new HttpsProxyAgent({ + let agent = new HttpsProxyAgent({ port: proxyPort, protocol: 'https:' }); assert.equal(true, agent.secureProxy); }); it('should be `true` when "https" protocol is used', function() { - var agent = new HttpsProxyAgent({ + let agent = new HttpsProxyAgent({ port: proxyPort, protocol: 'https' }); @@ -152,24 +152,24 @@ describe('HttpsProxyAgent', function() { res.end(JSON.stringify(req.headers)); }); - var proxy = + let proxy = process.env.HTTP_PROXY || process.env.http_proxy || - 'http://localhost:' + proxyPort; - var agent = new HttpsProxyAgent(proxy); + `http://localhost:${proxyPort}`; + let agent = new HttpsProxyAgent(proxy); - var opts = url.parse('http://localhost:' + serverPort); + let opts = url.parse(`http://localhost:${serverPort}`); opts.agent = agent; - var req = http.get(opts, function(res) { - var data = ''; + let req = http.get(opts, function(res) { + let data = ''; res.setEncoding('utf8'); res.on('data', function(b) { data += b; }); res.on('end', function() { data = JSON.parse(data); - assert.equal('localhost:' + serverPort, data.host); + assert.equal(`localhost:${serverPort}`, data.host); done(); }); }); @@ -180,26 +180,26 @@ describe('HttpsProxyAgent', function() { res.end(JSON.stringify(req.headers)); }); - var proxy = + let proxy = process.env.HTTPS_PROXY || process.env.https_proxy || - 'https://localhost:' + sslProxyPort; + `https://localhost:${sslProxyPort}`; proxy = url.parse(proxy); proxy.rejectUnauthorized = false; - var agent = new HttpsProxyAgent(proxy); + let agent = new HttpsProxyAgent(proxy); - var opts = url.parse('http://localhost:' + serverPort); + let opts = url.parse(`http://localhost:${serverPort}`); opts.agent = agent; http.get(opts, function(res) { - var data = ''; + let data = ''; res.setEncoding('utf8'); res.on('data', function(b) { data += b; }); res.on('end', function() { data = JSON.parse(data); - assert.equal('localhost:' + serverPort, data.host); + assert.equal(`localhost:${serverPort}`, data.host); done(); }); }); @@ -211,19 +211,19 @@ describe('HttpsProxyAgent', function() { fn(null, false); }; - var proxyUri = + let proxyUri = process.env.HTTP_PROXY || process.env.http_proxy || - 'http://localhost:' + proxyPort; - var agent = new HttpsProxyAgent(proxyUri); + `http://localhost:${proxyPort}`; + let agent = new HttpsProxyAgent(proxyUri); - var opts = {}; + let opts = {}; // `host` and `port` don't really matter since the proxy will reject anyways opts.host = 'localhost'; opts.port = 80; opts.agent = agent; - var req = http.get(opts, function(res) { + let req = http.get(opts, function(res) { assert.equal(407, res.statusCode); assert('proxy-authenticate' in res.headers); done(); @@ -237,7 +237,7 @@ describe('HttpsProxyAgent', function() { const proxyUri = process.env.HTTP_PROXY || process.env.http_proxy || - 'http://localhost:' + proxyPort; + `http://localhost:${proxyPort}`; const req = http.get( { @@ -251,15 +251,37 @@ describe('HttpsProxyAgent', function() { req.on('abort', done); }); + it('should emit an "end" event on the `http.IncomingMessage` if the proxy responds with non-200 status code', function(done) { + proxy.authenticate = function(req, fn) { + fn(null, false); + }; + + const proxyUri = + process.env.HTTP_PROXY || + process.env.http_proxy || + `http://localhost:${proxyPort}`; + + const req = http.get( + { + agent: new HttpsProxyAgent(proxyUri) + }, + function(res) { + assert.equal(407, res.statusCode); + + res.resume(); + res.on('end', done); + } + ); + }); it('should emit an "error" event on the `http.ClientRequest` if the proxy does not exist', function(done) { // port 4 is a reserved, but "unassigned" port - var proxyUri = 'http://localhost:4'; - var agent = new HttpsProxyAgent(proxyUri); + let proxyUri = 'http://localhost:4'; + let agent = new HttpsProxyAgent(proxyUri); - var opts = url.parse('http://nodejs.org'); + let opts = url.parse('http://nodejs.org'); opts.agent = agent; - var req = http.get(opts); + let req = http.get(opts); req.once('error', function(err) { assert.equal('ECONNREFUSED', err.code); req.abort(); @@ -275,14 +297,14 @@ describe('HttpsProxyAgent', function() { done(); }); - var uri = 'http://localhost:' + serverPort; - var proxyOpts = url.parse(uri); + let uri = `http://localhost:${serverPort}`; + let proxyOpts = url.parse(uri); proxyOpts.headers = { Foo: 'bar' }; - var agent = new HttpsProxyAgent(proxyOpts); + let agent = new HttpsProxyAgent(proxyOpts); - var opts = {}; + let opts = {}; // `host` and `port` don't really matter since the proxy will reject anyways opts.host = 'localhost'; opts.port = 80; @@ -298,25 +320,25 @@ describe('HttpsProxyAgent', function() { res.end(JSON.stringify(req.headers)); }); - var proxy = + let proxy = process.env.HTTP_PROXY || process.env.http_proxy || - 'http://localhost:' + proxyPort; - var agent = new HttpsProxyAgent(proxy); + `http://localhost:${proxyPort}`; + let agent = new HttpsProxyAgent(proxy); - var opts = url.parse('https://localhost:' + sslServerPort); + let opts = url.parse(`https://localhost:${sslServerPort}`); opts.rejectUnauthorized = false; opts.agent = agent; https.get(opts, function(res) { - var data = ''; + let data = ''; res.setEncoding('utf8'); res.on('data', function(b) { data += b; }); res.on('end', function() { data = JSON.parse(data); - assert.equal('localhost:' + sslServerPort, data.host); + assert.equal(`localhost:${sslServerPort}`, data.host); done(); }); }); @@ -327,27 +349,27 @@ describe('HttpsProxyAgent', function() { res.end(JSON.stringify(req.headers)); }); - var proxy = + let proxy = process.env.HTTPS_PROXY || process.env.https_proxy || - 'https://localhost:' + sslProxyPort; + `https://localhost:${sslProxyPort}`; proxy = url.parse(proxy); proxy.rejectUnauthorized = false; - var agent = new HttpsProxyAgent(proxy); + let agent = new HttpsProxyAgent(proxy); - var opts = url.parse('https://localhost:' + sslServerPort); + let opts = url.parse(`https://localhost:${sslServerPort}`); opts.agent = agent; opts.rejectUnauthorized = false; https.get(opts, function(res) { - var data = ''; + let data = ''; res.setEncoding('utf8'); res.on('data', function(b) { data += b; }); res.on('end', function() { data = JSON.parse(data); - assert.equal('localhost:' + sslServerPort, data.host); + assert.equal(`localhost:${sslServerPort}`, data.host); done(); }); }); @@ -358,21 +380,21 @@ describe('HttpsProxyAgent', function() { res.end(JSON.stringify(req.headers)); }); - var proxy = + let proxy = process.env.HTTPS_PROXY || process.env.https_proxy || - 'https://localhost:' + sslProxyPort; + `https://localhost:${sslProxyPort}`; proxy = url.parse(proxy); proxy.rejectUnauthorized = false; - var agent = new HttpsProxyAgent(proxy); + let agent = new HttpsProxyAgent(proxy); agent.defaultPort = sslServerPort; - var opts = url.parse('https://localhost:' + sslServerPort); + let opts = url.parse(`https://localhost:${sslServerPort}`); opts.agent = agent; opts.rejectUnauthorized = false; https.get(opts, function(res) { - var data = ''; + let data = ''; res.setEncoding('utf8'); res.on('data', function(b) { data += b; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..63692a77 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "module": "CommonJS", + "target": "es2015", + "esModuleInterop": true, + "lib": ["esnext"], + "outDir": "dist", + "sourceMap": true, + "declaration": true, + "typeRoots": [ + "./@types", + "./node_modules/@types" + ] + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +}