Skip to content

Instantly share code, notes, and snippets.

@ayanamist
Last active February 17, 2018 01:22
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ayanamist/9461784 to your computer and use it in GitHub Desktop.
Save ayanamist/9461784 to your computer and use it in GitHub Desktop.
HTTPS Proxy (Polipo + stunnel)
#!/usr/bin/env node
'use strict';
var fs = require('fs');
var http = require('http');
var https = require('https');
var net = require('net');
var url = require('url');
var util = require('util');
http.globalAgent.maxSockets = 128;
var prototype = http.IncomingMessage.prototype;
var _addHeaderLine = prototype._addHeaderLine;
//Patch ServerRequest to save unmodified copy of headers
prototype._addHeaderLine = function (field, value) {
var list = this.complete
?
(this.allTrailers || (this.allTrailers = []))
:
(this.allHeaders || (this.allHeaders = []));
list.push(field + ': ' + value);
_addHeaderLine.apply(this, arguments);
};
var log = function () {
util.log(util.format.apply(null, arguments));
};
var proxyServer = https.createServer({
'requestCert': true,
'rejectUnauthorized': true,
'ca': fs.readFileSync('/etc/stunnel/ca.crt'),
'cert': fs.readFileSync('/etc/stunnel/vps-server.pem'),
'key': fs.readFileSync('/etc/stunnel/vps-server.pem'),
});
proxyServer.on('request', function (request, response) {
var reqParsed = url.parse(request.url);
var reqHeaders = request.headers;
Object.keys(reqHeaders).forEach(function (key) {
if (key.toLowerCase().slice(0, 6) === 'proxy-') {
delete reqHeaders[key];
}
});
var srvRequest = http.request({
hostname: reqParsed.host,
host: reqParsed.host,
port: reqParsed.port || 80,
path: reqParsed.path,
method: request.method,
headers: reqHeaders,
});
srvRequest.setNoDelay(true);
request.pipe(srvRequest);
srvRequest.on('error', function (err) {
srvRequest.abort();
response.writeHead(504);
response.end(err.toString());
});
response.on('error', function () {
response.abort();
srvRequest.abort();
});
srvRequest.on('response', function (srvResponse) {
srvResponse.on('error', function () {
srvResponse.abort();
response.abort();
});
// nodejs will make all names of http headers lower case, which breaks many old clients.
// Should not directly manipulate socket, because cltResponse.socket will sometimes become null.
var rawHeader = Object.create(null);
srvResponse.allHeaders.map(function (header) {
// We don't need to validate split result, since nodejs has guaranteed by valid srvResponse.headers.
var key = header.split(":")[0].trim();
rawHeader[key] = srvResponse.headers[key.toLowerCase()];
});
response.writeHead(srvResponse.statusCode, rawHeader);
srvResponse.pipe(response);
});
});
proxyServer.on('connect', function (request, socket) {
var hostParsed = request.url.split(':');
var srvConn = net.createConnection(Number(hostParsed[1]), hostParsed[0]);
srvConn.on('connect', function () {
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
srvConn.pipe(socket);
});
socket.pipe(srvConn);
srvConn.on('error', function () {
srvConn.end();
socket.end();
});
socket.on('error', function () {
socket.end();
srvConn.end();
});
});
if (!module.parent) {
proxyServer.listen(443);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment