128 lines
4.0 KiB
JavaScript
128 lines
4.0 KiB
JavaScript
import { URL } from 'url';
|
|
import http from 'http';
|
|
import https from 'https';
|
|
import { logger, isValidPort } from '../utils';
|
|
|
|
function checkBasicAuthorization(credentials, { username, password }) {
|
|
if (!credentials) {
|
|
return false;
|
|
}
|
|
const utf8str = Buffer.from(credentials, 'base64').toString();
|
|
const [uname, passwd] = utf8str.split(':');
|
|
if (uname !== username || passwd !== password) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function createServer({ secure, https_key, https_cert, username, password }) {
|
|
const name = secure ? 'https' : 'http';
|
|
let server = null;
|
|
if (secure) {
|
|
server = https.createServer({ key: https_key, cert: https_cert });
|
|
} else {
|
|
server = http.createServer();
|
|
}
|
|
|
|
const isAuthRequired = username !== '' && password !== '';
|
|
|
|
// Simple HTTP Proxy
|
|
server.on('request', (req, res) => {
|
|
let parseResult;
|
|
try {
|
|
parseResult = new URL(req.url);
|
|
} catch (err) {
|
|
res.writeHead(400);
|
|
return res.end();
|
|
}
|
|
const { hostname, port, pathname } = parseResult;
|
|
const { socket, method, httpVersion, headers } = req;
|
|
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
|
|
const _port = +port || 80;
|
|
|
|
if (hostname === null || !isValidPort(_port)) {
|
|
const remote = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
logger.warn(`[${name}] drop invalid http request sent from ${remote}`);
|
|
return res.end();
|
|
}
|
|
|
|
// Basic Authorization
|
|
if (isAuthRequired) {
|
|
const proxyAuth = headers['proxy-authorization'] || '';
|
|
const [type, credentials] = proxyAuth.split(' ');
|
|
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
|
|
logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
|
|
res.writeHead(401);
|
|
return res.end();
|
|
}
|
|
}
|
|
|
|
// prevent recv before we connected to the server
|
|
socket.pause();
|
|
|
|
server.emit('proxyConnection', socket, {
|
|
host: hostname,
|
|
port: _port,
|
|
onConnected: (send) => {
|
|
// keep-alive mechanism seems problematic in our proxy
|
|
delete headers['proxy-connection'];
|
|
headers['connection'] = 'close';
|
|
|
|
// rebuild headers
|
|
const headerKeys = Object.keys(headers);
|
|
const newHeaders = headerKeys.reduce((result, key) => {
|
|
result.push(`${key}: ${headers[key]}\r\n`);
|
|
return result;
|
|
}, []);
|
|
const reqMsg = `${method} ${pathname} HTTP/${httpVersion}\r\n` + newHeaders.join('') + '\r\n';
|
|
send(Buffer.from(reqMsg));
|
|
|
|
// free to recv from application now
|
|
socket.resume();
|
|
},
|
|
});
|
|
});
|
|
|
|
// HTTPS tunnel
|
|
server.on('connect', (req, socket) => {
|
|
const [hostname, _port] = req.url.split(':');
|
|
const appAddress = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
|
|
const port = parseInt(_port) || 443;
|
|
|
|
if (hostname === null || !isValidPort(port)) {
|
|
const remote = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
logger.warn(`[${name}] [${appAddress}] drop invalid http CONNECT request sent from ${remote}`);
|
|
return socket.destroy();
|
|
}
|
|
|
|
// Basic Authorization
|
|
if (isAuthRequired) {
|
|
const proxyAuth = req.headers['proxy-authorization'] || '';
|
|
const [type, credentials] = proxyAuth.split(' ');
|
|
if (type !== 'Basic' || !checkBasicAuthorization(credentials, { username, password })) {
|
|
logger.error(`[${name}] [${appAddress}] authorization failed, type=${type} credentials=${credentials}`);
|
|
return socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
}
|
|
}
|
|
|
|
server.emit('proxyConnection', socket, {
|
|
host: hostname,
|
|
port: port,
|
|
onConnected: () => {
|
|
socket.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
|
},
|
|
});
|
|
});
|
|
|
|
// errors
|
|
server.on('clientError', (err, socket) => {
|
|
const appAddress = `${socket.remoteAddress || ''}:${socket.remotePort || ''}`;
|
|
logger.error(`[${name}] [${appAddress}] invalid http request: ${err.message}`);
|
|
socket.destroy();
|
|
});
|
|
|
|
return server;
|
|
}
|