mirror of
https://github.com/asdlokj1qpi233/subconverter.git
synced 2026-11-04 02:34:06 +00:00
549 lines
16 KiB
C++
549 lines
16 KiB
C++
#include <string>
|
|
#include <map>
|
|
#include <iostream>
|
|
#include <quickjspp.hpp>
|
|
#include <quickjs/quickjs-libc.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif // _WIN32
|
|
|
|
#include "../handler/multithread.h"
|
|
#include "../handler/webget.h"
|
|
#include "../handler/settings.h"
|
|
#include "../parser/config/proxy.h"
|
|
#include "../utils/map_extra.h"
|
|
#include "../utils/system.h"
|
|
#include "script_quickjs.h"
|
|
|
|
std::string parseProxy(const std::string &source);
|
|
|
|
static const std::string qjs_require_module {R"(import * as std from 'std'
|
|
import * as os from 'os'
|
|
|
|
let modules = {}
|
|
|
|
let debug = console.log
|
|
{
|
|
let _debugOptions = std.getenv('DEBUG')
|
|
if (typeof _debugOptions == 'undefined' || _debugOptions.split(',').indexOf('require') === -1) {
|
|
debug = function () {}
|
|
}
|
|
}
|
|
|
|
class CJSModule {
|
|
constructor (id) {
|
|
this.id = id
|
|
this._failed = null
|
|
this._loaded = false
|
|
this.exports = {}
|
|
}
|
|
|
|
load () {
|
|
const __file = this.id
|
|
const __dir = _basename(this.id)
|
|
const _require = require
|
|
|
|
let ctx = { exports: {} }
|
|
// Prevents modules from changing exports
|
|
Object.seal(ctx)
|
|
|
|
const _mark = '<<SCRIPT>>'
|
|
let _loaderTemplate = `(function _loader (exports, require, module, __filename, __dirname) {${_mark}})(ctx.exports, _require, ctx, __file, __dir)`
|
|
|
|
let _script = std.loadFile(__file)
|
|
this._failed = _script === null
|
|
if (this._failed) {
|
|
return new Error(`Can't load script ${__file}`)
|
|
}
|
|
|
|
_script = _loaderTemplate.replace('<<SCRIPT>>', _script)
|
|
eval(_script)
|
|
|
|
this.exports = ctx.exports
|
|
this._loaded = true
|
|
return true
|
|
}
|
|
|
|
}
|
|
|
|
function _basename (path) {
|
|
let idx = path.lastIndexOf('/')
|
|
if (idx === 0)
|
|
return '/'
|
|
return path.substring(0, idx)
|
|
}
|
|
|
|
function _statPath (path) {
|
|
const [fstat, err] = os.stat(path)
|
|
return {
|
|
errno: err,
|
|
isFile: fstat && (fstat.mode & os.S_IFREG) && true,
|
|
isDir: fstat && (fstat.mode & os.S_IFDIR) && true
|
|
}
|
|
}
|
|
|
|
function _loadModule (path) {
|
|
debug(`_loadModule# Module ${path}`)
|
|
const [id, err] = os.realpath(path)
|
|
if (err) {
|
|
throw new Error(`Module require error: Can't get real module path for ${path}`)
|
|
return
|
|
}
|
|
|
|
debug(`_loadModule# id ${id}`)
|
|
if (modules.hasOwnProperty(id)) {
|
|
return modules[id]
|
|
}
|
|
|
|
let _module = new CJSModule(id)
|
|
modules[id] = _module
|
|
|
|
let _result = _module.load()
|
|
if (_result !== true) {
|
|
throw _result
|
|
return
|
|
}
|
|
return _module
|
|
}
|
|
|
|
function _lookupModule (path) {
|
|
let fstat = _statPath(path)
|
|
|
|
debug(`_lookupModule# Looking for ${path}`)
|
|
// Path found
|
|
if (fstat.isFile) {
|
|
debug(`_lookupModule# Found module file`)
|
|
return path
|
|
}
|
|
|
|
// Path not found
|
|
if (fstat.errno) {
|
|
debug(`_lookupModule# Not found module file`)
|
|
// Try with '.js' extension
|
|
if (!path.endsWith('.js') && _statPath(`${path}.js`).isFile) {
|
|
debug(`_lookupModule# Found appending .js to file name`)
|
|
return `${path}.js`
|
|
}
|
|
return new Error(`Error: Module ${path} not found!`)
|
|
}
|
|
|
|
// Path found and it isn't a dir
|
|
if (!fstat.isDir) {
|
|
return new Error(`Error: Module file type not supported for ${path}`)
|
|
}
|
|
|
|
// Path it's a dir
|
|
let _path = null // Real path to module
|
|
let _tryOthers = true // Keep trying?
|
|
|
|
debug(`_lookupModule# Path is a directory, trying options...`)
|
|
// Try with package.json for NPM or YARN modules
|
|
if (_statPath(`${path}/package.json`).isFile) {
|
|
debug(`_lookupModule# It has package.json, looking for main script...`)
|
|
let _pkg = JSON.parse(std.loadFile(`${path}/package.json`))
|
|
if (_pkg && Object.keys(_pkg).indexOf('main') !== -1 && _pkg.main !== '' && _statPath(`${path}/${_pkg.main}`).isFile) {
|
|
_tryOthers = false
|
|
_path = `${path}/${_pkg.main}`
|
|
debug(`_lookupModule# Found package main script!`)
|
|
}
|
|
}
|
|
// Try other options
|
|
if (_tryOthers && _statPath(`${path}/index.js`).isFile) {
|
|
_tryOthers = false
|
|
_path = `${path}/index.js`
|
|
debug(`_lookupModule# Found package index.js file`)
|
|
}
|
|
if (_tryOthers && _statPath(`${path}/main.js`).isFile) {
|
|
_tryOthers = false
|
|
_path = `${path}/main.js`
|
|
debug(`_lookupModule# Found package main.js file`)
|
|
}
|
|
|
|
if (_path === null) {
|
|
return new Error(`Error: Module ${path} is a directory, but not a package`)
|
|
}
|
|
|
|
debug(`_lookupModule# Found module file: ${_path}`)
|
|
// Returns what it founded
|
|
return _path
|
|
}
|
|
|
|
export function require (path) {
|
|
if (typeof __filename == 'undefined') {
|
|
debug('require# Calling from main script')
|
|
} else {
|
|
debug(`require# Calling from ${__filename} parent module`)
|
|
}
|
|
let _path = _lookupModule(path)
|
|
|
|
// Module not found
|
|
if (_path instanceof Error) {
|
|
throw _path
|
|
return
|
|
}
|
|
|
|
let _module = _loadModule(_path)
|
|
|
|
return _module.exports
|
|
})"};
|
|
|
|
class qjs_fetch_Headers
|
|
{
|
|
public:
|
|
qjs_fetch_Headers() = default;
|
|
|
|
string_icase_map headers;
|
|
|
|
void append(const std::string &key, const std::string &value)
|
|
{
|
|
headers[key] = value;
|
|
}
|
|
void parse_from_string(const std::string &data)
|
|
{
|
|
headers.clear();
|
|
string_array all_kv = split(data, "\r\n");
|
|
for(std::string &x : all_kv)
|
|
{
|
|
size_t pos_colon = x.find(':');
|
|
if(pos_colon == std::string::npos)
|
|
continue;
|
|
else if(pos_colon >= x.size() - 1)
|
|
headers[x.substr(0, pos_colon)] = "";
|
|
else
|
|
headers[x.substr(0, pos_colon)] = x.substr(pos_colon + 2, x.size() - pos_colon);
|
|
}
|
|
}
|
|
};
|
|
|
|
class qjs_fetch_Request
|
|
{
|
|
public:
|
|
qjs_fetch_Request() = default;
|
|
std::string method = "GET";
|
|
std::string url;
|
|
std::string proxy;
|
|
qjs_fetch_Headers headers;
|
|
std::string cookies;
|
|
std::string postdata;
|
|
explicit qjs_fetch_Request(const std::string &url) : url(url) {}
|
|
};
|
|
|
|
class qjs_fetch_Response
|
|
{
|
|
public:
|
|
qjs_fetch_Response() = default;
|
|
int status_code = 200;
|
|
std::string content;
|
|
std::string cookies;
|
|
qjs_fetch_Headers headers;
|
|
};
|
|
|
|
namespace qjs
|
|
{
|
|
namespace detail
|
|
{
|
|
using string_map = std::map<std::string, std::string>;
|
|
using string_icase_map = std::map<std::string, std::string, strICaseComp>;
|
|
}
|
|
|
|
template<>
|
|
struct js_traits<detail::string_icase_map>
|
|
{
|
|
static detail::string_icase_map unwrap(JSContext *ctx, JSValueConst v)
|
|
{
|
|
string_icase_map res;
|
|
JSPropertyEnum *props = nullptr, *props_begin;
|
|
uint32_t len = 0;
|
|
JS_GetOwnPropertyNames(ctx, &props, &len, v, JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY);
|
|
props_begin = props;
|
|
while(len > 0)
|
|
{
|
|
auto key = JS_AtomToCString(ctx, props->atom);
|
|
auto val = JS_GetProperty(ctx, v, props->atom);
|
|
auto valData = JS_ToCString(ctx, val);
|
|
res[key] = valData;
|
|
JS_FreeCString(ctx, valData);
|
|
JS_FreeValue(ctx, val);
|
|
JS_FreeCString(ctx, key);
|
|
JS_FreeAtom(ctx, props->atom);
|
|
props++;
|
|
len--;
|
|
}
|
|
js_free(ctx, props_begin);
|
|
return res;
|
|
}
|
|
static JSValue wrap(JSContext *ctx, const detail::string_icase_map &m) noexcept
|
|
{
|
|
auto obj = JS_NewObject(ctx);
|
|
|
|
for(auto &kv : m)
|
|
{
|
|
auto value = JS_NewStringLen(ctx, kv.second.c_str(), kv.second.size());
|
|
JS_SetPropertyStr(ctx, obj, kv.first.c_str(), value);
|
|
}
|
|
return obj;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct js_traits<qjs_fetch_Headers>
|
|
{
|
|
static qjs_fetch_Headers unwrap(JSContext *ctx, JSValueConst v)
|
|
{
|
|
qjs_fetch_Headers result;
|
|
result.headers = unwrap_free<detail::string_icase_map>(ctx, v, "headers");
|
|
return result;
|
|
}
|
|
static JSValue wrap(JSContext *ctx, const qjs_fetch_Headers &h)
|
|
{
|
|
auto obj = JS_NewObject(ctx);
|
|
JS_SetPropertyStr(ctx, obj, "headers", js_traits<detail::string_icase_map>::wrap(ctx, h.headers));
|
|
return obj;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct js_traits<qjs_fetch_Request>
|
|
{
|
|
static qjs_fetch_Request unwrap(JSContext *ctx, JSValueConst v)
|
|
{
|
|
qjs_fetch_Request request;
|
|
request.method = unwrap_free<std::string>(ctx, v, "method");
|
|
request.url = unwrap_free<std::string>(ctx, v, "url");
|
|
request.postdata = unwrap_free<std::string>(ctx, v, "data");
|
|
request.proxy = unwrap_free<std::string>(ctx, v, "proxy");
|
|
request.cookies = unwrap_free<std::string>(ctx, v, "cookies");
|
|
request.headers = unwrap_free<qjs_fetch_Headers>(ctx, v, "headers");
|
|
return request;
|
|
}
|
|
};
|
|
|
|
template<>
|
|
struct js_traits<qjs_fetch_Response>
|
|
{
|
|
static JSValue wrap(JSContext *ctx, const qjs_fetch_Response &r) noexcept
|
|
{
|
|
auto obj = JS_NewObject(ctx);
|
|
JS_SetPropertyStr(ctx, obj, "status_code", JS_NewInt32(ctx, r.status_code));
|
|
JS_SetPropertyStr(ctx, obj, "headers", js_traits<qjs_fetch_Headers>::wrap(ctx, r.headers));
|
|
JS_SetPropertyStr(ctx, obj, "data", JS_NewStringLen(ctx, r.content.c_str(), r.content.size()));
|
|
JS_SetPropertyStr(ctx, obj, "cookies", JS_NewStringLen(ctx, r.cookies.c_str(), r.cookies.size()));
|
|
return obj;
|
|
}
|
|
};
|
|
}
|
|
|
|
static std::string makeDataURI(const std::string &content, bool shouldBase64 = false)
|
|
{
|
|
if(shouldBase64)
|
|
return "data:text/plain;base64," + base64Encode(content);
|
|
else
|
|
return "data:text/plain," + content;
|
|
}
|
|
|
|
static qjs_fetch_Response qjs_fetch(qjs_fetch_Request request)
|
|
{
|
|
qjs_fetch_Response response;
|
|
http_method method;
|
|
switch(hash_(toUpper(request.method)))
|
|
{
|
|
case "GET"_hash:
|
|
method = request.postdata.empty() ? HTTP_GET : HTTP_POST;
|
|
break;
|
|
case "POST"_hash:
|
|
method = HTTP_POST;
|
|
break;
|
|
case "PATCH"_hash:
|
|
method = HTTP_PATCH;
|
|
break;
|
|
case "HEAD"_hash:
|
|
method = HTTP_HEAD;
|
|
break;
|
|
default:
|
|
return response;
|
|
}
|
|
|
|
std::string response_headers;
|
|
FetchArgument argument {method, request.url, request.proxy, &request.postdata, &request.headers.headers, &request.cookies, 0};
|
|
FetchResult result {&response.status_code, &response.content, &response_headers, &response.cookies};
|
|
|
|
webGet(argument, result);
|
|
response.headers.parse_from_string(response_headers);
|
|
|
|
return response;
|
|
}
|
|
|
|
static std::string qjs_getUrlArg(const std::string &url, const std::string &request)
|
|
{
|
|
return getUrlArg(url, request);
|
|
}
|
|
|
|
std::string getGeoIP(const std::string &address, const std::string &proxy)
|
|
{
|
|
return fetchFile("https://api.ip.sb/geoip/" + address, parseProxy(proxy), global.cacheConfig);
|
|
}
|
|
|
|
void script_runtime_init(qjs::Runtime &runtime)
|
|
{
|
|
js_std_init_handlers(runtime.rt);
|
|
}
|
|
|
|
int ShowMsgbox(const std::string &title, std::string content, uint16_t type = 0)
|
|
{
|
|
#ifdef _WIN32
|
|
if(!type)
|
|
type = MB_ICONINFORMATION;
|
|
return MessageBoxA(NULL, utf8ToACP(content).c_str(), utf8ToACP(title).c_str(), type);
|
|
#else
|
|
return -1;
|
|
#endif // _WIN32
|
|
}
|
|
|
|
template<typename... Targs>
|
|
struct Lambda {
|
|
template<typename Tret, typename T>
|
|
static Tret lambda_ptr_exec(Targs... args) {
|
|
return (Tret) (*(T*)fn<T>())(args...);
|
|
}
|
|
|
|
template<typename Tret = void, typename Tfp = Tret(*)(Targs...), typename T>
|
|
static Tfp ptr(T& t) {
|
|
fn<T>(&t);
|
|
return (Tfp) lambda_ptr_exec<Tret, T>;
|
|
}
|
|
|
|
template<typename T>
|
|
static void* fn(void* new_fn = nullptr) {
|
|
static void* fn;
|
|
if (new_fn != nullptr)
|
|
fn = new_fn;
|
|
return fn;
|
|
}
|
|
};
|
|
|
|
uint32_t currentTime()
|
|
{
|
|
return time(NULL);
|
|
}
|
|
|
|
int script_context_init(qjs::Context &context)
|
|
{
|
|
try
|
|
{
|
|
js_init_module_os(context.ctx, "os");
|
|
js_init_module_std(context.ctx, "std");
|
|
js_std_add_helpers(context.ctx, 0, nullptr);
|
|
context.eval(qjs_require_module, "<require>", JS_EVAL_TYPE_MODULE);
|
|
auto &module = context.addModule("interUtils");
|
|
module.class_<qjs_fetch_Headers>("Headers")
|
|
.constructor<>()
|
|
.fun<&qjs_fetch_Headers::headers>("headers")
|
|
.fun<&qjs_fetch_Headers::append>("append")
|
|
.fun<&qjs_fetch_Headers::parse_from_string>("parse");
|
|
module.class_<qjs_fetch_Request>("Request")
|
|
.constructor<>()
|
|
.fun<&qjs_fetch_Request::method>("method")
|
|
.fun<&qjs_fetch_Request::url>("url")
|
|
.fun<&qjs_fetch_Request::proxy>("proxy")
|
|
.fun<&qjs_fetch_Request::postdata>("data")
|
|
.fun<&qjs_fetch_Request::headers>("headers")
|
|
.fun<&qjs_fetch_Request::cookies>("cookies");
|
|
module.class_<qjs_fetch_Response>("Response")
|
|
.constructor<>()
|
|
.fun<&qjs_fetch_Response::status_code>("code")
|
|
.fun<&qjs_fetch_Response::content>("data")
|
|
.fun<&qjs_fetch_Response::cookies>("cookies")
|
|
.fun<&qjs_fetch_Response::headers>("headers");
|
|
module.class_<Proxy>("Proxy")
|
|
.constructor<>()
|
|
.fun<&Proxy::Type>("Type")
|
|
.fun<&Proxy::Id>("Id")
|
|
.fun<&Proxy::GroupId>("GroupId")
|
|
.fun<&Proxy::Group>("Group")
|
|
.fun<&Proxy::Remark>("Remark")
|
|
.fun<&Proxy::Hostname>("Hostname")
|
|
.fun<&Proxy::Port>("Port")
|
|
.fun<&Proxy::Username>("Username")
|
|
.fun<&Proxy::Password>("Password")
|
|
.fun<&Proxy::EncryptMethod>("EncryptMethod")
|
|
.fun<&Proxy::Plugin>("Plugin")
|
|
.fun<&Proxy::PluginOption>("PluginOption")
|
|
.fun<&Proxy::Protocol>("Protocol")
|
|
.fun<&Proxy::ProtocolParam>("ProtocolParam")
|
|
.fun<&Proxy::OBFS>("OBFS")
|
|
.fun<&Proxy::OBFSParam>("OBFSParam")
|
|
.fun<&Proxy::UserId>("UserId")
|
|
.fun<&Proxy::AlterId>("AlterId")
|
|
.fun<&Proxy::TransferProtocol>("TransferProtocol")
|
|
.fun<&Proxy::FakeType>("FakeType")
|
|
.fun<&Proxy::TLSSecure>("TLSSecure")
|
|
.fun<&Proxy::Host>("Host")
|
|
.fun<&Proxy::Path>("Path")
|
|
.fun<&Proxy::Edge>("Edge")
|
|
.fun<&Proxy::QUICSecure>("QUICSecure")
|
|
.fun<&Proxy::QUICSecret>("QUICSecret")
|
|
.fun<&Proxy::UDP>("UDP")
|
|
.fun<&Proxy::TCPFastOpen>("TCPFastOpen")
|
|
.fun<&Proxy::AllowInsecure>("AllowInsecure")
|
|
.fun<&Proxy::TLS13>("TLS13")
|
|
.fun<&Proxy::SnellVersion>("SnellVersion")
|
|
.fun<&Proxy::ServerName>("ServerName")
|
|
.fun<&Proxy::SelfIP>("SelfIP")
|
|
.fun<&Proxy::SelfIPv6>("SelfIPv6")
|
|
.fun<&Proxy::PublicKey>("PublicKey")
|
|
.fun<&Proxy::PrivateKey>("PrivateKey")
|
|
.fun<&Proxy::PreSharedKey>("PreSharedKey")
|
|
.fun<&Proxy::DnsServers>("DnsServers")
|
|
.fun<&Proxy::Mtu>("Mtu")
|
|
.fun<&Proxy::AllowedIPs>("AllowedIPs")
|
|
.fun<&Proxy::KeepAlive>("KeepAlive")
|
|
.fun<&Proxy::TestUrl>("TestUrl")
|
|
.fun<&Proxy::ClientId>("ClientId");
|
|
context.global().add<&makeDataURI>("makeDataURI")
|
|
.add<&qjs_fetch>("fetch")
|
|
.add<&base64Encode>("atob")
|
|
.add<&base64Decode>("btoa")
|
|
.add<¤tTime>("time")
|
|
.add<&sleepMs>("sleep")
|
|
.add<&ShowMsgbox>("msgbox")
|
|
.add<&qjs_getUrlArg>("getUrlArg")
|
|
.add<&fileGet>("fileGet")
|
|
.add<&fileWrite>("fileWrite");
|
|
context.eval(R"(
|
|
import * as interUtils from 'interUtils'
|
|
globalThis.Request = interUtils.Request
|
|
globalThis.Response = interUtils.Response
|
|
globalThis.Headers = interUtils.Headers
|
|
globalThis.Proxy = interUtils.Proxy
|
|
import * as std from 'std'
|
|
import * as os from 'os'
|
|
globalThis.std = std
|
|
globalThis.os = os
|
|
import { require } from '<require>'
|
|
globalThis.require = require
|
|
)", "<import>", JS_EVAL_TYPE_MODULE);
|
|
return 0;
|
|
}
|
|
catch(qjs::exception)
|
|
{
|
|
script_print_stack(context);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int script_cleanup(qjs::Context &context)
|
|
{
|
|
js_std_loop(context.ctx);
|
|
js_std_free_handlers(JS_GetRuntime(context.ctx));
|
|
return 0;
|
|
}
|
|
|
|
void script_print_stack(qjs::Context &context)
|
|
{
|
|
auto exc = context.getException();
|
|
std::cerr << (std::string) exc << std::endl;
|
|
if((bool) exc["stack"])
|
|
std::cerr << (std::string) exc["stack"] << std::endl;
|
|
}
|