node-js-proxy
Version:
A multithreaded man-in-the-middle proxy to extract JavaScript from websites and modify it on the fly
143 lines (128 loc) • 5.23 kB
JavaScript
var start = function (options) {
var i,
httpProxy = require('http-proxy'),
url = require('url'),
path = require("path"),
instrument = require(path.join(__dirname, './instrument.js')),
instrument_html = require(path.join(__dirname, './instrument_html.js')),
zlib = require('zlib'),
port = parseInt(options.port, 10),
cluster = require('cluster'),
threads = parseInt(options.threads, 10),
iconv = require("iconv-lite");
if (!threads) {
threads = 1;
}
if (cluster.isMaster) {
for (i = 0; i < threads; i++) {
cluster.fork();
}
} else {
httpProxy.createServer(function (req, res, next) {
var _writeHead = res.writeHead,
_process = false,
_isJS = false,
_isHTML = false,
_code,
_headers,
_contentType,
_content,
_write = res.write,
_end = res.end,
processedContent = '';
var userid = "";
delete req.headers['accept-encoding'];
res.writeHead = function (code) {
_code = code.toString();
_headers = this._headers;
if (this.getHeader('content-type')) {
_contentType = this.getHeader('content-type');
if (_contentType.match(/javascript/)) {
_isJS = true;
} else if (_contentType.match(/text\/html/)) {
_isHTML = true;
}
}
if (_isHTML || _isJS) {
_process = true;
_content = new Buffer(0);
}
if (!_process) {
_writeHead.apply(res, arguments);
}
};
res.write = function (data) {
if (_process) {
_content = Buffer.concat([_content, data]);
} else {
_write.call(res, data);
}
};
res.end = function () {
var _instrumentJS = function (str, options) {
options.source = "ExternalJS";
options.userid = userid;
return instrument.instrument(str, options).toString();
};
var _instrumentHTML = function (str, options) {
options.userid = userid;
return instrument_html.instrument_html(str, options);
};
var processContent = function (content) {
res.removeHeader("Content-Encoding");
if (_isJS) {
processedContent = _instrumentJS(content, options);
} else if (_isHTML) {
processedContent = _instrumentHTML(content, options);
} else {
processedContent = content;
}
//finish up
_headers['content-length'] = Buffer.byteLength(processedContent, 'utf8');
_writeHead.call(res, _code, _headers);
_write.call(res, processedContent);
_end.apply(res, []);
};
var bufferToString = function (buffer) {
// console.log(buffer[0].toString(16), buffer[1].toString(16));
if(buffer.length > 2 && buffer[0] == 0xFF && buffer[1] == 0xFE) {
return iconv.decode(buffer, "utf16-le");
} else {
// assume it is utf-8
return iconv.decode(buffer, "utf8");
}
}
if (_process) {
if (this.getHeader("Content-Encoding") === "gzip") {
zlib.unzip(_content, function(err, buffer) {
processContent(bufferToString(buffer));
});
} else if (this.getHeader("Content-Encoding") === "deflate") {
zlib.inflateRaw(_content, function(err, buffer) {
processContent(bufferToString(buffer));
});
} else {
processContent(bufferToString(_content));
}
} else {
_end.apply(res, []);
}
};
next();
}, function (req, res) {
var proxy = new httpProxy.RoutingProxy(),
buffer = httpProxy.buffer(req),
urlObj = url.parse(req.url);
req.headers.host = urlObj.host;
req.url = urlObj.path;
proxy.proxyRequest(req, res, {
host: urlObj.hostname,
port: urlObj.port || 80,
buffer: buffer
});
}).listen(port, function () {
console.log("Waiting for requests...");
});
}
};
module.exports.start = start;