@lemonce3/mitm
Version:
HTTP/HTTPS man in the middle proxy server
155 lines (130 loc) • 3.63 kB
JavaScript
const fs = require('fs');
const path = require('path');
module.exports = function normalize(options) {
const finalOptions = defaultOptionsFactory();
if (options) {
const { strategy, certificate, socket, onError } = options;
if (strategy) {
const {
sslConnect = finalOptions.strategy.sslConnect,
websocket = finalOptions.strategy.websocket,
request = finalOptions.strategy.request,
response = finalOptions.strategy.response
} = strategy;
if (!isFunction(sslConnect)) {
throw new Error('`sslConnect` is not a function');
}
if (!isFunction(websocket)) {
throw new Error('`webSocket` is not a function');
}
if (!isFunction(request)) {
throw new Error('`request` is not a function');
}
if (!isFunction(response)) {
throw new Error('`response` is not a function');
}
finalOptions.strategy.sslConnect = sslConnect;
finalOptions.strategy.websocket = websocket;
finalOptions.strategy.request = request;
finalOptions.strategy.response = response;
}
if (certificate) {
const {
cert = finalOptions.certificate.cert,
key = finalOptions.certificate.key,
store
} = certificate;
if (!cert || !key) {
throw new Error('root certificate properties could not be empty.');
}
if (!store || !store.get || !store.set) {
throw new Error('self-made certificate storage not be provided.');
}
if (!isFunction(store.get) || !isFunction(store.set)) {
throw new Error('certificate storage method `get` or `set` is not a function.');
}
finalOptions.certificate.cert = cert;
finalOptions.certificate.key = key;
finalOptions.certificate.store = store;
}
if (socket) {
const {
path = finalOptions.socket.path,
getName = finalOptions.socket.getName
} = socket;
if (!path) {
throw new Error('socket path must not be empty.');
}
if (!isFunction(getName)) {
throw new Error('`getName` is not a function');
}
finalOptions.socket.path = path;
finalOptions.socket.getName = getName;
}
if (onError && isFunction(onError)) {
finalOptions.onError = onError;
}
}
try {
fs.accessSync(finalOptions.socket.path, fs.constants.R_OK && fs.constants.W_OK);
} catch (error) {
if (error.code === 'ENOENT') {
try {
fs.mkdirSync(finalOptions.socket.path, { recursive: true });
} catch (error) {
throw new Error('create `socketFile.path` failed.');
}
} else {
throw new Error('`socketFile.path` MUST can read and write.');
}
}
fs.readdirSync(finalOptions.socket.path).forEach(file => {
const filePath = path.join(finalOptions.socket.path, file);
fs.statSync(filePath).isDirectory() ? null : fs.unlinkSync(filePath);
});
return finalOptions;
}
function defaultOptionsFactory() {
const defaultCertificateStore = {};
return {
strategy: {
sslConnect() {
return false;
},
websocket(clientSocket, proxySocket) {
clientSocket.pipe(proxySocket);
proxySocket.pipe(clientSocket);
},
request(_context, _respond, forward) {
forward();
},
response(_context, respond) {
respond();
}
},
socket: {
path: path.resolve('.pipe'),
getName(protocol, hostname, port) {
return `${protocol}-${hostname}-${port}`;
}
},
certificate: {
cert: null,
key: null,
store: {
get(hostname) {
return defaultCertificateStore[hostname];
},
set(hostname, certKeyPair) {
defaultCertificateStore[hostname] = certKeyPair;
}
}
},
onError(type, message) {
console.log(type, message);
}
}
}
function isFunction(any) {
return typeof any === 'function';
}