1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var net = require('net');
|
7 | var tls = require('tls');
|
8 | var url = require('url');
|
9 | var extend = require('extend');
|
10 | var Agent = require('agent-base');
|
11 | var inherits = require('util').inherits;
|
12 | var debug = require('debug')('https-proxy-agent');
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | module.exports = HttpsProxyAgent;
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | function HttpsProxyAgent (opts) {
|
28 | if (!(this instanceof HttpsProxyAgent)) return new HttpsProxyAgent(opts);
|
29 | if ('string' == typeof opts) opts = url.parse(opts);
|
30 | if (!opts) throw new Error('an HTTP(S) proxy server `host` and `port` must be specified!');
|
31 | debug('creating new HttpsProxyAgent instance: %j', opts);
|
32 | Agent.call(this, connect);
|
33 |
|
34 | var proxy = extend({}, opts);
|
35 |
|
36 |
|
37 | this.secureProxy = proxy.protocol ? /^https:?$/i.test(proxy.protocol) : false;
|
38 |
|
39 |
|
40 | this.secureEndpoint = opts.secureEndpoint !== false;
|
41 |
|
42 |
|
43 | proxy.host = proxy.hostname || proxy.host;
|
44 | proxy.port = +proxy.port || (this.secureProxy ? 443 : 80);
|
45 |
|
46 | if (proxy.host && proxy.path) {
|
47 |
|
48 |
|
49 |
|
50 | delete proxy.path;
|
51 | delete proxy.pathname;
|
52 | }
|
53 |
|
54 | this.proxy = proxy;
|
55 | }
|
56 | inherits(HttpsProxyAgent, Agent);
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | var defaults = { port: 80 };
|
63 | var secureDefaults = { port: 443 };
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | function connect (req, _opts, fn) {
|
72 |
|
73 | var proxy = this.proxy;
|
74 | var secureProxy = this.secureProxy;
|
75 | var secureEndpoint = this.secureEndpoint;
|
76 |
|
77 |
|
78 | var socket;
|
79 | if (secureProxy) {
|
80 | socket = tls.connect(proxy);
|
81 | } else {
|
82 | socket = net.connect(proxy);
|
83 | }
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | var proxyOpts = extend({}, proxy);
|
89 | delete proxyOpts.host;
|
90 | delete proxyOpts.hostname;
|
91 | delete proxyOpts.port;
|
92 | var opts = extend({}, proxyOpts, secureEndpoint ? secureDefaults : defaults, _opts);
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 | var buffers = [];
|
99 | var buffersLength = 0;
|
100 |
|
101 | function read () {
|
102 | var b = socket.read();
|
103 | if (b) ondata(b);
|
104 | else socket.once('readable', read);
|
105 | }
|
106 |
|
107 | function cleanup () {
|
108 | socket.removeListener('data', ondata);
|
109 | socket.removeListener('end', onend);
|
110 | socket.removeListener('error', onerror);
|
111 | socket.removeListener('close', onclose);
|
112 | socket.removeListener('readable', read);
|
113 | }
|
114 |
|
115 | function onclose (err) {
|
116 | debug('onclose had error', err);
|
117 | }
|
118 |
|
119 | function onend () {
|
120 | debug('onend');
|
121 | }
|
122 |
|
123 | function onerror (err) {
|
124 | cleanup();
|
125 | fn(err);
|
126 | }
|
127 |
|
128 | function ondata (b) {
|
129 | buffers.push(b);
|
130 | buffersLength += b.length;
|
131 | var buffered = Buffer.concat(buffers, buffersLength);
|
132 | var str = buffered.toString('ascii');
|
133 |
|
134 | if (!~str.indexOf('\r\n\r\n')) {
|
135 |
|
136 | debug('have not received end of HTTP headers yet...');
|
137 | if (socket.read) {
|
138 | read();
|
139 | } else {
|
140 | socket.once('data', ondata);
|
141 | }
|
142 | return;
|
143 | }
|
144 |
|
145 | var firstLine = str.substring(0, str.indexOf('\r\n'));
|
146 | var statusCode = +firstLine.split(' ')[1];
|
147 | debug('got proxy server response: "%s"', firstLine);
|
148 |
|
149 |
|
150 |
|
151 | if (200 == statusCode) {
|
152 |
|
153 | var sock = socket;
|
154 |
|
155 |
|
156 | buffers = buffered = null;
|
157 |
|
158 | if (secureEndpoint) {
|
159 |
|
160 |
|
161 | debug('upgrading proxy-connected socket to TLS connection: "%s"', opts.host);
|
162 | opts.socket = socket;
|
163 | opts.servername = opts.host;
|
164 | opts.host = null;
|
165 | opts.hostname = null;
|
166 | opts.port = null;
|
167 | sock = tls.connect(opts);
|
168 | }
|
169 |
|
170 | cleanup();
|
171 | fn(null, sock);
|
172 | } else {
|
173 |
|
174 |
|
175 |
|
176 | cleanup();
|
177 |
|
178 |
|
179 | buffers = buffered;
|
180 |
|
181 |
|
182 | req.once('socket', onsocket);
|
183 | fn(null, socket);
|
184 | }
|
185 | }
|
186 |
|
187 | function onsocket (socket) {
|
188 |
|
189 |
|
190 | if ('function' == typeof socket.ondata) {
|
191 |
|
192 | socket.ondata(buffers, 0, buffers.length);
|
193 | } else if (socket.listeners('data').length > 0) {
|
194 |
|
195 | socket.emit('data', buffers);
|
196 | } else {
|
197 |
|
198 | throw new Error('should not happen...');
|
199 | }
|
200 |
|
201 |
|
202 | buffers = null;
|
203 | }
|
204 |
|
205 | socket.on('error', onerror);
|
206 | socket.on('close', onclose);
|
207 | socket.on('end', onend);
|
208 |
|
209 | if (socket.read) {
|
210 | read();
|
211 | } else {
|
212 | socket.once('data', ondata);
|
213 | }
|
214 |
|
215 | var hostname = opts.host + ':' + opts.port;
|
216 | var msg = 'CONNECT ' + hostname + ' HTTP/1.1\r\n';
|
217 | var auth = proxy.auth;
|
218 | if (auth) {
|
219 | msg += 'Proxy-Authorization: Basic ' + new Buffer(auth).toString('base64') + '\r\n';
|
220 | }
|
221 | msg += 'Host: ' + hostname + '\r\n' +
|
222 | 'Connection: close\r\n' +
|
223 | '\r\n';
|
224 | socket.write(msg);
|
225 | };
|