'use strict'; const DEFAULT_WEB_PORT = 8002; // port for web interface import * as express from 'express'; import * as url from 'url'; import * as bodyParser from 'body-parser'; import * as fs from 'fs'; import * as path from 'path'; import * as events from 'events'; import * as qrCode from 'qrcode-npm'; import * as juicer from 'juicer'; import * as ip from 'ip'; import * as http from 'http'; import * as compress from 'compression'; import * as buffer from 'buffer'; import util from './util'; import certMgr from './certMgr'; import WsServer from './wsServer'; import Recorder from './recorder'; import LogUtil from './log'; /*tslint:disable:no-var-requires*/ const packageJson = require('../package.json'); // 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').default, // certMgr = require('./certMgr').default, // wsServer = require('./wsServer'), // juicer = require('juicer'), // ip = require('ip'), // compress = require('compression'); // const packageJson = require('../package.json'); const Buffer = buffer.Buffer; const MAX_CONTENT_SIZE = 1024 * 2000; // 2000kb /** * * * @class webInterface * @extends {events.EventEmitter} */ class WebInterface extends events.EventEmitter { public webPort: number; private recorder: Recorder; private app: Express.Application; private server: http.Server; private wsServer: WsServer; constructor(config: AnyProxyWebInterfaceConfig, recorder: 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 */ public getServer(): Express.Application { const self = this; const recorder = self.recorder; const ipAddress = ip.address(); // userRule = proxyInstance.proxyRule, const webBasePath = 'web'; let ruleSummary = ''; let customMenu = []; try { ruleSummary = ''; // userRule.summary(); customMenu = []; // userRule._getCustomMenu(); } catch (e) { LogUtil.error(e.stack); } const myAbsAddress = 'http://' + ipAddress + ':' + self.webPort + '/'; const 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(parseInt(query.id, 10), (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(parseInt(query.id, 10), (err, result) => { // 返回下载信息 const resDownload = function(isDownload: boolean): void { isDownload = typeof isDownload === 'boolean' ? isDownload : true; res.json({ id: query.id, type: result.type, method: result.method, fileName: result.fileName, ref: `/downloadBody?id=${query.id}&download=${isDownload}&raw=${!isDownload}`, }); }; // 返回内容 const resContent = () => { if (util.getByteSize(result.content || Buffer.from('')) > 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(parseInt(query.id, 10), (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(parseInt(query.id, 10), (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'); const 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'); const 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.ifRootCAFileExists(); 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' }); const 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; } public start(): Promise { const self = this; return new Promise((resolve, reject) => { self.server = (self.app as any).listen(self.webPort); self.wsServer = new WsServer({ server: self.server, }, self.recorder); self.wsServer.start(); resolve(); }); } public close(cb: (error: Error) => void): void { this.server && this.server.close(); this.wsServer && this.wsServer.closeAll(); this.server = null; this.wsServer = null; // this.proxyInstance = null; } } export default WebInterface;