232 lines
5.0 KiB
Plaintext
232 lines
5.0 KiB
Plaintext
/*
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* Copyright (C) 2023 ImmortalWrt.org
|
|
*/
|
|
|
|
import { mkstemp } from 'fs';
|
|
import { urldecode, urldecode_params } from 'luci.http';
|
|
|
|
/* Global variables start */
|
|
export const HP_DIR = '/etc/homeproxy';
|
|
export const RUN_DIR = '/var/run/homeproxy';
|
|
/* Global variables end */
|
|
|
|
/* Utilities start */
|
|
/* Kanged from luci-app-commands */
|
|
export function shellQuote(s) {
|
|
return `'${replace(s, "'", "'\\''")}'`;
|
|
};
|
|
|
|
export function isBinary(str) {
|
|
for (let off = 0, byte = ord(str); off < length(str); byte = ord(str, ++off))
|
|
if (byte <= 8 || (byte >= 14 && byte <= 31))
|
|
return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
export function executeCommand(...args) {
|
|
let outfd = mkstemp();
|
|
let errfd = mkstemp();
|
|
|
|
const exitcode = system(`${join(' ', args)} >&${outfd.fileno()} 2>&${errfd.fileno()}`);
|
|
|
|
outfd.seek(0);
|
|
errfd.seek(0);
|
|
|
|
const stdout = outfd.read(1024 * 512) ?? '';
|
|
const stderr = errfd.read(1024 * 512) ?? '';
|
|
|
|
outfd.close();
|
|
errfd.close();
|
|
|
|
const binary = isBinary(stdout);
|
|
|
|
return {
|
|
command: join(' ', args),
|
|
stdout: binary ? null : stdout,
|
|
stderr,
|
|
exitcode,
|
|
binary
|
|
};
|
|
};
|
|
|
|
export function calcStringMD5(str) {
|
|
if (!str || type(str) !== 'string')
|
|
return null;
|
|
|
|
const output = executeCommand(`/bin/echo -n ${shellQuote(str)} | /usr/bin/md5sum | /usr/bin/awk '{print $1}'`) || {};
|
|
return trim(output.stdout);
|
|
};
|
|
|
|
export function getTime(epoch) {
|
|
const local_time = localtime(epoch);
|
|
return replace(replace(sprintf(
|
|
'%d-%2d-%2d@%2d:%2d:%2d',
|
|
local_time.year,
|
|
local_time.mon,
|
|
local_time.mday,
|
|
local_time.hour,
|
|
local_time.min,
|
|
local_time.sec
|
|
), ' ', '0'), '@', ' ');
|
|
|
|
};
|
|
|
|
export function wGET(url) {
|
|
if (!url || type(url) !== 'string')
|
|
return null;
|
|
|
|
const output = executeCommand(`/usr/bin/wget -qO- --user-agent 'Wget/1.21 (HomeProxy, like v2rayN)' --timeout=10 ${shellQuote(url)}`) || {};
|
|
return trim(output.stdout);
|
|
};
|
|
/* Utilities end */
|
|
|
|
/* String helper start */
|
|
export function isEmpty(res) {
|
|
return !res || res === 'nil' || (type(res) in ['array', 'object'] && length(res) === 0);
|
|
};
|
|
|
|
export function strToBool(str) {
|
|
return (str === '1') || null;
|
|
};
|
|
|
|
export function strToInt(str) {
|
|
return !isEmpty(str) ? (int(str) || null) : null;
|
|
};
|
|
|
|
export function removeBlankAttrs(res) {
|
|
let content;
|
|
|
|
if (type(res) === 'object') {
|
|
content = {};
|
|
map(keys(res), (k) => {
|
|
if (type(res[k]) in ['array', 'object'])
|
|
content[k] = removeBlankAttrs(res[k]);
|
|
else if (res[k] !== null && res[k] !== '')
|
|
content[k] = res[k];
|
|
});
|
|
} else if (type(res) === 'array') {
|
|
content = [];
|
|
map(res, (k, i) => {
|
|
if (type(k) in ['array', 'object'])
|
|
push(content, removeBlankAttrs(k));
|
|
else if (k !== null && k !== '')
|
|
push(content, k);
|
|
});
|
|
} else
|
|
return res;
|
|
|
|
return content;
|
|
};
|
|
|
|
export function validateHostname(hostname) {
|
|
return (match(hostname, /^[a-zA-Z0-9_]+$/) != null ||
|
|
(match(hostname, /^[a-zA-Z0-9_][a-zA-Z0-9_%-.]*[a-zA-Z0-9]$/) &&
|
|
match(hostname, /[^0-9.]/)));
|
|
};
|
|
|
|
export function validation(datatype, data) {
|
|
if (!datatype || !data)
|
|
return null;
|
|
|
|
const ret = system(`/sbin/validate_data ${shellQuote(datatype)} ${shellQuote(data)} 2>/dev/null`);
|
|
return (ret === 0);
|
|
};
|
|
/* String helper end */
|
|
|
|
/* String parser start */
|
|
export function decodeBase64Str(str) {
|
|
if (isEmpty(str))
|
|
return null;
|
|
|
|
str = trim(str);
|
|
str = replace(str, '_', '/');
|
|
str = replace(str, '-', '+');
|
|
|
|
const padding = length(str) % 4;
|
|
if (padding)
|
|
str = str + substr('====', padding);
|
|
|
|
return b64dec(str);
|
|
};
|
|
|
|
export function parseURL(url) {
|
|
if (type(url) !== 'string')
|
|
return null;
|
|
|
|
const services = {
|
|
http: '80',
|
|
https: '443'
|
|
};
|
|
|
|
const objurl = {};
|
|
|
|
objurl.href = url;
|
|
|
|
url = replace(url, /#(.+)$/, (_, val) => {
|
|
objurl.hash = val;
|
|
return '';
|
|
});
|
|
|
|
url = replace(url, /^(\w[A-Za-z0-9\+\-\.]+):/, (_, val) => {
|
|
objurl.protocol = val;
|
|
return '';
|
|
});
|
|
|
|
url = replace(url, /\?(.+)/, (_, val) => {
|
|
objurl.search = val;
|
|
objurl.searchParams = urldecode_params(val);
|
|
return '';
|
|
});
|
|
|
|
url = replace(url, /^\/\/([^\/]+)/, (_, val) => {
|
|
val = replace(val, /^([^@]+)@/, (_, val) => {
|
|
objurl.userinfo = val;
|
|
return '';
|
|
});
|
|
|
|
val = replace(val, /:(\d+)$/, (_, val) => {
|
|
objurl.port = val;
|
|
return '';
|
|
});
|
|
|
|
if (validation('ip4addr', val) ||
|
|
validation('ip6addr', replace(val, /\[|\]/g, '')) ||
|
|
validation('hostname', val))
|
|
objurl.hostname = val;
|
|
|
|
return '';
|
|
});
|
|
|
|
objurl.pathname = url || '/';
|
|
|
|
if (!objurl.protocol || !objurl.hostname)
|
|
return null;
|
|
|
|
if (objurl.userinfo) {
|
|
objurl.userinfo = replace(objurl.userinfo, /:([^:]+)$/, (_, val) => {
|
|
objurl.password = val;
|
|
return '';
|
|
});
|
|
|
|
if (match(objurl.userinfo, /^[A-Za-z0-9\+\-\_\.]+$/)) {
|
|
objurl.username = objurl.userinfo;
|
|
delete objurl.userinfo;
|
|
} else {
|
|
delete objurl.userinfo;
|
|
delete objurl.password;
|
|
}
|
|
};
|
|
|
|
if (!objurl.port)
|
|
objurl.port = services[objurl.protocol];
|
|
|
|
objurl.host = objurl.hostname + (objurl.port ? `:${objurl.port}` : '');
|
|
objurl.origin = `${objurl.protocol}://${objurl.host}`;
|
|
|
|
return objurl;
|
|
};
|
|
/* String parser end */
|