move functions to wtvshared, initial wtvproxy filtering code by flamelord
This commit is contained in:
@@ -464,7 +464,7 @@ async function handleCGI(executable, cgi_file, socket, request_headers, vault, s
|
|||||||
var stdout = data.split("\r\n\r\n", 2);
|
var stdout = data.split("\r\n\r\n", 2);
|
||||||
var headers = stdout[0];
|
var headers = stdout[0];
|
||||||
data = stdout[1];
|
data = stdout[1];
|
||||||
headers = headerStringToObj(headers, true);
|
headers = wtvshared.headerStringToObj(headers, true);
|
||||||
if (!headers.Status) headers.Status = "200 OK";
|
if (!headers.Status) headers.Status = "200 OK";
|
||||||
headers['Connection'] = 'keep-alive';
|
headers['Connection'] = 'keep-alive';
|
||||||
sendToClient(socket, headers, data);
|
sendToClient(socket, headers, data);
|
||||||
@@ -1167,7 +1167,7 @@ function handleProxy(socket, request_type, request_headers, res, data) {
|
|||||||
|
|
||||||
// header pass-through whitelist, case insensitive comparsion to server, however, you should
|
// header pass-through whitelist, case insensitive comparsion to server, however, you should
|
||||||
// specify the header case as you intend for the client
|
// specify the header case as you intend for the client
|
||||||
var headers = stripHeaders(res.headers, [
|
var headers = wtvshared.stripHeaders(res.headers, [
|
||||||
'Connection',
|
'Connection',
|
||||||
'Server',
|
'Server',
|
||||||
'Date',
|
'Date',
|
||||||
@@ -1345,79 +1345,6 @@ async function doHTTPProxy(socket, request_headers) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripHeaders(headers_obj, whitelist) {
|
|
||||||
var whitelisted_headers = new Array();
|
|
||||||
var out_headers = new Array();
|
|
||||||
out_headers.Status = headers_obj.Status;
|
|
||||||
if (headers_obj['wtv-connection-close']) out_headers['wtv-connection-close'] = headers_obj['wtv-connection-close'];
|
|
||||||
|
|
||||||
// compare regardless of case
|
|
||||||
Object.keys(whitelist).forEach(function (k) {
|
|
||||||
Object.keys(headers_obj).forEach(function (j) {
|
|
||||||
if (whitelist[k].toLowerCase() == j.toLowerCase()) {
|
|
||||||
// if header = connection, strip 'upgrade'
|
|
||||||
if (j.toLowerCase() == "connection") {
|
|
||||||
headers_obj[j] = headers_obj[j].replace("Upgrade", "").replace(",", "").trim();
|
|
||||||
}
|
|
||||||
whitelisted_headers[j.toLowerCase()] = [whitelist[k], j, headers_obj[j]];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// restore original header order
|
|
||||||
Object.keys(headers_obj).forEach(function (k) {
|
|
||||||
if (whitelisted_headers[k.toLowerCase()]) {
|
|
||||||
if (whitelisted_headers[k.toLowerCase()][1] == k) out_headers[whitelisted_headers[k.toLowerCase()][0]] = whitelisted_headers[k.toLowerCase()][2];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// return
|
|
||||||
return out_headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
function headerStringToObj(headers, response = false) {
|
|
||||||
var inc_headers = 0;
|
|
||||||
var headers_obj = {};
|
|
||||||
headers_obj.raw_headers = headers;
|
|
||||||
var headers_obj_pre = headers.split("\n");
|
|
||||||
headers_obj_pre.forEach(function (d) {
|
|
||||||
if (/^SECURE ON/.test(d) && !response) {
|
|
||||||
headers_obj.secure = true;
|
|
||||||
} else if (/^([0-9]{3}) $/.test(d.substring(0, 4)) && response && !headers_obj.Status) {
|
|
||||||
d.s
|
|
||||||
headers_obj.Status = d.trim("\r");
|
|
||||||
} else if (/^(GET |PUT |POST)$/.test(d.substring(0, 4)) && !response) {
|
|
||||||
headers_obj.request = d.trim("\r");
|
|
||||||
var request_url = d.split(' ');
|
|
||||||
if (request_url.length > 2) {
|
|
||||||
request_url.shift();
|
|
||||||
request_url = request_url.join(" ");
|
|
||||||
if (request_url.indexOf("HTTP/") > 0) {
|
|
||||||
var index = request_url.indexOf(" HTTP/");
|
|
||||||
request_url = request_url.substring(0, index);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
request_url = request_url[1];
|
|
||||||
}
|
|
||||||
headers_obj.request_url = decodeURI(request_url).trim("\r");
|
|
||||||
} else if (d.indexOf(":") > 0) {
|
|
||||||
var d_split = d.split(':');
|
|
||||||
var header_name = d_split[0];
|
|
||||||
if (headers_obj[header_name] != null) {
|
|
||||||
header_name = header_name + "_" + inc_headers;
|
|
||||||
inc_headers++;
|
|
||||||
}
|
|
||||||
d_split.shift();
|
|
||||||
d = d_split.join(':');
|
|
||||||
headers_obj[header_name] = (d).trim("\r");
|
|
||||||
if (headers_obj[header_name].substring(0, 1) == " ") {
|
|
||||||
headers_obj[header_name] = headers_obj[header_name].substring(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return headers_obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function sendToClient(socket, headers_obj, data = null) {
|
async function sendToClient(socket, headers_obj, data = null) {
|
||||||
var headers = "";
|
var headers = "";
|
||||||
var content_length = 0;
|
var content_length = 0;
|
||||||
@@ -1427,7 +1354,7 @@ async function sendToClient(socket, headers_obj, data = null) {
|
|||||||
if (typeof (data) === 'undefined' || data === null) data = '';
|
if (typeof (data) === 'undefined' || data === null) data = '';
|
||||||
if (typeof (headers_obj) === 'string') {
|
if (typeof (headers_obj) === 'string') {
|
||||||
// string to header object
|
// string to header object
|
||||||
headers_obj = headerStringToObj(headers_obj, true);
|
headers_obj = wtvshared.headerStringToObj(headers_obj, true);
|
||||||
}
|
}
|
||||||
if (!socket_sessions[socket.id]) {
|
if (!socket_sessions[socket.id]) {
|
||||||
if (socket.destroy) socket.destroy();
|
if (socket.destroy) socket.destroy();
|
||||||
@@ -1800,13 +1727,13 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
|
|||||||
}
|
}
|
||||||
if (isUnencryptedString(data)) {
|
if (isUnencryptedString(data)) {
|
||||||
if (headers.length != 0) {
|
if (headers.length != 0) {
|
||||||
var new_header_obj = headerStringToObj(data);
|
var new_header_obj = wtvshared.headerStringToObj(data);
|
||||||
Object.keys(new_header_obj).forEach(function (k, v) {
|
Object.keys(new_header_obj).forEach(function (k, v) {
|
||||||
headers[k] = new_header_obj[k];
|
headers[k] = new_header_obj[k];
|
||||||
});
|
});
|
||||||
new_header_obj = null;
|
new_header_obj = null;
|
||||||
} else {
|
} else {
|
||||||
headers = headerStringToObj(data);
|
headers = wtvshared.headerStringToObj(data);
|
||||||
}
|
}
|
||||||
} else if (!skipSecure) {
|
} else if (!skipSecure) {
|
||||||
// if its a POST request, assume its a binary blob and not encrypted (dangerous)
|
// if its a POST request, assume its a binary blob and not encrypted (dangerous)
|
||||||
|
|||||||
86
zefie_wtvp_minisrv/includes/classes/WTVProxy.js
Normal file
86
zefie_wtvp_minisrv/includes/classes/WTVProxy.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
'use strict';
|
||||||
|
const { WTVShared, clientShowAlert } = require("./WTVShared.js");
|
||||||
|
|
||||||
|
class WTVProxy {
|
||||||
|
constructor(minisrv_config) {
|
||||||
|
this.minisrv_config = minisrv_config;
|
||||||
|
this.wtvshared = new WTVShared(this.minisrv_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
transformHtml(html) {
|
||||||
|
try {
|
||||||
|
// Apply existing transformations
|
||||||
|
let transformed = html
|
||||||
|
.replace(/[^\x20-\x7E\n\r\t]/g, '') // Remove non-ASCII
|
||||||
|
.replace(/\s+/g, ' ') // Collapse whitespace
|
||||||
|
.replace(/<!--[\s\S]*?-->/g, '') // Remove comments
|
||||||
|
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // Remove scripts
|
||||||
|
.replace(/<meta\b[^<]*(?:(?!>)<[^<]*)*>/gi, '') // Remove meta tags
|
||||||
|
.replace(/<img\b[^<]*(?:(?!>)<[^<]*)*>/gi, '') // Remove images
|
||||||
|
.replace(/<input\b[^<]*(?:(?!>)<[^<]*)*>/gi, '') // Remove input tags
|
||||||
|
.replace(/<link\b[^<]*(?:(?!>)<[^<]*)*>/gi, '') // Remove link tags
|
||||||
|
.replace(/<embed\b[^<]*(?:(?!>)<[^<]*)*>/gi, '') // Remove embed tags
|
||||||
|
.replace(/<a\b[^<]*(?:(?!>)<[^<]*)*>/gi, '') // Remove links
|
||||||
|
.replace(/<\/a>/gi, '') // Remove closing links
|
||||||
|
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
||||||
|
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
|
||||||
|
.replace(/javascript:/gi, '')
|
||||||
|
.replace(/on\w+\s*=\s*("[^"]*"|'[^']*'|[^ >]+)/gi, '')
|
||||||
|
.replace(/style\s*=\s*("[^"]*"|'[^']*'|[^ >]+)/gi, '')
|
||||||
|
.replace(/class\s*=\s*("[^"]*"|'[^']*'|[^ >]+)/gi, '')
|
||||||
|
.replace(/id\s*=\s*("[^"]*"|'[^']*'|[^ >]+)/gi, '')
|
||||||
|
.replace(/<(div|span|section|article|aside|header|footer|nav)\b/gi, '')
|
||||||
|
.replace(/<\/(div|span|section|article|aside|header|footer|nav)>/gi, '')
|
||||||
|
.replace(/FP_preloadImgs\s*\(.*?\)/gi, '');
|
||||||
|
|
||||||
|
// Normalize for processing
|
||||||
|
transformed = transformed
|
||||||
|
.replace(/>\s+</g, '><') // Remove accidental whitespace between tags
|
||||||
|
.replace(/</g, '\n<') // Add newline before each tag
|
||||||
|
.replace(/>/g, '>\n') // Add newline after each tag
|
||||||
|
.replace(/\n\s*\n/g, '\n'); // Collapse multiple newlines
|
||||||
|
|
||||||
|
// Format with indentation
|
||||||
|
const lines = transformed.split('\n');
|
||||||
|
let indentLevel = 0;
|
||||||
|
const indentSize = 2;
|
||||||
|
|
||||||
|
const formatted = lines.map((line) => {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed === '') return '';
|
||||||
|
|
||||||
|
const isClosing = /^<\/.+?>/.test(trimmed);
|
||||||
|
const isSelfClosing = /^<.+?\/>$/.test(trimmed) ||
|
||||||
|
/^<hr/i.test(trimmed) || /^<br/i.test(trimmed) ||
|
||||||
|
/^<meta/i.test(trimmed) || /^<img/i.test(trimmed) ||
|
||||||
|
/^<input/i.test(trimmed) || /^<audioscope/i.test(trimmed);
|
||||||
|
const isOpening = /^<([a-zA-Z0-9]+)(?!.*\/>).*?>/.test(trimmed) && !isClosing;
|
||||||
|
|
||||||
|
if (isClosing) indentLevel = Math.max(indentLevel - 1, 0);
|
||||||
|
|
||||||
|
const indentedLine = ' '.repeat(indentLevel * indentSize) + trimmed;
|
||||||
|
|
||||||
|
if (isOpening && !isSelfClosing) indentLevel++;
|
||||||
|
|
||||||
|
return indentedLine;
|
||||||
|
});
|
||||||
|
|
||||||
|
transformed = formatted.join('\n').trim();
|
||||||
|
|
||||||
|
// Wrap in DOCTYPE and HTML structure
|
||||||
|
transformed = `<!DOCTYPE html>\n<html>\n <head>\n <meta http-equiv="content-type" content="text/html; charset=iso-8859-1">\n </head>\n <body>\n${transformed}\n </body>\n</html>`;
|
||||||
|
|
||||||
|
// Truncate if necessary
|
||||||
|
if (transformed.length > 512) {
|
||||||
|
transformed = transformed.substring(0, 512);
|
||||||
|
transformed = transformed.substring(0, transformed.lastIndexOf('<')) + '\n </body>\n</html>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(transformed, 'ascii').toString('ascii');
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`HTML transformation failed: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WTVProxy;
|
||||||
@@ -327,6 +327,91 @@ class WTVShared {
|
|||||||
return clean;
|
return clean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a header string into an object
|
||||||
|
* @param {string} headers Header string to convert
|
||||||
|
* @param {boolean} response If true, the headers are a response, otherwise they are a request
|
||||||
|
* @return {object} Headers object
|
||||||
|
* */
|
||||||
|
headerStringToObj(headers, response = false) {
|
||||||
|
var inc_headers = 0;
|
||||||
|
var headers_obj = {};
|
||||||
|
headers_obj.raw_headers = headers;
|
||||||
|
var headers_obj_pre = headers.split("\n");
|
||||||
|
headers_obj_pre.forEach(function (d) {
|
||||||
|
if (/^SECURE ON/.test(d) && !response) {
|
||||||
|
headers_obj.secure = true;
|
||||||
|
} else if (/^([0-9]{3}) $/.test(d.substring(0, 4)) && response && !headers_obj.Status) {
|
||||||
|
d.s
|
||||||
|
headers_obj.Status = d.trim("\r");
|
||||||
|
} else if (/^(GET |PUT |POST)$/.test(d.substring(0, 4)) && !response) {
|
||||||
|
headers_obj.request = d.trim("\r");
|
||||||
|
var request_url = d.split(' ');
|
||||||
|
if (request_url.length > 2) {
|
||||||
|
request_url.shift();
|
||||||
|
request_url = request_url.join(" ");
|
||||||
|
if (request_url.indexOf("HTTP/") > 0) {
|
||||||
|
var index = request_url.indexOf(" HTTP/");
|
||||||
|
request_url = request_url.substring(0, index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
request_url = request_url[1];
|
||||||
|
}
|
||||||
|
headers_obj.request_url = decodeURI(request_url).trim("\r");
|
||||||
|
} else if (d.indexOf(":") > 0) {
|
||||||
|
var d_split = d.split(':');
|
||||||
|
var header_name = d_split[0];
|
||||||
|
if (headers_obj[header_name] != null) {
|
||||||
|
header_name = header_name + "_" + inc_headers;
|
||||||
|
inc_headers++;
|
||||||
|
}
|
||||||
|
d_split.shift();
|
||||||
|
d = d_split.join(':');
|
||||||
|
headers_obj[header_name] = (d).trim("\r");
|
||||||
|
if (headers_obj[header_name].substring(0, 1) == " ") {
|
||||||
|
headers_obj[header_name] = headers_obj[header_name].substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return headers_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips headers not in the whitelist
|
||||||
|
* @param {object} headers_obj // Headers object to strip
|
||||||
|
* @param {Array<string>} whitelist // Array of header names to keep, case insensitive
|
||||||
|
* @returns {object} // Headers object with only whitelisted headers
|
||||||
|
*/
|
||||||
|
stripHeaders(headers_obj, whitelist) {
|
||||||
|
var whitelisted_headers = new Array();
|
||||||
|
var out_headers = new Array();
|
||||||
|
out_headers.Status = headers_obj.Status;
|
||||||
|
if (headers_obj['wtv-connection-close']) out_headers['wtv-connection-close'] = headers_obj['wtv-connection-close'];
|
||||||
|
|
||||||
|
// compare regardless of case
|
||||||
|
Object.keys(whitelist).forEach(function (k) {
|
||||||
|
Object.keys(headers_obj).forEach(function (j) {
|
||||||
|
if (whitelist[k].toLowerCase() == j.toLowerCase()) {
|
||||||
|
// if header = connection, strip 'upgrade'
|
||||||
|
if (j.toLowerCase() == "connection") {
|
||||||
|
headers_obj[j] = headers_obj[j].replace("Upgrade", "").replace(",", "").trim();
|
||||||
|
}
|
||||||
|
whitelisted_headers[j.toLowerCase()] = [whitelist[k], j, headers_obj[j]];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// restore original header order
|
||||||
|
Object.keys(headers_obj).forEach(function (k) {
|
||||||
|
if (whitelisted_headers[k.toLowerCase()]) {
|
||||||
|
if (whitelisted_headers[k.toLowerCase()][1] == k) out_headers[whitelisted_headers[k.toLowerCase()][0]] = whitelisted_headers[k.toLowerCase()][2];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// return
|
||||||
|
return out_headers;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to determine if the string is ASCII
|
* Attempts to determine if the string is ASCII
|
||||||
* @param {string} str
|
* @param {string} str
|
||||||
|
|||||||
Reference in New Issue
Block a user