mirror of
https://github.com/alibaba/anyproxy.git
synced 2025-07-29 00:59:10 +00:00
update to 4.0
This commit is contained in:
@@ -1,52 +0,0 @@
|
||||
function asyncTaskMgr(){
|
||||
var self = this;
|
||||
|
||||
self.callbackList = {
|
||||
sampleName:{
|
||||
status:0, /* 0,stopped,will not callback / 1,loading / 2,loaded */
|
||||
result:null,
|
||||
callbackList:[]
|
||||
}
|
||||
}
|
||||
|
||||
self.addTask = function(name,cb,action){
|
||||
if(self.callbackList[name]){
|
||||
var task = self.callbackList[name];
|
||||
|
||||
if(task.status == 2){ //done
|
||||
cb && cb.apply(null,task.result);
|
||||
|
||||
}else if(task.status == 1){ //pending
|
||||
task.callbackList.push(cb);
|
||||
|
||||
}else if(task.status == 0){ //stopped
|
||||
return; //do nothing
|
||||
}
|
||||
}else{
|
||||
var task;
|
||||
task = self.callbackList[name] = {
|
||||
status : 1,
|
||||
result : null,
|
||||
callbackList : [cb]
|
||||
};
|
||||
|
||||
action && action.call(null,function(){ //action应该带一个回调
|
||||
if(arguments && arguments[0] === -1){ //返回第一个参数为-1,为停止任务
|
||||
task.status = 0;
|
||||
task.callbackList = [];
|
||||
}else{
|
||||
task.result = arguments;
|
||||
task.status = 2;
|
||||
var tmpCb;
|
||||
while(tmpCb = task.callbackList.shift()){
|
||||
tmpCb && tmpCb.apply(null,task.result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
module.exports = asyncTaskMgr;
|
@@ -1,91 +0,0 @@
|
||||
var forge = require('node-forge');
|
||||
|
||||
var defaultAttrs = [
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ name: 'organizationName', value: 'AnyProxy' },
|
||||
{ shortName: 'ST', value: 'SH' },
|
||||
{ shortName: 'OU', value: 'AnyProxy SSL Proxy'}
|
||||
];
|
||||
|
||||
function getKeysAndCert(serialNumber){
|
||||
var keys = forge.pki.rsa.generateKeyPair(1024);
|
||||
var cert = forge.pki.createCertificate();
|
||||
cert.publicKey = keys.publicKey;
|
||||
cert.serialNumber = serialNumber || (Math.floor(Math.random() * 100000) + '');
|
||||
cert.validity.notBefore = new Date();
|
||||
cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 10); // 10 years
|
||||
cert.validity.notAfter = new Date();
|
||||
cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 10); // 10 years
|
||||
return {
|
||||
keys: keys,
|
||||
cert: cert
|
||||
};
|
||||
}
|
||||
|
||||
function generateRootCA(){
|
||||
var keysAndCert = getKeysAndCert();
|
||||
keys = keysAndCert.keys;
|
||||
cert = keysAndCert.cert;
|
||||
|
||||
var attrs = defaultAttrs.concat([
|
||||
{
|
||||
name: 'commonName',
|
||||
value: 'AnyProxy'
|
||||
}
|
||||
]);
|
||||
cert.setSubject(attrs);
|
||||
cert.setIssuer(attrs);
|
||||
cert.setExtensions([
|
||||
{ name: 'basicConstraints', cA: true }
|
||||
// { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true },
|
||||
// { name: 'extKeyUsage', serverAuth: true, clientAuth: true, codeSigning: true, emailProtection: true, timeStamping: true },
|
||||
// { name: 'nsCertType', client: true, server: true, email: true, objsign: true, sslCA: true, emailCA: true, objCA: true },
|
||||
// { name: 'subjectAltName', altNames: [ { type: 6, /* URI */ value: 'http://example.org/webid#me' }, { type: 7, /* IP */ ip: '127.0.0.1' } ] },
|
||||
// { name: 'subjectKeyIdentifier' }
|
||||
]);
|
||||
|
||||
cert.sign(keys.privateKey, forge.md.sha256.create());
|
||||
|
||||
return {
|
||||
privateKey: forge.pki.privateKeyToPem(keys.privateKey),
|
||||
publicKey: forge.pki.publicKeyToPem(keys.publicKey),
|
||||
certificate: forge.pki.certificateToPem(cert)
|
||||
};
|
||||
|
||||
return pem;
|
||||
}
|
||||
|
||||
function generateCertsForHostname(domain, rootCAConfig){
|
||||
|
||||
//generate a serialNumber for domain
|
||||
var md = forge.md.md5.create();
|
||||
md.update(domain);
|
||||
|
||||
var keysAndCert = getKeysAndCert(md.digest().toHex());
|
||||
keys = keysAndCert.keys;
|
||||
cert = keysAndCert.cert;
|
||||
|
||||
var caCert = forge.pki.certificateFromPem(rootCAConfig.cert);
|
||||
var caKey = forge.pki.privateKeyFromPem(rootCAConfig.key);
|
||||
|
||||
// issuer from CA
|
||||
cert.setIssuer(caCert.subject.attributes);
|
||||
|
||||
var attrs = defaultAttrs.concat([
|
||||
{
|
||||
name: 'commonName',
|
||||
value: domain
|
||||
}
|
||||
]);
|
||||
cert.setSubject(attrs);
|
||||
cert.sign(caKey, forge.md.sha256.create());
|
||||
|
||||
return {
|
||||
privateKey: forge.pki.privateKeyToPem(keys.privateKey),
|
||||
publicKey: forge.pki.publicKeyToPem(keys.publicKey),
|
||||
certificate: forge.pki.certificateToPem(cert)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.generateRootCA = generateRootCA;
|
||||
module.exports.generateCertsForHostname = generateCertsForHostname;
|
118
lib/certMgr.js
118
lib/certMgr.js
@@ -1,81 +1,57 @@
|
||||
var logUtil = require('./log');
|
||||
var util = require('./util');
|
||||
var color = require('colorful');
|
||||
var EasyCert = require('node-easy-cert');
|
||||
var exec = require('child_process').exec;
|
||||
var path = require('path');
|
||||
var readline = require('readline');
|
||||
'use strict'
|
||||
|
||||
var isWin = /^win/.test(process.platform);
|
||||
var options = {
|
||||
rootDirPath: util.getUserHome() + '/.anyproxy_certs',
|
||||
defaultCertAttrs: [
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ name: 'organizationName', value: 'AnyProxy' },
|
||||
{ shortName: 'ST', value: 'SH' },
|
||||
{ shortName: 'OU', value: 'AnyProxy SSL Proxy' }
|
||||
]
|
||||
const util = require('./util');
|
||||
const EasyCert = require('node-easy-cert');
|
||||
const co = require('co');
|
||||
|
||||
const options = {
|
||||
rootDirPath: util.getAnyProxyPath('certificates'),
|
||||
defaultCertAttrs: [
|
||||
{ name: 'countryName', value: 'CN' },
|
||||
{ name: 'organizationName', value: 'AnyProxy' },
|
||||
{ shortName: 'ST', value: 'SH' },
|
||||
{ shortName: 'OU', value: 'AnyProxy SSL Proxy' }
|
||||
]
|
||||
};
|
||||
|
||||
var easyCert = new EasyCert(options);
|
||||
var crtMgr = util.merge({}, easyCert);
|
||||
const easyCert = new EasyCert(options);
|
||||
const crtMgr = util.merge({}, easyCert);
|
||||
|
||||
// catch specified error, such as ROOT_CA_NOT_EXISTS
|
||||
crtMgr.getCertificate = function (host, cb) {
|
||||
easyCert.getCertificate(host, (error, keyContent, crtContent) => {
|
||||
if (error === 'ROOT_CA_NOT_EXISTS') {
|
||||
util.showRootInstallTip();
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
// rename function
|
||||
crtMgr.ifRootCAFileExists = easyCert.isRootCAFileExists;
|
||||
|
||||
cb(error, keyContent, crtContent);
|
||||
});
|
||||
};
|
||||
|
||||
// set default common name of the cert
|
||||
crtMgr.generateRootCA = function (cb) {
|
||||
doGenerate(false);
|
||||
doGenerate(false);
|
||||
|
||||
function doGenerate(overwrite) {
|
||||
const rootOptions = {
|
||||
commonName: 'AnyProxy',
|
||||
overwrite: !!overwrite
|
||||
};
|
||||
|
||||
easyCert.generateRootCA(rootOptions, (error, keyPath, crtPath) => {
|
||||
if (!error) {
|
||||
const certDir = path.dirname(keyPath);
|
||||
logUtil.printLog(color.cyan('The cert is generated at "' + certDir + '"'));
|
||||
if(isWin){
|
||||
exec("start .",{ cwd : certDir });
|
||||
}else{
|
||||
exec("open .",{ cwd : certDir });
|
||||
}
|
||||
}
|
||||
|
||||
if (error === 'ROOT_CA_EXISTED') {
|
||||
var rl = readline.createInterface({
|
||||
input : process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.question("do you really want to generate a new one ?)(yes/NO)", function(answer) {
|
||||
if(/yes/i.test(answer)){
|
||||
doGenerate(true);
|
||||
}else{
|
||||
console.log("will not generate a new one");
|
||||
|
||||
}
|
||||
rl.close();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
cb(error, keyPath, crtPath);
|
||||
});
|
||||
}
|
||||
// set default common name of the cert
|
||||
function doGenerate(overwrite) {
|
||||
const rootOptions = {
|
||||
commonName: 'AnyProxy',
|
||||
overwrite: !!overwrite
|
||||
};
|
||||
|
||||
easyCert.generateRootCA(rootOptions, (error, keyPath, crtPath) => {
|
||||
cb(error, keyPath, crtPath);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = crtMgr;
|
||||
crtMgr.getCAStatus = function *() {
|
||||
return co(function *() {
|
||||
const result = {
|
||||
exist: false,
|
||||
};
|
||||
const ifExist = easyCert.isRootCAFileExists();
|
||||
if (!ifExist) {
|
||||
return result;
|
||||
} else {
|
||||
result.exist = true;
|
||||
if (!/^win/.test(process.platform)) {
|
||||
result.trusted = yield easyCert.ifRootCATrusted;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = crtMgr;
|
||||
|
16
lib/configUtil.js
Normal file
16
lib/configUtil.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* a util to set and get all configuable constant
|
||||
*
|
||||
*/
|
||||
const path = require('path');
|
||||
|
||||
const USER_HOME = process.env.HOME || process.env.USERPROFILE;
|
||||
const DEFAULT_ANYPROXY_HOME = path.join(USER_HOME, '/.anyproxy/');
|
||||
|
||||
/**
|
||||
* return AnyProxy's home path
|
||||
*/
|
||||
module.exports.getAnyProxyHome = function () {
|
||||
const ENV_ANYPROXY_HOME = process.env.ANYPROXY_HOME || '';
|
||||
return ENV_ANYPROXY_HOME || DEFAULT_ANYPROXY_HOME;
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
var portrange = 40000;
|
||||
|
||||
function getPort(cb) {
|
||||
var port = portrange;
|
||||
++portrange;
|
||||
|
||||
var server = require("net").createServer();
|
||||
server.listen(port, function (err) {
|
||||
server.once('close', function () {
|
||||
cb(port);
|
||||
});
|
||||
server.close();
|
||||
});
|
||||
server.on('error', function (err) {
|
||||
getPort(cb);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = getPort;
|
@@ -1,77 +1,194 @@
|
||||
//manage https servers
|
||||
var getPort = require('./getPort'),
|
||||
async = require("async"),
|
||||
http = require('http'),
|
||||
https = require('https'),
|
||||
Buffer = require('buffer').Buffer,
|
||||
fs = require('fs'),
|
||||
net = require('net'),
|
||||
tls = require('tls'),
|
||||
crypto = require('crypto'),
|
||||
color = require('colorful'),
|
||||
certMgr = require("./certMgr"),
|
||||
logUtil = require("./log"),
|
||||
asyncTask = require("async-task-mgr");
|
||||
'use strict'
|
||||
|
||||
var createSecureContext = tls.createSecureContext || crypto.createSecureContext;
|
||||
//manage https servers
|
||||
const async = require('async'),
|
||||
https = require('https'),
|
||||
tls = require('tls'),
|
||||
crypto = require('crypto'),
|
||||
color = require('colorful'),
|
||||
certMgr = require('./certMgr'),
|
||||
logUtil = require('./log'),
|
||||
util = require('./util'),
|
||||
co = require('co'),
|
||||
constants = require('constants'),
|
||||
asyncTask = require('async-task-mgr');
|
||||
|
||||
const createSecureContext = tls.createSecureContext || crypto.createSecureContext;
|
||||
|
||||
//using sni to avoid multiple ports
|
||||
function SNIPrepareCert(serverName,SNICallback){
|
||||
var keyContent, crtContent,ctx;
|
||||
function SNIPrepareCert(serverName, SNICallback) {
|
||||
let keyContent,
|
||||
crtContent,
|
||||
ctx;
|
||||
|
||||
async.series([
|
||||
function(callback){
|
||||
certMgr.getCertificate(serverName,function(err,key,crt){
|
||||
if(err){
|
||||
callback(err);
|
||||
}else{
|
||||
keyContent = key;
|
||||
crtContent = crt;
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
function(callback){
|
||||
try{
|
||||
ctx = createSecureContext({
|
||||
key :keyContent,
|
||||
cert :crtContent
|
||||
});
|
||||
callback();
|
||||
}catch(e){
|
||||
callback(e);
|
||||
}
|
||||
async.series([
|
||||
(callback) => {
|
||||
certMgr.getCertificate(serverName, (err, key, crt) => {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
keyContent = key;
|
||||
crtContent = crt;
|
||||
callback();
|
||||
}
|
||||
],function(err,result){
|
||||
if(!err){
|
||||
var tipText = "proxy server for __NAME established".replace("__NAME",serverName);
|
||||
logUtil.printLog(color.yellow(color.bold("[internal https]")) + color.yellow(tipText));
|
||||
SNICallback(null,ctx);
|
||||
}else{
|
||||
logUtil.printLog("err occurred when prepare certs for SNI - " + err, logUtil.T_ERR);
|
||||
logUtil.printLog("err occurred when prepare certs for SNI - " + err.stack, logUtil.T_ERR);
|
||||
logUtil.printLog("you may upgrade your Node.js to >= v0.12", logUtil.T_ERR);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
(callback) => {
|
||||
try {
|
||||
ctx = createSecureContext({
|
||||
key: keyContent,
|
||||
cert: crtContent
|
||||
});
|
||||
callback();
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
}
|
||||
], (err) => {
|
||||
if (!err) {
|
||||
const tipText = 'proxy server for __NAME established'.replace('__NAME', serverName);
|
||||
logUtil.printLog(color.yellow(color.bold('[internal https]')) + color.yellow(tipText));
|
||||
SNICallback(null, ctx);
|
||||
} else {
|
||||
logUtil.printLog('err occurred when prepare certs for SNI - ' + err, logUtil.T_ERR);
|
||||
logUtil.printLog('err occurred when prepare certs for SNI - ' + err.stack, logUtil.T_ERR);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//config.port - port to start https server
|
||||
//config.handler - request handler
|
||||
module.exports =function(config){
|
||||
var self = this;
|
||||
|
||||
if(!config || !config.port ){
|
||||
throw(new Error("please assign a port"));
|
||||
}
|
||||
|
||||
certMgr.getCertificate("anyproxy_internal_https_server",function(err,keyContent,crtContent){
|
||||
https.createServer({
|
||||
SNICallback : SNIPrepareCert ,
|
||||
key : keyContent,
|
||||
cert : crtContent
|
||||
},config.handler).listen(config.port);
|
||||
/**
|
||||
* Create an https server
|
||||
*
|
||||
* @param {object} config
|
||||
* @param {number} config.port
|
||||
* @param {function} config.handler
|
||||
*/
|
||||
function createHttpsServer(config) {
|
||||
if (!config || !config.port || !config.handler) {
|
||||
throw (new Error('please assign a port'));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
certMgr.getCertificate('anyproxy_internal_https_server', (err, keyContent, crtContent) => {
|
||||
const server = https.createServer({
|
||||
secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
|
||||
SNICallback: SNIPrepareCert,
|
||||
key: keyContent,
|
||||
cert: crtContent
|
||||
}, config.handler).listen(config.port);
|
||||
|
||||
resolve(server);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* create an https server that serving on IP address
|
||||
* @param @required {object} config
|
||||
* @param @required {string} config.ip the IP address of the server
|
||||
* @param @required {number} config.port the port to listen on
|
||||
* @param @required {function} handler the handler of each connect
|
||||
*/
|
||||
function createIPHttpsServer(config) {
|
||||
if (!config || !config.port || !config.handler) {
|
||||
throw (new Error('please assign a port'));
|
||||
}
|
||||
|
||||
if (!config.ip) {
|
||||
throw (new Error('please assign an IP to create the https server'));
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
certMgr.getCertificate(config.ip, (err, keyContent, crtContent) => {
|
||||
const server = https.createServer({
|
||||
secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
|
||||
key: keyContent,
|
||||
cert: crtContent
|
||||
}, config.handler).listen(config.port);
|
||||
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @class httpsServerMgr
|
||||
* @param {object} config
|
||||
* @param {function} config.handler handler to deal https request
|
||||
*
|
||||
*/
|
||||
class httpsServerMgr {
|
||||
constructor(config) {
|
||||
if (!config || !config.handler) {
|
||||
throw new Error('handler is required');
|
||||
}
|
||||
this.instanceDefaultHost = '127.0.0.1';
|
||||
this.httpsAsyncTask = new asyncTask();
|
||||
this.handler = config.handler;
|
||||
}
|
||||
|
||||
getSharedHttpsServer(hostname) {
|
||||
// ip address will have a unique name
|
||||
const finalHost = util.isIpDomain(hostname) ? hostname : this.instanceDefaultHost;
|
||||
|
||||
const self = this;
|
||||
function prepareServer(callback) {
|
||||
let instancePort;
|
||||
co(util.getFreePort)
|
||||
.then(co.wrap(function *(port) {
|
||||
instancePort = port;
|
||||
let httpsServer = null;
|
||||
|
||||
// if ip address passed in, will create an IP http server
|
||||
if (util.isIpDomain(hostname)) {
|
||||
httpsServer = yield createIPHttpsServer({
|
||||
ip: hostname,
|
||||
port,
|
||||
handler: self.handler
|
||||
});
|
||||
} else {
|
||||
httpsServer = yield createHttpsServer({
|
||||
port,
|
||||
handler: self.handler
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
httpsServer.on('upgrade', (req, socket, head) => {
|
||||
const reqHost = req.headers.host || 'unknown host';
|
||||
logUtil.printLog(`wss:// is not supported when intercepting https. This request will be closed by AnyProxy. You may either exclude this domain in your rule file, or stop all https intercepting. (${reqHost})`, logUtil.T_ERR);
|
||||
socket.end();
|
||||
});
|
||||
const result = {
|
||||
host: finalHost,
|
||||
port: instancePort,
|
||||
};
|
||||
callback(null, result);
|
||||
return result;
|
||||
}))
|
||||
.catch(e => {
|
||||
callback(e);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// each ip address will gain a unit task name,
|
||||
// while the domain address will share a common task name
|
||||
self.httpsAsyncTask.addTask(`createHttpsServer-${finalHost}`, prepareServer, (error, serverInfo) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(serverInfo);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = httpsServerMgr;
|
||||
|
87
lib/log.js
87
lib/log.js
@@ -1,17 +1,82 @@
|
||||
var ifPrint = true;
|
||||
'use strict'
|
||||
|
||||
function setPrintStatus(status){
|
||||
ifPrint = !!status;
|
||||
const color = require('colorful');
|
||||
const util = require('./util');
|
||||
|
||||
let ifPrint = true;
|
||||
let logLevel = 0;
|
||||
const LogLevelMap = {
|
||||
tip: 0,
|
||||
system_error: 1,
|
||||
rule_error: 2,
|
||||
warn: 3,
|
||||
debug: 4,
|
||||
};
|
||||
|
||||
function setPrintStatus(status) {
|
||||
ifPrint = !!status;
|
||||
}
|
||||
|
||||
function printLog(content,type){
|
||||
if(!ifPrint) return;
|
||||
|
||||
var tip = content;
|
||||
console.log(tip);
|
||||
function setLogLevel(level) {
|
||||
logLevel = parseInt(level, 10);
|
||||
}
|
||||
|
||||
module.exports.printLog = printLog;
|
||||
function printLog(content, type) {
|
||||
if (!ifPrint) {
|
||||
return;
|
||||
}
|
||||
const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss');
|
||||
switch (type) {
|
||||
case LogLevelMap.tip: {
|
||||
if (logLevel > 0) {
|
||||
return;
|
||||
}
|
||||
console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevelMap.system_error: {
|
||||
if (logLevel > 1) {
|
||||
return;
|
||||
}
|
||||
console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content));
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevelMap.rule_error: {
|
||||
if (logLevel > 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content));
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevelMap.warn: {
|
||||
if (logLevel > 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content));
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevelMap.debug: {
|
||||
return;
|
||||
}
|
||||
|
||||
default : {
|
||||
console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.printLog = printLog;
|
||||
module.exports.setPrintStatus = setPrintStatus;
|
||||
module.exports.T_TIP = 0;
|
||||
module.exports.T_ERR = 1;
|
||||
module.exports.setLogLevel = setLogLevel;
|
||||
module.exports.T_TIP = LogLevelMap.tip;
|
||||
module.exports.T_ERR = LogLevelMap.system_error;
|
||||
module.exports.T_RULE_ERROR = LogLevelMap.rule_error;
|
||||
module.exports.T_WARN = LogLevelMap.warn;
|
||||
module.exports.T_DEBUG = LogLevelMap.debug;
|
||||
|
451
lib/recorder.js
451
lib/recorder.js
@@ -1,233 +1,244 @@
|
||||
'use strict'
|
||||
|
||||
//start recording and share a list when required
|
||||
var zlib = require('zlib'),
|
||||
Datastore = require('nedb'),
|
||||
util = require("util"),
|
||||
path = require("path"),
|
||||
fs = require("fs"),
|
||||
events = require('events'),
|
||||
iconv = require('iconv-lite'),
|
||||
proxyUtil = require("./util"),
|
||||
logUtil = require("./log");
|
||||
const Datastore = require('nedb'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
events = require('events'),
|
||||
iconv = require('iconv-lite'),
|
||||
proxyUtil = require('./util');
|
||||
|
||||
//option.filename
|
||||
function Recorder(option){
|
||||
var self = this,
|
||||
id = 1,
|
||||
cachePath = proxyUtil.generateCacheDir(),
|
||||
db;
|
||||
const BODY_FILE_PRFIX = 'res_body_';
|
||||
const CACHE_DIR_PREFIX = 'cache_r';
|
||||
function getCacheDir() {
|
||||
const rand = Math.floor(Math.random() * 1000000),
|
||||
cachePath = path.join(proxyUtil.getAnyProxyPath('cache'), './' + CACHE_DIR_PREFIX + rand);
|
||||
|
||||
option = option || {};
|
||||
if(option.filename && typeof option.filename == "string"){
|
||||
try{
|
||||
if(fs.existsSync(option.filename)){
|
||||
fs.writeFileSync(option.filename,""); //empty original file
|
||||
}
|
||||
|
||||
db = new Datastore({
|
||||
filename : option.filename,
|
||||
autoload :true
|
||||
});
|
||||
db.persistence.setAutocompactionInterval(5001);
|
||||
logUtil.printLog("db file : " + option.filename);
|
||||
|
||||
}catch(e){
|
||||
logUtil.printLog(e, logUtil.T_ERR);
|
||||
logUtil.printLog("Failed to load on-disk db file. Will use in-meomory db instead.", logUtil.T_ERR);
|
||||
db = new Datastore();
|
||||
}
|
||||
|
||||
}else{
|
||||
//in-memory db
|
||||
db = new Datastore();
|
||||
}
|
||||
|
||||
self.recordBodyMap = []; // id - body
|
||||
|
||||
self.emitUpdate = function(id,info){
|
||||
if(info){
|
||||
self.emit("update",info);
|
||||
}else{
|
||||
self.getSingleRecord(id,function(err,doc){
|
||||
if(!err && !!doc && !!doc[0]){
|
||||
self.emit("update",doc[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
self.updateRecord = function(id,info){
|
||||
if(id < 0 ) return;
|
||||
|
||||
var finalInfo = normalizeInfo(id,info);
|
||||
|
||||
db.update({_id:id},finalInfo);
|
||||
self.updateRecordBody(id,info);
|
||||
|
||||
self.emitUpdate(id,finalInfo);
|
||||
};
|
||||
|
||||
self.updateExtInfo = function(id,extInfo){
|
||||
db.update({_id:id},{ $set: { ext: extInfo } },{},function(err,nums){
|
||||
if(!err){
|
||||
self.emitUpdate(id);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
self.appendRecord = function(info){
|
||||
if(info.req.headers.anyproxy_web_req){ //request from web interface
|
||||
return -1;
|
||||
}
|
||||
|
||||
var thisId = id++,
|
||||
finalInfo = normalizeInfo(thisId,info);
|
||||
db.insert(finalInfo);
|
||||
self.updateRecordBody(id,info);
|
||||
|
||||
self.emitUpdate(id,finalInfo);
|
||||
return thisId;
|
||||
};
|
||||
|
||||
//update recordBody if exits
|
||||
|
||||
//TODO : trigger update callback
|
||||
var BODY_FILE_PRFIX = "res_body_";
|
||||
self.updateRecordBody =function(id,info){
|
||||
if(id == -1) return;
|
||||
|
||||
if(!id || !info.resBody) return;
|
||||
//add to body map
|
||||
//ignore image data
|
||||
var bodyFile = path.join(cachePath,BODY_FILE_PRFIX + id);
|
||||
fs.writeFile(bodyFile, info.resBody);
|
||||
};
|
||||
|
||||
self.getBody = function(id,cb){
|
||||
if(id < 0){
|
||||
cb && cb("");
|
||||
}
|
||||
|
||||
var bodyFile = path.join(cachePath,BODY_FILE_PRFIX + id);
|
||||
fs.access(bodyFile, fs.F_OK | fs.R_OK ,function(err){
|
||||
if(err){
|
||||
cb && cb(err);
|
||||
}else{
|
||||
fs.readFile(bodyFile,cb);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.getDecodedBody = function(id,cb){
|
||||
var result = {
|
||||
type : "unknown",
|
||||
mime : "",
|
||||
content : ""
|
||||
};
|
||||
global.recorder.getSingleRecord(id,function(err,doc){
|
||||
//check whether this record exists
|
||||
if(!doc || !doc[0]){
|
||||
cb(new Error("failed to find record for this id"));
|
||||
return;
|
||||
}
|
||||
|
||||
self.getBody(id,function(err,bodyContent){
|
||||
if(err){
|
||||
cb(err);
|
||||
}else if(!bodyContent){
|
||||
cb(null,result);
|
||||
}else{
|
||||
var record = doc[0],
|
||||
resHeader = record['resHeader'] || {};
|
||||
try{
|
||||
var headerStr = JSON.stringify(resHeader),
|
||||
charsetMatch = headerStr.match(/charset="?([a-zA-Z0-9\-]+)"?/),
|
||||
imageMatch = resHeader && resHeader["content-type"];
|
||||
|
||||
if(charsetMatch && charsetMatch.length){
|
||||
|
||||
var currentCharset = charsetMatch[1].toLowerCase();
|
||||
if(currentCharset != "utf-8" && iconv.encodingExists(currentCharset)){
|
||||
bodyContent = iconv.decode(bodyContent, currentCharset);
|
||||
}
|
||||
result.type = "text";
|
||||
result.content = bodyContent.toString();
|
||||
}else if(imageMatch && /image/i.test(imageMatch)){
|
||||
|
||||
result.type = "image";
|
||||
result.mime = imageMatch;
|
||||
result.content = bodyContent;
|
||||
}else{
|
||||
result.content = bodyContent.toString();
|
||||
}
|
||||
}catch(e){}
|
||||
|
||||
cb(null,result);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
self.getSingleRecord = function(id,cb){
|
||||
db.find({_id:parseInt(id)},cb);
|
||||
};
|
||||
|
||||
self.getSummaryList = function(cb){
|
||||
db.find({},cb);
|
||||
};
|
||||
|
||||
self.getRecords = function(idStart, limit, cb){
|
||||
limit = limit || 10;
|
||||
idStart = typeof idStart == "number" ? idStart : (id - limit);
|
||||
db.find({ _id: { $gte: parseInt(idStart) } }).limit(limit).exec(cb);
|
||||
};
|
||||
|
||||
self.db = db;
|
||||
fs.mkdirSync(cachePath);
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
util.inherits(Recorder, events.EventEmitter);
|
||||
function normalizeInfo(id, info) {
|
||||
const singleRecord = {};
|
||||
|
||||
function normalizeInfo(id,info){
|
||||
var singleRecord = {};
|
||||
//general
|
||||
singleRecord._id = id;
|
||||
singleRecord.id = id;
|
||||
singleRecord.url = info.url;
|
||||
singleRecord.host = info.host;
|
||||
singleRecord.path = info.path;
|
||||
singleRecord.method = info.method;
|
||||
|
||||
//general
|
||||
singleRecord._id = id;
|
||||
singleRecord.id = id;
|
||||
singleRecord.url = info.url;
|
||||
singleRecord.host = info.host;
|
||||
singleRecord.path = info.path;
|
||||
singleRecord.method = info.method;
|
||||
//req
|
||||
singleRecord.reqHeader = info.req.headers;
|
||||
singleRecord.startTime = info.startTime;
|
||||
singleRecord.reqBody = info.reqBody || '';
|
||||
singleRecord.protocol = info.protocol || '';
|
||||
|
||||
//req
|
||||
singleRecord.reqHeader = info.req.headers;
|
||||
singleRecord.startTime = info.startTime;
|
||||
singleRecord.reqBody = info.reqBody || "";
|
||||
singleRecord.protocol = info.protocol || "";
|
||||
|
||||
//res
|
||||
if(info.endTime){
|
||||
singleRecord.statusCode= info.statusCode;
|
||||
singleRecord.endTime = info.endTime;
|
||||
singleRecord.resHeader = info.resHeader;
|
||||
singleRecord.length = info.length;
|
||||
if(info.resHeader['content-type']){
|
||||
singleRecord.mime = info.resHeader['content-type'].split(";")[0];
|
||||
}else{
|
||||
singleRecord.mime = "";
|
||||
}
|
||||
|
||||
singleRecord.duration = info.endTime - info.startTime;
|
||||
}else{
|
||||
singleRecord.statusCode= "";
|
||||
singleRecord.endTime = "";
|
||||
singleRecord.resHeader = "";
|
||||
singleRecord.length = "";
|
||||
singleRecord.mime = "";
|
||||
singleRecord.duration = "";
|
||||
//res
|
||||
if (info.endTime) {
|
||||
singleRecord.statusCode = info.statusCode;
|
||||
singleRecord.endTime = info.endTime;
|
||||
singleRecord.resHeader = info.resHeader;
|
||||
singleRecord.length = info.length;
|
||||
const contentType = info.resHeader['content-type'] || info.resHeader['Content-Type'];
|
||||
if (contentType) {
|
||||
singleRecord.mime = contentType.split(';')[0];
|
||||
} else {
|
||||
singleRecord.mime = '';
|
||||
}
|
||||
|
||||
return singleRecord;
|
||||
singleRecord.duration = info.endTime - info.startTime;
|
||||
} else {
|
||||
singleRecord.statusCode = '';
|
||||
singleRecord.endTime = '';
|
||||
singleRecord.resHeader = '';
|
||||
singleRecord.length = '';
|
||||
singleRecord.mime = '';
|
||||
singleRecord.duration = '';
|
||||
}
|
||||
|
||||
return singleRecord;
|
||||
}
|
||||
|
||||
class Recorder extends events.EventEmitter {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
this.globalId = 1;
|
||||
this.cachePath = getCacheDir();
|
||||
this.db = new Datastore();
|
||||
this.db.persistence.setAutocompactionInterval(5001);
|
||||
|
||||
this.recordBodyMap = []; // id - body
|
||||
}
|
||||
|
||||
emitUpdate(id, info) {
|
||||
const self = this;
|
||||
if (info) {
|
||||
self.emit('update', info);
|
||||
} else {
|
||||
self.getSingleRecord(id, (err, doc) => {
|
||||
if (!err && !!doc && !!doc[0]) {
|
||||
self.emit('update', doc[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateRecord(id, info) {
|
||||
if (id < 0) return;
|
||||
const self = this;
|
||||
const db = self.db;
|
||||
|
||||
const finalInfo = normalizeInfo(id, info);
|
||||
|
||||
db.update({ _id: id }, finalInfo);
|
||||
self.updateRecordBody(id, info);
|
||||
|
||||
self.emitUpdate(id, finalInfo);
|
||||
}
|
||||
|
||||
updateExtInfo(id, extInfo) {
|
||||
const self = this;
|
||||
const db = self.db;
|
||||
|
||||
db.update({ _id: id }, { $set: { ext: extInfo } }, {}, (err, nums) => {
|
||||
if (!err) {
|
||||
self.emitUpdate(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
appendRecord(info) {
|
||||
if (info.req.headers.anyproxy_web_req) { //TODO request from web interface
|
||||
return -1;
|
||||
}
|
||||
const self = this;
|
||||
const db = self.db;
|
||||
|
||||
const thisId = self.globalId++;
|
||||
const finalInfo = normalizeInfo(thisId, info);
|
||||
db.insert(finalInfo);
|
||||
self.updateRecordBody(thisId, info);
|
||||
|
||||
self.emitUpdate(thisId, finalInfo);
|
||||
return thisId;
|
||||
}
|
||||
|
||||
updateRecordBody(id, info) {
|
||||
const self = this;
|
||||
const cachePath = self.cachePath;
|
||||
|
||||
if (id === -1) return;
|
||||
|
||||
if (!id || typeof info.resBody === 'undefined') return;
|
||||
//add to body map
|
||||
//ignore image data
|
||||
const bodyFile = path.join(cachePath, BODY_FILE_PRFIX + id);
|
||||
fs.writeFile(bodyFile, info.resBody, () => {});
|
||||
}
|
||||
|
||||
getBody(id, cb) {
|
||||
const self = this;
|
||||
const cachePath = self.cachePath;
|
||||
|
||||
if (id < 0) {
|
||||
cb && cb('');
|
||||
}
|
||||
|
||||
const bodyFile = path.join(cachePath, BODY_FILE_PRFIX + id);
|
||||
fs.access(bodyFile, fs.F_OK || fs.R_OK, (err) => {
|
||||
if (err) {
|
||||
cb && cb(err);
|
||||
} else {
|
||||
fs.readFile(bodyFile, cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getDecodedBody(id, cb) {
|
||||
const self = this;
|
||||
const result = {
|
||||
type: 'unknown',
|
||||
mime: '',
|
||||
content: ''
|
||||
};
|
||||
self.getSingleRecord(id, (err, doc) => {
|
||||
//check whether this record exists
|
||||
if (!doc || !doc[0]) {
|
||||
cb(new Error('failed to find record for this id'));
|
||||
return;
|
||||
}
|
||||
|
||||
self.getBody(id, (error, bodyContent) => {
|
||||
if (error) {
|
||||
cb(error);
|
||||
} else if (!bodyContent) {
|
||||
cb(null, result);
|
||||
} else {
|
||||
const record = doc[0],
|
||||
resHeader = record.resHeader || {};
|
||||
try {
|
||||
const headerStr = JSON.stringify(resHeader),
|
||||
charsetMatch = headerStr.match(/charset='?([a-zA-Z0-9-]+)'?/),
|
||||
contentType = resHeader && (resHeader['content-type'] || resHeader['Content-Type']);
|
||||
|
||||
if (charsetMatch && charsetMatch.length) {
|
||||
const currentCharset = charsetMatch[1].toLowerCase();
|
||||
if (currentCharset !== 'utf-8' && iconv.encodingExists(currentCharset)) {
|
||||
bodyContent = iconv.decode(bodyContent, currentCharset);
|
||||
}
|
||||
|
||||
result.mime = contentType;
|
||||
result.content = bodyContent.toString();
|
||||
result.type = contentType && /application\/json/i.test(contentType) ? 'json' : 'text';
|
||||
} else if (contentType && /image/i.test(contentType)) {
|
||||
result.type = 'image';
|
||||
result.mime = contentType;
|
||||
result.content = bodyContent;
|
||||
} else {
|
||||
result.type = contentType;
|
||||
result.mime = contentType;
|
||||
result.content = bodyContent.toString();
|
||||
}
|
||||
result.fileName = path.basename(record.path);
|
||||
result.statusCode = record.statusCode;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
cb(null, result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getSingleRecord(id, cb) {
|
||||
const self = this;
|
||||
const db = self.db;
|
||||
db.find({ _id: parseInt(id, 10) }, cb);
|
||||
}
|
||||
|
||||
getSummaryList(cb) {
|
||||
const self = this;
|
||||
const db = self.db;
|
||||
db.find({}, cb);
|
||||
}
|
||||
|
||||
getRecords(idStart, limit, cb) {
|
||||
const self = this;
|
||||
const db = self.db;
|
||||
limit = limit || 10;
|
||||
idStart = typeof idStart === 'number' ? idStart : (self.globalId - limit);
|
||||
db.find({ _id: { $gte: parseInt(idStart, 10) } })
|
||||
.sort({ _id: 1 })
|
||||
.limit(limit)
|
||||
.exec(cb);
|
||||
}
|
||||
|
||||
clear() {
|
||||
const self = this;
|
||||
proxyUtil.deleteFolderContentsRecursive(self.cachePath, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Recorder;
|
||||
|
84
lib/requestErrorHandler.js
Normal file
84
lib/requestErrorHandler.js
Normal file
@@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* handle all request error here,
|
||||
*
|
||||
*/
|
||||
const pug = require('pug');
|
||||
const path = require('path');
|
||||
|
||||
const error502PugFn = pug.compileFile(path.join(__dirname, '../resource/502.pug'));
|
||||
const certPugFn = pug.compileFile(path.join(__dirname, '../resource/cert_error.pug'));
|
||||
|
||||
/**
|
||||
* get error content for certification issues
|
||||
*/
|
||||
function getCertErrorContent(error, fullUrl) {
|
||||
let content;
|
||||
const title = 'The connection is not private. ';
|
||||
let explain = 'There are error with the certfication of the site.';
|
||||
switch (error.code) {
|
||||
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
|
||||
explain = 'The certfication of the site you are visiting is not issued by a known agency, '
|
||||
+ 'It usually happenes when the cert is a self-signed one.</br>'
|
||||
+ 'If you know and trust the site, you can run AnyProxy with option <strong>-ignore-unauthorized-ssl</strong> to continue.'
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
explain = ''
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
content = certPugFn({
|
||||
title: title,
|
||||
explain: explain,
|
||||
code: error.code
|
||||
});
|
||||
} catch (parseErro) {
|
||||
content = error.stack;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/*
|
||||
* get the default error content
|
||||
*/
|
||||
function getDefaultErrorCotent(error, fullUrl) {
|
||||
let content;
|
||||
|
||||
try {
|
||||
content = error502PugFn({
|
||||
error,
|
||||
url: fullUrl,
|
||||
errorStack: error.stack.split(/\n/)
|
||||
});
|
||||
} catch (parseErro) {
|
||||
content = error.stack;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/*
|
||||
* get mapped error content for each error
|
||||
*/
|
||||
module.exports.getErrorContent = function (error, fullUrl) {
|
||||
let content = '';
|
||||
error = error || {};
|
||||
switch (error.code) {
|
||||
case 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY': {
|
||||
content = getCertErrorContent(error, fullUrl);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
content = getDefaultErrorCotent(error, fullUrl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
74
lib/ruleLoader.js
Normal file
74
lib/ruleLoader.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const proxyUtil = require('./util');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const request = require('request');
|
||||
|
||||
const cachePath = proxyUtil.getAnyProxyPath('cache');
|
||||
|
||||
/**
|
||||
* download a file and cache
|
||||
*
|
||||
* @param {any} url
|
||||
* @returns {string} cachePath
|
||||
*/
|
||||
function cacheRemoteFile(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(url, (error, response, body) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
} else if (response.statusCode !== 200) {
|
||||
return reject(`failed to load with a status code ${response.statusCode}`);
|
||||
} else {
|
||||
const fileCreatedTime = proxyUtil.formatDate(new Date(), 'YYYY_MM_DD_hh_mm_ss');
|
||||
const random = Math.ceil(Math.random() * 500);
|
||||
const fileName = `remote_rule_${fileCreatedTime}_r${random}.js`;
|
||||
const filePath = path.join(cachePath, fileName);
|
||||
fs.writeFileSync(filePath, body);
|
||||
resolve(filePath);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* load a local npm module
|
||||
*
|
||||
* @param {any} filePath
|
||||
* @returns module
|
||||
*/
|
||||
function loadLocalPath(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ruleFilePath = path.resolve(process.cwd(), filePath);
|
||||
if (fs.existsSync(ruleFilePath)) {
|
||||
resolve(require(ruleFilePath));
|
||||
} else {
|
||||
resolve(require(filePath));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* load a module from url or local path
|
||||
*
|
||||
* @param {any} urlOrPath
|
||||
* @returns module
|
||||
*/
|
||||
function requireModule(urlOrPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (/^http/i.test(urlOrPath)) {
|
||||
resolve(cacheRemoteFile(urlOrPath));
|
||||
} else {
|
||||
resolve(urlOrPath);
|
||||
}
|
||||
}).then(localPath => loadLocalPath(localPath));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cacheRemoteFile,
|
||||
loadLocalPath,
|
||||
requireModule,
|
||||
};
|
@@ -1,190 +1,68 @@
|
||||
var utils = require("./util"),
|
||||
bodyParser = require("body-parser"),
|
||||
path = require("path"),
|
||||
fs = require("fs"),
|
||||
Promise = require("promise");
|
||||
|
||||
var isRootCAFileExists = require("./certMgr.js").isRootCAFileExists(),
|
||||
interceptFlag = false;
|
||||
|
||||
//e.g. [ { keyword: 'aaa', local: '/Users/Stella/061739.pdf' } ]
|
||||
var mapConfig = [],
|
||||
configFile = "mapConfig.json";
|
||||
function saveMapConfig(content,cb){
|
||||
new Promise(function(resolve,reject){
|
||||
var anyproxyHome = utils.getAnyProxyHome(),
|
||||
mapCfgPath = path.join(anyproxyHome,configFile);
|
||||
|
||||
if(typeof content == "object"){
|
||||
content = JSON.stringify(content);
|
||||
}
|
||||
resolve({
|
||||
path :mapCfgPath,
|
||||
content :content
|
||||
});
|
||||
})
|
||||
.then(function(config){
|
||||
return new Promise(function(resolve,reject){
|
||||
fs.writeFile(config.path, config.content, function(e){
|
||||
if(e){
|
||||
reject(e);
|
||||
}else{
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(function(e){
|
||||
cb && cb(e);
|
||||
})
|
||||
.done(function(){
|
||||
cb && cb();
|
||||
});
|
||||
}
|
||||
function getMapConfig(cb){
|
||||
var read = Promise.denodeify(fs.readFile);
|
||||
|
||||
new Promise(function(resolve,reject){
|
||||
var anyproxyHome = utils.getAnyProxyHome(),
|
||||
mapCfgPath = path.join(anyproxyHome,configFile);
|
||||
|
||||
resolve(mapCfgPath);
|
||||
})
|
||||
.then(read)
|
||||
.then(function(content){
|
||||
return JSON.parse(content);
|
||||
})
|
||||
.catch(function(e){
|
||||
cb && cb(e);
|
||||
})
|
||||
.done(function(obj){
|
||||
cb && cb(null,obj);
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(function(){
|
||||
//load saved config file
|
||||
getMapConfig(function(err,result){
|
||||
if(result){
|
||||
mapConfig = result;
|
||||
}
|
||||
});
|
||||
},1000);
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
token: Date.now(),
|
||||
summary:function(){
|
||||
var tip = "the default rule for AnyProxy.";
|
||||
if(!isRootCAFileExists){
|
||||
tip += "\nRoot CA does not exist, will not intercept any https requests.";
|
||||
}
|
||||
return tip;
|
||||
},
|
||||
|
||||
shouldUseLocalResponse : function(req,reqBody){
|
||||
//intercept all options request
|
||||
var simpleUrl = (req.headers.host || "") + (req.url || "");
|
||||
mapConfig.map(function(item){
|
||||
var key = item.keyword;
|
||||
if(simpleUrl.indexOf(key) >= 0){
|
||||
req.anyproxy_map_local = item.local;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
summary: 'the default rule for AnyProxy',
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {object} requestDetail
|
||||
* @param {string} requestDetail.protocol
|
||||
* @param {object} requestDetail.requestOptions
|
||||
* @param {object} requestDetail.requestData
|
||||
* @param {object} requestDetail.response
|
||||
* @param {number} requestDetail.response.statusCode
|
||||
* @param {object} requestDetail.response.header
|
||||
* @param {buffer} requestDetail.response.body
|
||||
* @returns
|
||||
*/
|
||||
*beforeSendRequest(requestDetail) {
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
return !!req.anyproxy_map_local;
|
||||
},
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {object} requestDetail
|
||||
* @param {object} responseDetail
|
||||
*/
|
||||
*beforeSendResponse(requestDetail, responseDetail) {
|
||||
return null;
|
||||
},
|
||||
|
||||
dealLocalResponse : function(req,reqBody,callback){
|
||||
if(req.anyproxy_map_local){
|
||||
fs.readFile(req.anyproxy_map_local,function(err,buffer){
|
||||
if(err){
|
||||
callback(200, {}, "[AnyProxy failed to load local file] " + err);
|
||||
}else{
|
||||
var header = {
|
||||
'Content-Type': utils.contentType(req.anyproxy_map_local)
|
||||
};
|
||||
callback(200, header, buffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
replaceRequestProtocol:function(req,protocol){
|
||||
},
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {any} requestDetail
|
||||
* @returns
|
||||
*/
|
||||
*beforeDealHttpsRequest(requestDetail) {
|
||||
return false;
|
||||
},
|
||||
|
||||
replaceRequestOption : function(req,option){
|
||||
},
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {any} requestDetail
|
||||
* @param {any} error
|
||||
* @returns
|
||||
*/
|
||||
*onError(requestDetail, error) {
|
||||
return null;
|
||||
},
|
||||
|
||||
replaceRequestData: function(req,data){
|
||||
},
|
||||
|
||||
replaceResponseStatusCode: function(req,res,statusCode){
|
||||
},
|
||||
|
||||
replaceResponseHeader: function(req,res,header){
|
||||
},
|
||||
|
||||
// Deprecated
|
||||
// replaceServerResData: function(req,res,serverResData){
|
||||
// return serverResData;
|
||||
// },
|
||||
|
||||
replaceServerResDataAsync: function(req,res,serverResData,callback){
|
||||
callback(serverResData);
|
||||
},
|
||||
|
||||
pauseBeforeSendingResponse: function(req,res){
|
||||
},
|
||||
|
||||
shouldInterceptHttpsReq:function(req){
|
||||
return interceptFlag;
|
||||
},
|
||||
|
||||
//[beta]
|
||||
//fetch entire traffic data
|
||||
fetchTrafficData: function(id,info){},
|
||||
|
||||
setInterceptFlag: function(flag){
|
||||
interceptFlag = flag && isRootCAFileExists;
|
||||
},
|
||||
|
||||
_plugIntoWebinterface: function(app,cb){
|
||||
|
||||
app.get("/filetree",function(req,res){
|
||||
try{
|
||||
var root = req.query.root || utils.getUserHome() || "/";
|
||||
utils.filewalker(root,function(err, info){
|
||||
res.json(info);
|
||||
});
|
||||
}catch(e){
|
||||
res.end(e);
|
||||
}
|
||||
});
|
||||
|
||||
app.use(bodyParser.json());
|
||||
app.get("/getMapConfig",function(req,res){
|
||||
res.json(mapConfig);
|
||||
});
|
||||
app.post("/setMapConfig",function(req,res){
|
||||
mapConfig = req.body;
|
||||
res.json(mapConfig);
|
||||
|
||||
saveMapConfig(mapConfig);
|
||||
});
|
||||
|
||||
cb();
|
||||
},
|
||||
|
||||
_getCustomMenu : function(){
|
||||
return [
|
||||
// {
|
||||
// name:"test",
|
||||
// icon:"uk-icon-lemon-o",
|
||||
// url :"http://anyproxy.io"
|
||||
// }
|
||||
];
|
||||
}
|
||||
};
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {any} requestDetail
|
||||
* @param {any} error
|
||||
* @returns
|
||||
*/
|
||||
*onConnectError(requestDetail, error) {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
@@ -1,21 +1,23 @@
|
||||
var child_process = require('child_process');
|
||||
'use strict'
|
||||
|
||||
var networkTypes = ['Ethernet', 'Thunderbolt Ethernet', 'Wi-Fi'];
|
||||
const child_process = require('child_process');
|
||||
|
||||
const networkTypes = ['Ethernet', 'Thunderbolt Ethernet', 'Wi-Fi'];
|
||||
|
||||
function execSync(cmd) {
|
||||
var stdout, status = 0;
|
||||
let stdout,
|
||||
status = 0;
|
||||
try {
|
||||
stdout = child_process.execSync(cmd);
|
||||
} catch (err) {
|
||||
stdout = err.stdout;
|
||||
status = err.status;
|
||||
}
|
||||
|
||||
try {
|
||||
stdout = child_process.execSync(cmd);
|
||||
} catch (err) {
|
||||
stdout = err.stdout;
|
||||
status = err.status;
|
||||
}
|
||||
|
||||
return {
|
||||
stdout: stdout.toString(),
|
||||
status: status
|
||||
};
|
||||
return {
|
||||
stdout: stdout.toString(),
|
||||
status
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,79 +51,71 @@ function execSync(cmd) {
|
||||
* ------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
var macProxyManager = {};
|
||||
const macProxyManager = {};
|
||||
|
||||
macProxyManager.getNetworkType = function() {
|
||||
macProxyManager.getNetworkType = () => {
|
||||
for (let i = 0; i < networkTypes.length; i++) {
|
||||
const type = networkTypes[i],
|
||||
result = execSync('networksetup -getwebproxy ' + type);
|
||||
|
||||
for (var i = 0; i < networkTypes.length; i++) {
|
||||
if (result.status === 0) {
|
||||
macProxyManager.networkType = type;
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
var
|
||||
type = networkTypes[i],
|
||||
result = execSync('networksetup -getwebproxy ' + type);
|
||||
|
||||
if (result.status === 0) {
|
||||
macProxyManager.networkType = type;
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unknown network type');
|
||||
};
|
||||
|
||||
macProxyManager.enableGlobalProxy = function(ip, port, proxyType) {
|
||||
|
||||
if (!ip || !port) {
|
||||
console.log('failed to set global proxy server.\n ip and port are required.');
|
||||
return;
|
||||
};
|
||||
|
||||
proxyType = proxyType || 'http';
|
||||
|
||||
var networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
|
||||
|
||||
return /^http$/i.test(proxyType) ?
|
||||
|
||||
// set http proxy
|
||||
execSync(
|
||||
'networksetup -setwebproxy ${networkType} ${ip} ${port}'
|
||||
.replace("${networkType}", networkType)
|
||||
.replace("${ip}", ip)
|
||||
.replace("${port}", port)) :
|
||||
|
||||
// set https proxy
|
||||
execSync('networksetup -setsecurewebproxy ${networkType} ${ip} ${port}'
|
||||
.replace("${networkType}", networkType)
|
||||
.replace("${ip}", ip)
|
||||
.replace("${port}", port));
|
||||
|
||||
};
|
||||
|
||||
macProxyManager.disableGlobalProxy = function(proxyType) {
|
||||
proxyType = proxyType || 'http';
|
||||
|
||||
var networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
|
||||
|
||||
return /^http$/i.test(proxyType) ?
|
||||
|
||||
// set http proxy
|
||||
execSync(
|
||||
'networksetup -setwebproxystate ${networkType} off'
|
||||
.replace("${networkType}", networkType)) :
|
||||
|
||||
// set https proxy
|
||||
execSync(
|
||||
'networksetup -setsecurewebproxystate ${networkType} off'
|
||||
.replace("${networkType}", networkType));
|
||||
};
|
||||
|
||||
macProxyManager.getProxyState = function() {
|
||||
var networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
|
||||
var result = execSync('networksetup -getwebproxy ${networkType}'.replace('${networkType}', networkType));
|
||||
|
||||
return result;
|
||||
throw new Error('Unknown network type');
|
||||
};
|
||||
|
||||
|
||||
macProxyManager.enableGlobalProxy = (ip, port, proxyType) => {
|
||||
if (!ip || !port) {
|
||||
console.log('failed to set global proxy server.\n ip and port are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
proxyType = proxyType || 'http';
|
||||
|
||||
const networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
|
||||
|
||||
return /^http$/i.test(proxyType) ?
|
||||
|
||||
// set http proxy
|
||||
execSync(
|
||||
'networksetup -setwebproxy ${networkType} ${ip} ${port} && networksetup -setproxybypassdomains ${networkType} 127.0.0.1 localhost'
|
||||
.replace(/\${networkType}/g, networkType)
|
||||
.replace('${ip}', ip)
|
||||
.replace('${port}', port)) :
|
||||
|
||||
// set https proxy
|
||||
execSync('networksetup -setsecurewebproxy ${networkType} ${ip} ${port} && networksetup -setproxybypassdomains ${networkType} 127.0.0.1 localhost'
|
||||
.replace(/\${networkType}/g, networkType)
|
||||
.replace('${ip}', ip)
|
||||
.replace('${port}', port));
|
||||
};
|
||||
|
||||
macProxyManager.disableGlobalProxy = (proxyType) => {
|
||||
proxyType = proxyType || 'http';
|
||||
const networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
|
||||
return /^http$/i.test(proxyType) ?
|
||||
|
||||
// set http proxy
|
||||
execSync(
|
||||
'networksetup -setwebproxystate ${networkType} off'
|
||||
.replace('${networkType}', networkType)) :
|
||||
|
||||
// set https proxy
|
||||
execSync(
|
||||
'networksetup -setsecurewebproxystate ${networkType} off'
|
||||
.replace('${networkType}', networkType));
|
||||
};
|
||||
|
||||
macProxyManager.getProxyState = () => {
|
||||
const networkType = macProxyManager.networkType || macProxyManager.getNetworkType();
|
||||
const result = execSync('networksetup -getwebproxy ${networkType}'.replace('${networkType}', networkType));
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------------------
|
||||
@@ -131,36 +125,28 @@ macProxyManager.getProxyState = function() {
|
||||
* ------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
var winProxyManager = {};
|
||||
const winProxyManager = {};
|
||||
|
||||
winProxyManager.enableGlobalProxy = function(ip, port) {
|
||||
winProxyManager.enableGlobalProxy = (ip, port) => {
|
||||
if (!ip && !port) {
|
||||
console.log('failed to set global proxy server.\n ip and port are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ip && !port) {
|
||||
console.log('failed to set global proxy server.\n ip and port are required.');
|
||||
return;
|
||||
};
|
||||
|
||||
return execSync(
|
||||
// set proxy
|
||||
'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /t REG_SZ /d ${ip}:${port} /f & '
|
||||
.replace("${ip}", ip)
|
||||
.replace("${port}", port) +
|
||||
|
||||
// enable proxy
|
||||
'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f');
|
||||
return execSync(
|
||||
// set proxy
|
||||
'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /t REG_SZ /d ${ip}:${port} /f & '
|
||||
.replace('${ip}', ip)
|
||||
.replace('${port}', port) +
|
||||
|
||||
// enable proxy
|
||||
'reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f');
|
||||
};
|
||||
|
||||
winProxyManager.disableGlobalProxy = function() {
|
||||
return execSync('reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 0 /f');
|
||||
};
|
||||
winProxyManager.disableGlobalProxy = () => execSync('reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 0 /f');
|
||||
|
||||
winProxyManager.getProxyState = function() {
|
||||
return '';
|
||||
};
|
||||
winProxyManager.getProxyState = () => ''
|
||||
|
||||
winProxyManager.getNetworkType = function() {
|
||||
return '';
|
||||
};
|
||||
winProxyManager.getNetworkType = () => ''
|
||||
|
||||
module.exports = /^win/.test(process.platform) ? winProxyManager : macProxyManager;
|
||||
|
391
lib/util.js
391
lib/util.js
@@ -1,110 +1,96 @@
|
||||
var fs = require("fs"),
|
||||
path = require("path"),
|
||||
mime = require('mime-types'),
|
||||
color = require('colorful'),
|
||||
logUtil = require("./log"),
|
||||
exec = require('child_process').exec;
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs'),
|
||||
path = require('path'),
|
||||
mime = require('mime-types'),
|
||||
color = require('colorful'),
|
||||
Buffer = require('buffer').Buffer,
|
||||
configUtil = require('./configUtil'),
|
||||
logUtil = require('./log');
|
||||
const networkInterfaces = require('os').networkInterfaces();
|
||||
|
||||
const changeCase = require('change-case');
|
||||
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
|
||||
module.exports.lower_keys = function(obj){
|
||||
for(var key in obj){
|
||||
var val = obj[key];
|
||||
delete obj[key];
|
||||
module.exports.lower_keys = (obj) => {
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
delete obj[key];
|
||||
|
||||
obj[key.toLowerCase()] = val;
|
||||
}
|
||||
obj[key.toLowerCase()] = val;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
module.exports.merge = function(baseObj, extendObj){
|
||||
for(var key in extendObj){
|
||||
baseObj[key] = extendObj[key];
|
||||
}
|
||||
module.exports.merge = function (baseObj, extendObj) {
|
||||
for (const key in extendObj) {
|
||||
baseObj[key] = extendObj[key];
|
||||
}
|
||||
|
||||
return baseObj;
|
||||
}
|
||||
return baseObj;
|
||||
};
|
||||
|
||||
function getUserHome(){
|
||||
return process.env.HOME || process.env.USERPROFILE;
|
||||
function getUserHome() {
|
||||
return process.env.HOME || process.env.USERPROFILE;
|
||||
}
|
||||
module.exports.getUserHome = getUserHome;
|
||||
|
||||
function getAnyProxyHome() {
|
||||
const home = configUtil.getAnyProxyHome();
|
||||
if (!fs.existsSync(home)) {
|
||||
fs.mkdirSync(home);
|
||||
}
|
||||
return home;
|
||||
}
|
||||
module.exports.getAnyProxyHome = getAnyProxyHome;
|
||||
|
||||
module.exports.getAnyProxyHome = function(){
|
||||
var home = path.join(util.getUserHome(),"/.anyproxy/");
|
||||
module.exports.getAnyProxyPath = function (pathName) {
|
||||
const home = getAnyProxyHome();
|
||||
const targetPath = path.join(home, pathName);
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
fs.mkdirSync(targetPath);
|
||||
}
|
||||
return targetPath;
|
||||
}
|
||||
|
||||
if(!fs.existsSync(home)){
|
||||
try{
|
||||
fs.mkdirSync(home, '0777');
|
||||
}catch(e){
|
||||
return null;
|
||||
module.exports.simpleRender = function (str, object, regexp) {
|
||||
return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => {
|
||||
if (match.charAt(0) === '\\') {
|
||||
return match.slice(1);
|
||||
}
|
||||
return (object[name] != null) ? object[name] : '';
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.filewalker = function (root, cb) {
|
||||
root = root || process.cwd();
|
||||
|
||||
const ret = {
|
||||
directory: [],
|
||||
file: []
|
||||
};
|
||||
|
||||
fs.readdir(root, (err, list) => {
|
||||
if (list && list.length) {
|
||||
list.map((item) => {
|
||||
const fullPath = path.join(root, item),
|
||||
stat = fs.lstatSync(fullPath);
|
||||
|
||||
if (stat.isFile()) {
|
||||
ret.file.push({
|
||||
name: item,
|
||||
fullPath
|
||||
});
|
||||
} else if (stat.isDirectory()) {
|
||||
ret.directory.push({
|
||||
name: item,
|
||||
fullPath
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return home;
|
||||
}
|
||||
|
||||
var CACHE_DIR_PREFIX = "cache_r";
|
||||
module.exports.generateCacheDir = function(){
|
||||
var rand = Math.floor(Math.random() * 1000000),
|
||||
cachePath = path.join(util.getAnyProxyHome(),"./" + CACHE_DIR_PREFIX + rand);
|
||||
|
||||
fs.mkdirSync(cachePath, '0777');
|
||||
return cachePath;
|
||||
}
|
||||
|
||||
module.exports.clearCacheDir = function(cb){
|
||||
var home = util.getAnyProxyHome(),
|
||||
isWin = /^win/.test(process.platform);
|
||||
|
||||
var dirNameWildCard = CACHE_DIR_PREFIX + "*";
|
||||
if(isWin){
|
||||
exec("for /D %f in (" + dirNameWildCard + ") do rmdir %f /s /q",{cwd : home},cb);
|
||||
}else{
|
||||
exec("rm -rf " + dirNameWildCard + "",{cwd : home},cb);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.simpleRender = function(str, object, regexp){
|
||||
return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), function(match, name){
|
||||
if (match.charAt(0) == '\\') return match.slice(1);
|
||||
return (object[name] != null) ? object[name] : '';
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.filewalker = function(root,cb){
|
||||
root = root || process.cwd();
|
||||
|
||||
var ret = {
|
||||
directory :[],
|
||||
file :[]
|
||||
};
|
||||
|
||||
fs.readdir(root,function(err, list){
|
||||
if(list && list.length){
|
||||
list.map(function(item){
|
||||
var fullPath = path.join(root,item),
|
||||
stat = fs.lstatSync(fullPath);
|
||||
|
||||
if(stat.isFile()){
|
||||
ret.file.push({
|
||||
name : item,
|
||||
fullPath : fullPath
|
||||
});
|
||||
|
||||
}else if(stat.isDirectory()){
|
||||
ret.directory.push({
|
||||
name : item,
|
||||
fullPath : fullPath
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cb && cb.apply(null,[null,ret]);
|
||||
});
|
||||
cb && cb.apply(null, [null, ret]);
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -112,46 +98,211 @@ module.exports.filewalker = function(root,cb){
|
||||
* 比如在useLocalResponse的时候会使用到
|
||||
*/
|
||||
module.exports.contentType = function (filepath) {
|
||||
return mime.contentType(path.extname(filepath));
|
||||
return mime.contentType(path.extname(filepath));
|
||||
};
|
||||
|
||||
/*
|
||||
* 读取file的大小,以byte为单位
|
||||
*/
|
||||
module.exports.contentLength = function (filepath) {
|
||||
try {
|
||||
var stat = fs.statSync(filepath);
|
||||
return stat.size;
|
||||
} catch (e) {
|
||||
logUtil.printLog(color.red("\nfailed to ready local file : " + filepath));
|
||||
logUtil.printLog(color.red(e));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.showRootInstallTip = function () {
|
||||
logUtil.printLog(color.red("can not find rootCA.crt or rootCA.key"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("you may generate one by the following methods"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\twhen using globally : anyproxy --root"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\twhen using as a module : require(\"anyproxy\").generateRootCA();"), logUtil.T_ERR);
|
||||
logUtil.printLog(color.red("\tmore info : https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy"), logUtil.T_ERR);
|
||||
try {
|
||||
const stat = fs.statSync(filepath);
|
||||
return stat.size;
|
||||
} catch (e) {
|
||||
logUtil.printLog(color.red('\nfailed to ready local file : ' + filepath));
|
||||
logUtil.printLog(color.red(e));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* remove the cache before requering, the path SHOULD BE RELATIVE TO UTIL.JS
|
||||
* remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS
|
||||
*/
|
||||
module.exports.freshRequire = function (path) {
|
||||
delete require.cache[require.resolve(path)];
|
||||
return require(path);
|
||||
module.exports.freshRequire = function (modulePath) {
|
||||
delete require.cache[require.resolve(modulePath)];
|
||||
return require(modulePath);
|
||||
};
|
||||
|
||||
module.exports.upper_keys = function (obj) {
|
||||
var upperObject = {};
|
||||
for(var key in obj) {
|
||||
var upperKey = changeCase.headerCase(key);
|
||||
upperObject[upperKey] = obj[key];
|
||||
/*
|
||||
* format the date string
|
||||
* @param date Date or timestamp
|
||||
* @param formatter YYYYMMDDHHmmss
|
||||
*/
|
||||
module.exports.formatDate = function (date, formatter) {
|
||||
if (typeof date !== 'object') {
|
||||
date = new Date(date);
|
||||
}
|
||||
const transform = function (value) {
|
||||
return value < 10 ? '0' + value : value;
|
||||
};
|
||||
return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => {
|
||||
switch (match) {
|
||||
case 'YYYY':
|
||||
return transform(date.getFullYear());
|
||||
case 'MM':
|
||||
return transform(date.getMonth() + 1);
|
||||
case 'mm':
|
||||
return transform(date.getMinutes());
|
||||
case 'DD':
|
||||
return transform(date.getDate());
|
||||
case 'hh':
|
||||
return transform(date.getHours());
|
||||
case 'ss':
|
||||
return transform(date.getSeconds());
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
|
||||
return upperObject;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* get headers(Object) from rawHeaders(Array)
|
||||
* @param rawHeaders [key, value, key2, value2, ...]
|
||||
|
||||
*/
|
||||
|
||||
module.exports.getHeaderFromRawHeaders = function (rawHeaders) {
|
||||
const headerObj = {};
|
||||
const _handleSetCookieHeader = function (key, value) {
|
||||
if (headerObj[key].constructor === Array) {
|
||||
headerObj[key].push(value);
|
||||
} else {
|
||||
headerObj[key] = [headerObj[key], value];
|
||||
}
|
||||
};
|
||||
|
||||
if (!!rawHeaders) {
|
||||
for (let i = 0; i < rawHeaders.length; i += 2) {
|
||||
const key = rawHeaders[i];
|
||||
let value = rawHeaders[i + 1];
|
||||
|
||||
if (typeof value === 'string') {
|
||||
value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串
|
||||
}
|
||||
|
||||
if (!headerObj[key]) {
|
||||
headerObj[key] = value;
|
||||
} else {
|
||||
// headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
|
||||
// set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265
|
||||
if (key.toLowerCase() === 'set-cookie') {
|
||||
_handleSetCookieHeader(key, value);
|
||||
} else {
|
||||
headerObj[key] = headerObj[key] + ',' + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return headerObj;
|
||||
};
|
||||
|
||||
module.exports.getAllIpAddress = function getAllIpAddress() {
|
||||
const allIp = [];
|
||||
|
||||
Object.keys(networkInterfaces).map((nic) => {
|
||||
networkInterfaces[nic].filter((detail) => {
|
||||
if (detail.family.toLowerCase() === 'ipv4') {
|
||||
allIp.push(detail.address);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return allIp.length ? allIp : ['127.0.0.1'];
|
||||
};
|
||||
|
||||
function deleteFolderContentsRecursive(dirPath, ifClearFolderItself) {
|
||||
if (!dirPath.trim() || dirPath === '/') {
|
||||
throw new Error('can_not_delete_this_dir');
|
||||
}
|
||||
|
||||
if (fs.existsSync(dirPath)) {
|
||||
fs.readdirSync(dirPath).forEach((file) => {
|
||||
const curPath = path.join(dirPath, file);
|
||||
if (fs.lstatSync(curPath).isDirectory()) {
|
||||
deleteFolderContentsRecursive(curPath, true);
|
||||
} else { // delete all files
|
||||
fs.unlinkSync(curPath);
|
||||
}
|
||||
});
|
||||
|
||||
if (ifClearFolderItself) {
|
||||
try {
|
||||
// ref: https://github.com/shelljs/shelljs/issues/49
|
||||
const start = Date.now();
|
||||
while (true) {
|
||||
try {
|
||||
fs.rmdirSync(dirPath);
|
||||
break;
|
||||
} catch (er) {
|
||||
if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) {
|
||||
// Retry on windows, sometimes it takes a little time before all the files in the directory are gone
|
||||
if (Date.now() - start > 1000) throw er;
|
||||
} else if (er.code === 'ENOENT') {
|
||||
break;
|
||||
} else {
|
||||
throw er;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.deleteFolderContentsRecursive = deleteFolderContentsRecursive;
|
||||
|
||||
module.exports.getFreePort = function () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = require('net').createServer();
|
||||
server.unref();
|
||||
server.on('error', reject);
|
||||
server.listen(0, () => {
|
||||
const port = server.address().port;
|
||||
server.close(() => {
|
||||
resolve(port);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.collectErrorLog = function (error) {
|
||||
if (error && error.code && error.toString()) {
|
||||
return error.toString();
|
||||
} else {
|
||||
let result = [error, error.stack].join('\n');
|
||||
try {
|
||||
const errorString = error.toString();
|
||||
if (errorString.indexOf('You may only yield a function') >= 0) {
|
||||
result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq';
|
||||
}
|
||||
} catch (e) {}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.isFunc = function (source) {
|
||||
return source && Object.tostring.call(source) === '[object Function]';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {object} content
|
||||
* @returns the size of the content
|
||||
*/
|
||||
module.exports.getByteSize = function (content) {
|
||||
return Buffer.byteLength(content);
|
||||
};
|
||||
|
||||
/*
|
||||
* identify whether the
|
||||
*/
|
||||
module.exports.isIpDomain = function (domain) {
|
||||
if (!domain) {
|
||||
return false;
|
||||
}
|
||||
const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/;
|
||||
|
||||
return ipReg.test(domain);
|
||||
};
|
||||
|
@@ -1,157 +1,361 @@
|
||||
var express = require("express"),
|
||||
url = require('url'),
|
||||
fs = require("fs"),
|
||||
path = require("path"),
|
||||
events = require("events"),
|
||||
inherits = require("util").inherits,
|
||||
qrCode = require('qrcode-npm'),
|
||||
util = require("./util"),
|
||||
certMgr = require("./certMgr"),
|
||||
logUtil = require("./log"),
|
||||
juicer = require("juicer"),
|
||||
compress = require('compression');
|
||||
'use strict';
|
||||
|
||||
const DEFAULT_WEB_PORT = 8002; // port for web interface
|
||||
|
||||
function webInterface(config){
|
||||
var port = config.port,
|
||||
wsPort = config.wsPort,
|
||||
ipAddress = config.ip,
|
||||
userRule = config.userRule,
|
||||
ruleSummary = "",
|
||||
customMenu = [],
|
||||
server;
|
||||
const express = require('express'),
|
||||
url = require('url'),
|
||||
bodyParser = require('body-parser'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
events = require('events'),
|
||||
qrCode = require('qrcode-npm'),
|
||||
util = require('./util'),
|
||||
certMgr = require('./certMgr'),
|
||||
wsServer = require('./wsServer'),
|
||||
juicer = require('juicer'),
|
||||
ip = require('ip'),
|
||||
co = require('co'),
|
||||
compress = require('compression');
|
||||
|
||||
try{
|
||||
ruleSummary = userRule.summary();
|
||||
customMenu = userRule._getCustomMenu();
|
||||
}catch(e){}
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
var self = this,
|
||||
myAbsAddress = "http://" + ipAddress + ":" + port +"/",
|
||||
crtFilePath = certMgr.getRootCAFilePath(),
|
||||
staticDir = path.join(__dirname,'../web');
|
||||
const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @class webInterface
|
||||
* @extends {events.EventEmitter}
|
||||
*/
|
||||
class webInterface extends events.EventEmitter {
|
||||
|
||||
var app = express();
|
||||
app.use(compress()); //invoke gzip
|
||||
app.use(function(req, res, next) {
|
||||
res.setHeader("note", "THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE");
|
||||
/**
|
||||
* Creates an instance of webInterface.
|
||||
*
|
||||
* @param {object} config
|
||||
* @param {number} config.webPort
|
||||
* @param {number} config.wsPort
|
||||
* @param {object} recorder
|
||||
*
|
||||
* @memberOf webInterface
|
||||
*/
|
||||
constructor(config, recorder) {
|
||||
if (!recorder) {
|
||||
throw new Error('recorder is required for web interface');
|
||||
}
|
||||
super();
|
||||
const self = this;
|
||||
self.webPort = config.webPort || DEFAULT_WEB_PORT;
|
||||
self.recorder = recorder;
|
||||
self.config = config || {};
|
||||
|
||||
self.app = null;
|
||||
self.server = null;
|
||||
self.wsServer = null;
|
||||
}
|
||||
|
||||
start() {
|
||||
const self = this;
|
||||
const recorder = self.recorder;
|
||||
let wsPort;
|
||||
|
||||
return co(function *() {
|
||||
// determine ws port
|
||||
wsPort = self.config.wsPort ? self.config.wsPort : yield util.getFreePort();
|
||||
}).then(() => {
|
||||
const ipAddress = ip.address(),
|
||||
// userRule = proxyInstance.proxyRule,
|
||||
webBasePath = 'web';
|
||||
let ruleSummary = '';
|
||||
let customMenu = [];
|
||||
|
||||
try {
|
||||
ruleSummary = ''; //userRule.summary();
|
||||
customMenu = ''; // userRule._getCustomMenu();
|
||||
} catch (e) { }
|
||||
|
||||
const myAbsAddress = 'http://' + ipAddress + ':' + self.webPort + '/',
|
||||
staticDir = path.join(__dirname, '../', webBasePath);
|
||||
|
||||
const app = express();
|
||||
app.use(compress()); //invoke gzip
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('note', 'THIS IS A REQUEST FROM ANYPROXY WEB INTERFACE');
|
||||
return next();
|
||||
});
|
||||
});
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.get("/lastestLog",function(req,res){
|
||||
recorder.getRecords(null,120,function(err,docs){
|
||||
if(err){
|
||||
res.end(err.toString());
|
||||
}else{
|
||||
res.json(docs);
|
||||
}
|
||||
app.get('/latestLog', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
recorder.getRecords(null, 10000, (err, docs) => {
|
||||
if (err) {
|
||||
res.end(err.toString());
|
||||
} else {
|
||||
res.json(docs);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/downloadBody', (req, res) => {
|
||||
const query = req.query;
|
||||
recorder.getDecodedBody(query.id, (err, result) => {
|
||||
if (err || !result || !result.content) {
|
||||
res.json({});
|
||||
} else if (result.mime) {
|
||||
if (query.raw === 'true') {
|
||||
//TODO : cache query result
|
||||
res.type(result.mime).end(result.content);
|
||||
} else if (query.download === 'true') {
|
||||
res.setHeader('Content-disposition', `attachment; filename=${result.fileName}`);
|
||||
res.setHeader('Content-type', result.mime);
|
||||
res.end(result.content);
|
||||
}
|
||||
} else {
|
||||
res.json({
|
||||
|
||||
app.get("/fetchBody",function(req,res){
|
||||
var query = req.query;
|
||||
if(query && query.id){
|
||||
global.recorder.getDecodedBody(query.id, function(err, result){
|
||||
if(err || !result || !result.content){
|
||||
res.json({});
|
||||
}else if(result.type && result.type == "image" && result.mime){
|
||||
if(query.raw){
|
||||
//TODO : cache query result
|
||||
res.type(result.mime).end(result.content);
|
||||
}else{
|
||||
res.json({
|
||||
id : query.id,
|
||||
type : result.type,
|
||||
ref : "/fetchBody?id=" + query.id + "&raw=true"
|
||||
});
|
||||
}
|
||||
}else{
|
||||
res.json({
|
||||
id : query.id,
|
||||
type : result.type,
|
||||
content : result.content
|
||||
});
|
||||
}
|
||||
});
|
||||
}else{
|
||||
res.end({});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/fetchCrtFile",function(req,res){
|
||||
if(crtFilePath){
|
||||
res.setHeader("Content-Type","application/x-x509-ca-cert");
|
||||
res.setHeader("Content-Disposition",'attachment; filename="rootCA.crt"');
|
||||
res.end(fs.readFileSync(crtFilePath,{encoding:null}));
|
||||
}else{
|
||||
res.setHeader("Content-Type","text/html");
|
||||
res.end("can not file rootCA ,plase use <strong>anyproxy --root</strong> to generate one");
|
||||
}
|
||||
});
|
||||
|
||||
//make qr code
|
||||
app.get("/qr",function(req,res){
|
||||
var qr = qrCode.qrcode(4, 'M'),
|
||||
targetUrl = myAbsAddress,
|
||||
qrImageTag,
|
||||
resDom;
|
||||
|
||||
qr.addData(targetUrl);
|
||||
qr.make();
|
||||
qrImageTag = qr.createImgTag(4);
|
||||
|
||||
resDom = '<a href="__url"> __img <br> click or scan qr code to start client </a>'.replace(/__url/,targetUrl).replace(/__img/,qrImageTag);
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end(resDom);
|
||||
});
|
||||
|
||||
app.get("/qr_root",function(req,res){
|
||||
var qr = qrCode.qrcode(4, 'M'),
|
||||
targetUrl = myAbsAddress + "fetchCrtFile",
|
||||
qrImageTag,
|
||||
resDom;
|
||||
|
||||
qr.addData(targetUrl);
|
||||
qr.make();
|
||||
qrImageTag = qr.createImgTag(4);
|
||||
|
||||
resDom = '<a href="__url"> __img <br> click or scan qr code to download rootCA.crt </a>'.replace(/__url/,targetUrl).replace(/__img/,qrImageTag);
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end(resDom);
|
||||
});
|
||||
|
||||
app.use(function(req,res,next){
|
||||
var indexTpl = fs.readFileSync(path.join(staticDir,"/index.html"),{encoding:"utf8"}),
|
||||
opt = {
|
||||
rule : ruleSummary || "",
|
||||
customMenu : customMenu || [],
|
||||
wsPort : wsPort,
|
||||
ipAddress : ipAddress || "127.0.0.1"
|
||||
app.get('/fetchBody', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const query = req.query;
|
||||
if (query && query.id) {
|
||||
recorder.getDecodedBody(query.id, (err, result) => {
|
||||
// 返回下载信息
|
||||
const _resDownload = function (isDownload) {
|
||||
isDownload = typeof isDownload === 'boolean' ? isDownload : true;
|
||||
res.json({
|
||||
id: query.id,
|
||||
type: result.type,
|
||||
fileName: result.fileName,
|
||||
ref: `/downloadBody?id=${query.id}&download=${isDownload}&raw=${!isDownload}`
|
||||
});
|
||||
};
|
||||
|
||||
if( url.parse(req.url).pathname == "/"){
|
||||
res.setHeader("Content-Type", "text/html");
|
||||
res.end(juicer(indexTpl, opt));
|
||||
}else{
|
||||
next();
|
||||
// 返回内容
|
||||
const _resContent = () => {
|
||||
if (util.getByteSize(result.content || '') > MAX_CONTENT_SIZE) {
|
||||
_resDownload(true);
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
id: query.id,
|
||||
type: result.type,
|
||||
resBody: result.content
|
||||
});
|
||||
};
|
||||
|
||||
if (err || !result) {
|
||||
res.json({});
|
||||
} else if (result.statusCode === 200 && result.mime) {
|
||||
if (result.type === 'json' ||
|
||||
result.mime.indexOf('text') === 0 ||
|
||||
// deal with 'application/x-javascript' and 'application/javascript'
|
||||
result.mime.indexOf('javascript') > -1) {
|
||||
_resContent();
|
||||
} else if (result.type === 'image') {
|
||||
_resDownload(false);
|
||||
} else {
|
||||
_resDownload(true);
|
||||
}
|
||||
} else {
|
||||
_resContent();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.end({});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.use(express.static(staticDir));
|
||||
app.get('/fetchReqBody', (req, res) => {
|
||||
const query = req.query;
|
||||
if (query && query.id) {
|
||||
recorder.getSingleRecord(query.id, (err, doc) => {
|
||||
if (err || !doc[0]) {
|
||||
console.error(err);
|
||||
res.end('');
|
||||
return;
|
||||
}
|
||||
|
||||
//plugin from rule file
|
||||
if(typeof userRule._plugIntoWebinterface == "function"){
|
||||
userRule._plugIntoWebinterface(app,function(){
|
||||
server = app.listen(port);
|
||||
res.setHeader('Content-disposition', `attachment; filename=request_${query.id}_body.txt`);
|
||||
res.setHeader('Content-type', 'text/plain');
|
||||
res.end(doc[0].reqBody);
|
||||
});
|
||||
} else {
|
||||
res.end('');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/fetchCrtFile', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const _crtFilePath = certMgr.getRootCAFilePath();
|
||||
if (_crtFilePath) {
|
||||
res.setHeader('Content-Type', 'application/x-x509-ca-cert');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="rootCA.crt"');
|
||||
res.end(fs.readFileSync(_crtFilePath, { encoding: null }));
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end('can not file rootCA ,plase use <strong>anyproxy --root</strong> to generate one');
|
||||
}
|
||||
});
|
||||
|
||||
//make qr code
|
||||
app.get('/qr', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const qr = qrCode.qrcode(4, 'M'),
|
||||
targetUrl = myAbsAddress;
|
||||
qr.addData(targetUrl);
|
||||
qr.make();
|
||||
const qrImageTag = qr.createImgTag(4);
|
||||
const resDom = '<a href="__url"> __img <br> click or scan qr code to start client </a>'.replace(/__url/, targetUrl).replace(/__img/, qrImageTag);
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(resDom);
|
||||
});
|
||||
|
||||
app.get('/api/getQrCode', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const qr = qrCode.qrcode(4, 'M'),
|
||||
targetUrl = myAbsAddress + 'fetchCrtFile';
|
||||
|
||||
qr.addData(targetUrl);
|
||||
qr.make();
|
||||
const qrImageTag = qr.createImgTag(4);
|
||||
|
||||
// resDom = '<a href="__url"> __img <br> click or scan qr code to download rootCA.crt </a>'.replace(/__url/,targetUrl).replace(/__img/,qrImageTag);
|
||||
// res.setHeader("Content-Type", "text/html");
|
||||
// res.end(resDom);
|
||||
|
||||
const isRootCAFileExists = certMgr.isRootCAFileExists();
|
||||
res.json({
|
||||
status: 'success',
|
||||
url: targetUrl,
|
||||
isRootCAFileExists,
|
||||
qrImgDom: qrImageTag
|
||||
});
|
||||
}else{
|
||||
server = app.listen(port);
|
||||
}
|
||||
});
|
||||
|
||||
self.app = app;
|
||||
self.server = server;
|
||||
// response init data
|
||||
app.get('/api/getInitData', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const rootCAExists = certMgr.isRootCAFileExists();
|
||||
const rootDirPath = certMgr.getRootDirPath();
|
||||
const interceptFlag = false; //proxyInstance.getInterceptFlag(); TODO
|
||||
const globalProxyFlag = false; // TODO: proxyInstance.getGlobalProxyFlag();
|
||||
res.json({
|
||||
status: 'success',
|
||||
rootCAExists,
|
||||
rootCADirPath: rootDirPath,
|
||||
currentInterceptFlag: interceptFlag,
|
||||
currentGlobalProxyFlag: globalProxyFlag,
|
||||
ruleSummary: ruleSummary || '',
|
||||
ipAddress: util.getAllIpAddress(),
|
||||
port: '', //proxyInstance.proxyPort, // TODO
|
||||
wsPort,
|
||||
appVersion: packageJson.version
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/generateRootCA', (req, res) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
const rootExists = certMgr.isRootCAFileExists();
|
||||
if (!rootExists) {
|
||||
certMgr.generateRootCA(() => {
|
||||
res.json({
|
||||
status: 'success',
|
||||
code: 'done'
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
status: 'success',
|
||||
code: 'root_ca_exists'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// should not be available in in-build version
|
||||
// app.post('/api/toggleInterceptHttps', (req, res) => {
|
||||
// const rootExists = certMgr.isRootCAFileExists();
|
||||
// if (!rootExists) {
|
||||
// certMgr.generateRootCA(() => {
|
||||
// proxyInstance.setIntercept(req.body.flag);
|
||||
// // Also inform the web if RootCa exists
|
||||
// res.json({
|
||||
// status: 'success',
|
||||
// rootExists
|
||||
// });
|
||||
// });
|
||||
// } else {
|
||||
// proxyInstance.setIntercept(req.body.flag);
|
||||
// res.json({
|
||||
// status: 'success',
|
||||
// rootExists
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// app.post('/api/toggleGlobalProxy', (req, res) => {
|
||||
// const flag = req.body.flag;
|
||||
// let result = {};
|
||||
// result = flag ? proxyInstance.enableGlobalProxy() : proxyInstance.disableGlobalProxy();
|
||||
|
||||
// if (result.status) {
|
||||
// res.json({
|
||||
// status: 'failed',
|
||||
// errorMsg: result.stdout
|
||||
// });
|
||||
// } else {
|
||||
// res.json({
|
||||
// status: 'success',
|
||||
// isWindows: /^win/.test(process.platform)
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const indexTpl = fs.readFileSync(path.join(staticDir, '/index.html'), { encoding: 'utf8' }),
|
||||
opt = {
|
||||
rule: ruleSummary || '',
|
||||
customMenu: customMenu || [],
|
||||
wsPort,
|
||||
ipAddress: ipAddress || '127.0.0.1'
|
||||
};
|
||||
|
||||
if (url.parse(req.url).pathname === '/') {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end(juicer(indexTpl, opt));
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.use(express.static(staticDir));
|
||||
|
||||
//plugin from rule file
|
||||
const server = app.listen(self.webPort);
|
||||
|
||||
self.app = app;
|
||||
self.server = server;
|
||||
}).then(() => {
|
||||
// start ws server
|
||||
self.wsServer = new wsServer({
|
||||
port: wsPort
|
||||
}, recorder);
|
||||
|
||||
return self.wsServer.start();
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.server && this.server.close();
|
||||
this.wsServer && this.wsServer.closeAll();
|
||||
|
||||
this.server = null;
|
||||
this.wsServer = null;
|
||||
this.proxyInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
inherits(webInterface, events.EventEmitter);
|
||||
|
||||
module.exports = webInterface;
|
||||
|
222
lib/wsServer.js
222
lib/wsServer.js
@@ -1,98 +1,158 @@
|
||||
'use strict';
|
||||
|
||||
//websocket server manager
|
||||
|
||||
var WebSocketServer = require('ws').Server,
|
||||
logUtil = require("./log");
|
||||
const WebSocketServer = require('ws').Server;
|
||||
const logUtil = require('./log');
|
||||
|
||||
function resToMsg(msg,cb){
|
||||
var result = {},
|
||||
jsonData;
|
||||
function resToMsg(msg, recorder, cb) {
|
||||
let result = {},
|
||||
jsonData;
|
||||
|
||||
try{
|
||||
jsonData = JSON.parse(msg);
|
||||
}catch(e){
|
||||
result = {
|
||||
type : "error",
|
||||
error: "failed to parse your request : " + e.toString()
|
||||
try {
|
||||
jsonData = JSON.parse(msg);
|
||||
} catch (e) {
|
||||
result = {
|
||||
type: 'error',
|
||||
error: 'failed to parse your request : ' + e.toString()
|
||||
};
|
||||
cb && cb(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonData.reqRef) {
|
||||
result.reqRef = jsonData.reqRef;
|
||||
}
|
||||
|
||||
if (jsonData.type === 'reqBody' && jsonData.id) {
|
||||
result.type = 'body';
|
||||
recorder.getBody(jsonData.id, (err, data) => {
|
||||
if (err) {
|
||||
result.content = {
|
||||
id: null,
|
||||
body: null,
|
||||
error: err.toString()
|
||||
};
|
||||
cb && cb(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if(jsonData.reqRef){
|
||||
result.reqRef = jsonData.reqRef;
|
||||
}
|
||||
|
||||
if(jsonData.type == "reqBody" && jsonData.id){
|
||||
result.type = "body";
|
||||
global.recorder.getBody(jsonData.id, function(err, data){
|
||||
if(err){
|
||||
result.content = {
|
||||
id : null,
|
||||
body : null,
|
||||
error : err.toString()
|
||||
};
|
||||
}else{
|
||||
result.content = {
|
||||
id : jsonData.id,
|
||||
body : data.toString()
|
||||
};
|
||||
}
|
||||
cb && cb(result);
|
||||
});
|
||||
}else{ // more req handler here
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
result.content = {
|
||||
id: jsonData.id,
|
||||
body: data.toString()
|
||||
};
|
||||
}
|
||||
cb && cb(result);
|
||||
});
|
||||
} else { // more req handler here
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//config.port
|
||||
function wsServer(config){
|
||||
//web socket interface
|
||||
var self = this,
|
||||
wss = new WebSocketServer({port: config.port});
|
||||
wss.broadcast = function(data) {
|
||||
var key = data.id;
|
||||
if(typeof data == "object"){
|
||||
data = JSON.stringify(data);
|
||||
|
||||
class wsServer {
|
||||
constructor(config, recorder) {
|
||||
if (!recorder) {
|
||||
throw new Error('proxy recorder is required');
|
||||
} else if (!config || !config.port) {
|
||||
throw new Error('config.port is required');
|
||||
}
|
||||
|
||||
const self = this;
|
||||
self.config = config;
|
||||
self.recorder = recorder;
|
||||
}
|
||||
|
||||
start() {
|
||||
const self = this;
|
||||
const config = self.config;
|
||||
const recorder = self.recorder;
|
||||
return new Promise((resolve, reject) => {
|
||||
//web socket interface
|
||||
const wss = new WebSocketServer({
|
||||
port: config.port,
|
||||
clientTracking: true,
|
||||
}, resolve);
|
||||
|
||||
logUtil.printLog(`WebSoket setup at port ${config.port} `, logUtil.T_DEBUG);
|
||||
// the queue of the messages to be delivered
|
||||
let messageQueue = [];
|
||||
// the flat to indicate wheter to broadcast the record
|
||||
let broadcastFlag = true;
|
||||
|
||||
setInterval(() => {
|
||||
broadcastFlag = true;
|
||||
sendMultipleMessage();
|
||||
}, 50);
|
||||
|
||||
function sendMultipleMessage(data) {
|
||||
// if the flag goes to be true, and there are records to send
|
||||
if (broadcastFlag && messageQueue.length > 0) {
|
||||
wss && wss.broadcast({
|
||||
type: 'updateMultiple',
|
||||
content: messageQueue
|
||||
});
|
||||
messageQueue = [];
|
||||
broadcastFlag = false;
|
||||
} else {
|
||||
data && messageQueue.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i in this.clients){
|
||||
try{
|
||||
this.clients[i].send(data);
|
||||
}catch(e){
|
||||
logUtil.printLog("websocket failed to send data, " + e, logUtil.T_ERR);
|
||||
}
|
||||
wss.broadcast = function (data) {
|
||||
if (typeof data === 'object') {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
};
|
||||
|
||||
wss.on("connection",function(ws){
|
||||
ws.on("message",function(msg){
|
||||
resToMsg(msg,function(res){
|
||||
res && ws.send(JSON.stringify(res));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
wss.on("close",function(){});
|
||||
|
||||
global.recorder.on("update",function(data){
|
||||
try{
|
||||
wss && wss.broadcast({
|
||||
type : "update",
|
||||
content: data
|
||||
});
|
||||
|
||||
}catch(e){
|
||||
console.log("ws error");
|
||||
console.log(e);
|
||||
for (const client of wss.clients) {
|
||||
try {
|
||||
client.send(data);
|
||||
} catch (e) {
|
||||
logUtil.printLog('websocket failed to send data, ' + e, logUtil.T_ERR);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
self.wss = wss;
|
||||
}
|
||||
wss.on('connection', (ws) => {
|
||||
ws.on('message', (msg) => {
|
||||
resToMsg(msg, recorder, (res) => {
|
||||
res && ws.send(JSON.stringify(res));
|
||||
});
|
||||
});
|
||||
|
||||
wsServer.prototype.closeAll = function(){
|
||||
var self = this;
|
||||
self.wss.close();
|
||||
ws.on('error', (e) => {
|
||||
console.error('error in ws:', e);
|
||||
});
|
||||
});
|
||||
|
||||
wss.on('error', (e) => {
|
||||
logUtil.printLog('websocket error, ' + e, logUtil.T_ERR);
|
||||
});
|
||||
|
||||
wss.on('close', () => { });
|
||||
|
||||
recorder.on('update', (data) => {
|
||||
try {
|
||||
sendMultipleMessage(data);
|
||||
} catch (e) {
|
||||
console.log('ws error');
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
self.wss = wss;
|
||||
});
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
const self = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
self.wss.close((e) => {
|
||||
if (e) {
|
||||
reject(e);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = wsServer;
|
||||
|
Reference in New Issue
Block a user