'use strict'; const DEFAULT_WEB_PORT = 8002; // port for web interface 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'), compress = require('compression'); const packageJson = require('../package.json'); const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb /** * * * @class webInterface * @extends {events.EventEmitter} */ class webInterface extends events.EventEmitter { /** * Creates an instance of webInterface. * * @param {object} config * @param {number} config.webPort * @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 = this.getServer(); self.server = null; self.wsServer = null; } /** * get the express server */ getServer() { const self = this; const recorder = self.recorder; 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('/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', (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, method: result.meethod, fileName: result.fileName, ref: `/downloadBody?id=${query.id}&download=${isDownload}&raw=${!isDownload}` }); }; // 返回内容 const _resContent = () => { if (util.getByteSize(result.content || '') > MAX_CONTENT_SIZE) { _resDownload(true); return; } res.json({ id: query.id, type: result.type, method: result.method, 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.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; } 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('/fetchWsMessages', (req, res) => { const query = req.query; if (query && query.id) { recorder.getDecodedWsMessage(query.id, (err, messages) => { if (err) { console.error(err); res.json([]); return; } res.json(messages); }); } else { res.json([]); } }); 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 anyproxy --root 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 = ' __img
click or scan qr code to start client
'.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 = ' __img
click or scan qr code to download rootCA.crt
'.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 }); }); // 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 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' }); } }); app.use((req, res, next) => { const indexTpl = fs.readFileSync(path.join(staticDir, '/index.html'), { encoding: 'utf8' }), opt = { rule: ruleSummary || '', customMenu: customMenu || [], 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)); return app; } start() { const self = this; return new Promise((resolve, reject) => { self.server = self.app.listen(self.webPort); self.wsServer = new wsServer({ server: self.server }, self.recorder); self.wsServer.start(); resolve(); }) } close() { this.server && this.server.close(); this.wsServer && this.wsServer.closeAll(); this.server = null; this.wsServer = null; this.proxyInstance = null; } } module.exports = webInterface;