/* * SPDX-License-Identifier: GPL-2.0-only * * Copyright (C) 2023 ImmortalWrt.org */ import { mkstemp } from 'fs'; import { 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, ua) { if (!url || type(url) !== 'string') return null; if (!ua) ua = 'Wget/1.21 (HomeProxy, like v2rayN)'; const output = executeCommand(`/usr/bin/wget -qO- --user-agent ${shellQuote(ua)} --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 */