mirror of
https://github.com/titaniumnetwork-dev/Ultraviolet.git
synced 2025-05-15 20:40:01 -04:00
229 lines
No EOL
7.3 KiB
JavaScript
229 lines
No EOL
7.3 KiB
JavaScript
import http from "http";
|
|
import https from "https";
|
|
import httpStatic from "node-static";
|
|
import path from "path";
|
|
import { readFileSync, createReadStream } from "fs";
|
|
import webpack from "webpack";
|
|
|
|
const __dirname = path.resolve(path.dirname(decodeURI(new URL(import.meta.url).pathname))).slice(3);
|
|
const file = new httpStatic.Server(path.join(__dirname, './static/'));
|
|
|
|
const server = https.createServer({
|
|
key: readFileSync(path.join(__dirname, './ssl.key')),
|
|
cert: readFileSync(path.join(__dirname, './ssl.cert')),
|
|
});
|
|
|
|
server.on('request', (req, res) => {
|
|
|
|
if (req.url.startsWith('/service/')) {
|
|
res.writeHead(200, { "Content-Type": "text/html", "Cache-Control": 'no-cache' });
|
|
createReadStream(path.join(__dirname, './load.html')).pipe(res);
|
|
return;
|
|
};
|
|
|
|
if (!req.url.startsWith('/bare/v1/')) return file.serve(req, res);
|
|
|
|
|
|
try {
|
|
const headers = JSON.parse(req.headers['x-bare-headers']);
|
|
const forward = JSON.parse((req.headers['x-bare-forward-headers'] || '[]'));
|
|
const url = new URL(req.headers['x-bare-protocol'] + '//' + req.headers['x-bare-host'] + ':' + req.headers['x-bare-port'] + req.headers['x-bare-path']);
|
|
|
|
for (const header of forward) {
|
|
if (req.headers[header]) headers[header] = req.headers[header];
|
|
};
|
|
|
|
const remoteRequest = (url.protocol === 'https:' ? https : http).request(
|
|
url,
|
|
{
|
|
headers: headers,
|
|
method: req.method,
|
|
}
|
|
);
|
|
|
|
remoteRequest.on('response', remoteResponse => {
|
|
remoteResponse.headers['x-bare-headers'] = JSON.stringify(remoteResponse.headers);
|
|
remoteResponse.headers['x-bare-status'] = remoteResponse.statusCode.toString();
|
|
remoteResponse.headers['x-bare-status-text'] = remoteResponse.statusMessage;
|
|
remoteResponse.headers['cache-control'] = 'no-cache';
|
|
|
|
const headers = {
|
|
'x-bare-headers': JSON.stringify(remoteResponse.headers),
|
|
'x-bare-status': remoteResponse.statusCode.toString(),
|
|
'x-bare-status-text': remoteResponse.statusMessage,
|
|
'cache-control': 'no-cache',
|
|
};
|
|
|
|
if (remoteResponse.headers['content-encoding']) headers['content-encoding'] = remoteResponse.headers['content-encoding'];
|
|
if (remoteResponse.headers['content-length']) headers['content-length'] = remoteResponse.headers['content-length'];
|
|
|
|
res.writeHead(200, headers);
|
|
remoteResponse.pipe(res);
|
|
});
|
|
|
|
remoteRequest.on('error', e => {
|
|
res.writeHead(500, {});
|
|
res.end();
|
|
});
|
|
|
|
req.pipe(remoteRequest);
|
|
} catch(e) {
|
|
res.writeHead(500, {});
|
|
res.end();
|
|
};
|
|
|
|
});
|
|
|
|
const impl = {
|
|
'accept-encoding': 'Accept-Encoding',
|
|
'accept-language': 'Accept-Language',
|
|
'accept': 'Accept',
|
|
'sec-websocket-extensions': 'Sec-WebSocket-Extensions',
|
|
'sec-websocket-key': 'Sec-WebSocket-Key',
|
|
'sec-websocket-version': 'Sec-WebSocket-Version'
|
|
};
|
|
|
|
server.on('upgrade', (req, socket, head) => {
|
|
if (!req.url.startsWith('/bare/v1/') || !req.headers['sec-websocket-protocol']) return socket.end();
|
|
try {
|
|
const [ bare, data ] = req.headers['sec-websocket-protocol'].split(/,\s*/g);
|
|
const {
|
|
remote,
|
|
headers,
|
|
forward_headers: forward,
|
|
} = JSON.parse(decodeProtocol(data));
|
|
|
|
for (const header of forward) {
|
|
if (req.headers[header]) headers[(impl[header] || header)] = req.headers[header];
|
|
};
|
|
|
|
const url = new URL(remote.protocol + '//' + remote.host + ':' + remote.port + remote.path);
|
|
const remoteRequest = (url.protocol === 'https:' ? https : http).request(
|
|
url,
|
|
{
|
|
headers,
|
|
method: req.method,
|
|
}
|
|
);
|
|
|
|
remoteRequest.on('upgrade', (remoteResponse, remoteSocket, remoteHead) => {
|
|
let handshake = 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n';
|
|
if (remoteResponse.headers['sec-websocket-accept']) handshake += `Sec-WebSocket-Accept: ${remoteResponse.headers['sec-websocket-accept']}\r\n`;
|
|
if (remoteResponse.headers['sec-websocket-extensions']) handshake += `Sec-WebSocket-Extensions: ${remoteResponse.headers['sec-websocket-extensions']}\r\n`;
|
|
handshake += `Sec-WebSocket-Protocol: bare\r\n`;
|
|
if (remoteResponse.headers['connection']) handshake += `Connection: ${remoteResponse.headers['connection']}\r\n`;
|
|
if (remoteResponse.headers['upgrade']) handshake += `Upgrade: ${remoteResponse.headers['upgrade']}\r\n`;
|
|
handshake += '\r\n';
|
|
socket.write(handshake);
|
|
socket.write(remoteHead);
|
|
remoteSocket.on('close', () => socket.end());
|
|
socket.on('close', () => remoteSocket.end());
|
|
remoteSocket.on('error', () => socket.end());
|
|
socket.on('error', () => remoteSocket.end());
|
|
|
|
remoteSocket.pipe(socket);
|
|
socket.pipe(remoteSocket);
|
|
});
|
|
|
|
remoteRequest.on('error', () => socket.end());
|
|
|
|
remoteRequest.end();
|
|
|
|
} catch(e) {
|
|
console.log(e);
|
|
socket.end();
|
|
};
|
|
});
|
|
|
|
|
|
server.listen(443);
|
|
|
|
|
|
const valid_chars = "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~";
|
|
const reserved_chars = "%";
|
|
|
|
function encodeProtocol(protocol){
|
|
protocol = protocol.toString();
|
|
|
|
let result = '';
|
|
|
|
for(let i = 0; i < protocol.length; i++){
|
|
const char = protocol[i];
|
|
|
|
if(valid_chars.includes(char) && !reserved_chars.includes(char)){
|
|
result += char;
|
|
}else{
|
|
const code = char.charCodeAt();
|
|
result += '%' + code.toString(16).padStart(2, 0);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function decodeProtocol(protocol){
|
|
if(typeof protocol != 'string')throw new TypeError('protocol must be a string');
|
|
|
|
let result = '';
|
|
|
|
for(let i = 0; i < protocol.length; i++){
|
|
const char = protocol[i];
|
|
|
|
if(char == '%'){
|
|
const code = parseInt(protocol.slice(i + 1, i + 3), 16);
|
|
const decoded = String.fromCharCode(code);
|
|
|
|
result += decoded;
|
|
i += 2;
|
|
}else{
|
|
result += char;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
function parseRawHeaders(rawHeaders = []) {
|
|
const obj = {};
|
|
|
|
for (let i = 0; i < rawHeaders.length; i+=2) {
|
|
const name = rawHeaders[i] || '';
|
|
const lowerCaseName = name.toLowerCase();
|
|
const value = rawHeaders[i + 1] || '';
|
|
|
|
if (lowerCaseName in obj) {
|
|
if (Array.isArray(obj[lowerCaseName].value)) {
|
|
obj[lowerCaseName].value.push(value);
|
|
} else {
|
|
obj[lowerCaseName].value = [ obj[lowerCaseName].value, value ];
|
|
};
|
|
} else {
|
|
obj[lowerCaseName] = { name, value };
|
|
};
|
|
};
|
|
|
|
return obj;
|
|
};
|
|
|
|
function compileParsedHeaders(headers = {}, prefix = false) {
|
|
const compiled = {};
|
|
|
|
for (const key in headers) {
|
|
const { name, value } = headers[key];
|
|
compiled[(prefix ? 'x-op-' : '') + name] = value;
|
|
};
|
|
|
|
return compiled;
|
|
};
|
|
|
|
webpack({
|
|
mode: 'none',
|
|
entry: path.join(__dirname, '../lib/index.js'),
|
|
output: {
|
|
path: __dirname,
|
|
filename: './static/op.bundle.js',
|
|
}
|
|
}, (err, i) =>
|
|
console.log(!err ? 'Ultraviolet bundled!' : 'Err')
|
|
); |