update to 4.0

This commit is contained in:
Otto Mao
2017-12-01 21:30:49 +08:00
parent e392fefc64
commit 4be5aa8954
267 changed files with 27008 additions and 84482 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
View 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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View 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
View 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,
};

View File

@@ -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;
},
};

View File

@@ -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;

View File

@@ -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);
};

View File

@@ -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;

View File

@@ -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;