Skip to content

Commit

Permalink
feat: hook.js
Browse files Browse the repository at this point in the history
  • Loading branch information
arm64v8a committed Dec 4, 2022
1 parent 348ccf0 commit 4bbbd45
Show file tree
Hide file tree
Showing 13 changed files with 247 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ tags
.DS_Store
.directory
*.debug
Makefile*
/Makefile*
*.prl
*.app
moc_*.cpp
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "3rdparty/QHotkey"]
path = 3rdparty/QHotkey
url = https://github.com/Skycoder42/QHotkey.git
[submodule "3rdparty/qjs"]
path = 3rdparty/qjs
url = https://github.com/MatsuriDayo/qjs
1 change: 1 addition & 0 deletions 3rdparty/qjs
Submodule qjs added at 578534
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ if (NKR_NO_EXTERNAL)
set(NKR_NO_YAML 1)
set(NKR_NO_ZXING 1)
set(NKR_NO_QHOTKEY 1)
set(NKR_NO_QUICKJS 1)
endif ()

# quickjs (static submodule)
if (NKR_NO_QUICKJS)
nkr_add_compile_definitions(NKR_NO_QUICKJS)
else ()
add_subdirectory(3rdparty/qjs)
list(APPEND NKR_EXTERNAL_TARGETS quickjs)
endif ()

# grpc
Expand Down Expand Up @@ -124,6 +133,7 @@ set(PROJECT_SOURCES
main/main.cpp
main/NekoRay.cpp
main/NekoRay_Utils.cpp
main/QJS.cpp

3rdparty/base64.cpp
3rdparty/qrcodegen.cpp
Expand Down
41 changes: 39 additions & 2 deletions db/ConfigBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "db/Database.hpp"
#include "fmt/includes.h"
#include "fmt/Preset.hpp"
#include "main/QJS.hpp"

#include <QApplication>
#include <QFile>
Expand All @@ -12,10 +13,26 @@ namespace NekoRay {
// Common

QSharedPointer<BuildConfigResult> BuildConfig(const QSharedPointer<ProxyEntity> &ent, bool forTest, bool forExport) {
QSharedPointer<BuildConfigResult> result;
if (IS_NEKO_BOX) {
return BuildConfigSingBox(ent, forTest, forExport);
result = BuildConfigSingBox(ent, forTest, forExport);
} else {
result = BuildConfigV2Ray(ent, forTest, forExport);
}
// hook.js
if (result->error.isEmpty()) {
auto source = qjs::ReadHookJS();
if (!source.isEmpty()) {
qjs::QJS js(source);
auto js_result = js.EvalFunction("hook.hook_core_config", QJsonObject2QString(result->coreConfig, true));
auto js_result_json = QString2QJsonObject(js_result);
if (!js_result_json.isEmpty() && result->coreConfig != js_result_json) {
MW_show_log("hook.js modified your " + software_core_name + " json config.");
result->coreConfig = js_result_json;
}
}
}
return BuildConfigV2Ray(ent, forTest, forExport);
return result;
}

QString BuildChain(int chainId, const QSharedPointer<BuildConfigStatus> &status) {
Expand Down Expand Up @@ -878,6 +895,16 @@ namespace NekoRay {
.replace("%TUN_NAME%", tun_name)
.replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false")
.replace("%PORT%", Int2String(dataStore->inbound_socks_port));
// hook.js
auto source = qjs::ReadHookJS();
if (!source.isEmpty()) {
qjs::QJS js(source);
auto js_result = js.EvalFunction("hook.hook_vpn_config", config);
if (config != js_result) {
MW_show_log("hook.js modified your VPN config.");
config = js_result;
}
}
// write config
QFile file;
file.setFileName(QFileInfo(configFn).fileName());
Expand All @@ -897,6 +924,16 @@ namespace NekoRay {
.replace("$PROTECT_LISTEN_PATH", protectPath)
.replace("$CONFIG_PATH", configPath)
.replace("$TABLE_FWMARK", "514");
// hook.js
auto source = qjs::ReadHookJS();
if (!source.isEmpty()) {
qjs::QJS js(source);
auto js_result = js.EvalFunction("hook.hook_vpn_script", script);
if (script != js_result) {
MW_show_log("hook.js modified your VPN script.");
script = js_result;
}
}
// write script
QFile file2;
file2.setFileName(QFileInfo(scriptFn).fileName());
Expand Down
1 change: 1 addition & 0 deletions main/NekoRay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ namespace NekoRay {
_add(new configItem("sp_format", &system_proxy_format, itemType::string));
_add(new configItem("sub_clear", &sub_clear, itemType::boolean));
_add(new configItem("sub_insecure", &sub_insecure, itemType::boolean));
_add(new configItem("enable_js_hook", &enable_js_hook, itemType::boolean));
}

void DataStore::UpdateStartedId(int id) {
Expand Down
1 change: 1 addition & 0 deletions main/NekoRay_DataStore.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ namespace NekoRay {
// Security
bool insecure_hint = true;
bool skip_cert = false;
bool enable_js_hook = false;

// Remember
int remember_spmode = NekoRay::SystemProxyMode::DISABLE;
Expand Down
143 changes: 143 additions & 0 deletions main/QJS.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#include "QJS.hpp"

#include "3rdparty/qjs/nekoray_qjs.h"
#include "main/NekoRay.hpp"

namespace NekoRay::qjs {
#ifndef NKR_NO_QUICKJS
namespace exception {
static void js_dump_obj(JSContext *ctx, QString &out, JSValueConst val) {
const char *str;

str = JS_ToCString(ctx, val);
if (str) {
out.append(str);
out.append('\n');
JS_FreeCString(ctx, str);
} else {
out += "[exception]\n";
}
}

static void js_std_dump_error1(JSContext *ctx, QString &out, JSValueConst exception_val) {
JSValue val;
auto is_error = JS_IsError(ctx, exception_val);
js_dump_obj(ctx, out, exception_val);
if (is_error) {
val = JS_GetPropertyStr(ctx, exception_val, "stack");
if (!JS_IsUndefined(val)) {
js_dump_obj(ctx, out, val);
}
JS_FreeValue(ctx, val);
}
}

QString js_std_dump_error(JSContext *ctx) {
QString result;
JSValue exception_val;

exception_val = JS_GetException(ctx);
js_std_dump_error1(ctx, result, exception_val);
JS_FreeValue(ctx, exception_val);

return result;
}
} // namespace exception

JSValue func_log(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
QString qString;

int i;
const char *str;
size_t len;

for (i = 0; i < argc; i++) {
if (i != 0) qString.append(' ');
str = JS_ToCStringLen(ctx, &len, argv[i]);
if (!str)
return JS_EXCEPTION;
qString.append(str);
JS_FreeCString(ctx, str);
}

MW_show_log(qString);
qDebug() << "func_log:" << qString;

return JS_UNDEFINED;
}
#endif

#define NEKO_CTX ((nekoray_qjs_context *) this->neko_ctx)

QJS::QJS() {
#ifndef NKR_NO_QUICKJS
MW_show_log("loading quickjs......");
//
this->neko_ctx = malloc(sizeof(nekoray_qjs_context));
nekoray_qjs_new_arg arg;
arg.neko_ctx = NEKO_CTX;
arg.func_log = func_log;
nekoray_qjs_new(arg);
#endif
}

QJS::QJS(const QByteArray &jsSource) : QJS() {
this->Eval(jsSource);
}

QJS::~QJS() {
#ifndef NKR_NO_QUICKJS
nekoray_qjs_free(NEKO_CTX);
free(this->neko_ctx);
#endif
}

QString QJS::Eval(const QByteArray &jsSource) const {
#ifndef NKR_NO_QUICKJS
auto result = nekoray_qjs_eval(NEKO_CTX, jsSource.data(), jsSource.length());
if (JS_IsException(result)) {
MW_show_log(exception::js_std_dump_error(NEKO_CTX->ctx));
return {};
}
auto cString = JS_ToCString(NEKO_CTX->ctx, result);
QString qString(cString);
JS_FreeCString(NEKO_CTX->ctx, cString);
JS_FreeValue(NEKO_CTX->ctx, result);
return qString;
#else
return {};
#endif
}

QString QJS::Eval(const QString &jsSource) const {
return this->Eval(jsSource.toUtf8());
}

QString QJS::EvalFile(const QString &jsPath) const {
return this->Eval(ReadFile(jsPath));
}

QString QJS::EvalFunction(const QString &funcName, const QString &arg) const {
#ifndef NKR_NO_QUICKJS
auto ba1 = arg.toUtf8();
JSValue globalObj = JS_GetGlobalObject(NEKO_CTX->ctx);
JSValue tempObj = JS_NewStringLen(NEKO_CTX->ctx, ba1.data(), ba1.length());
JS_SetPropertyStr(NEKO_CTX->ctx, globalObj, "tempObj", tempObj);
auto result = this->Eval(QString("%1(tempObj)").arg(funcName));
JS_DeleteProperty(NEKO_CTX->ctx, globalObj, JS_NewAtom(NEKO_CTX->ctx, "tempObj"), 1); // Free tempObj
JS_FreeValue(NEKO_CTX->ctx, globalObj);
return result;
#else
return {};
#endif
}

QByteArray ReadHookJS() {
#ifndef NKR_NO_QUICKJS
if (NekoRay::dataStore->enable_js_hook) {
return ReadFile(QString("./hook.%1.js").arg(software_name.toLower()));
}
#endif
return {};
}
} // namespace NekoRay::qjs
23 changes: 23 additions & 0 deletions main/QJS.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

class QByteArray;
class QString;

namespace NekoRay::qjs {
class QJS {
public:
QJS();
explicit QJS(const QByteArray &jsSource);
~QJS();

QString Eval(const QByteArray &jsSource) const;
QString Eval(const QString &jsSource) const;
QString EvalFile(const QString &jsPath) const;
QString EvalFunction(const QString &funcName, const QString &arg) const;

private:
void *neko_ctx;
};

QByteArray ReadHookJS();
} // namespace NekoRay::qjs
12 changes: 12 additions & 0 deletions sub/GroupUpdater.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "db/ProfileFilter.hpp"
#include "fmt/includes.h"
#include "fmt/Preset.hpp"
#include "main/QJS.hpp"

#include "GroupUpdater.hpp"

Expand Down Expand Up @@ -394,6 +395,17 @@ namespace NekoRay::sub {
}
}

// hook.js
auto source = qjs::ReadHookJS();
if (!source.isEmpty()) {
qjs::QJS js(source);
auto js_result = js.EvalFunction("hook.hook_import", content);
if (content != js_result) {
MW_show_log("hook.js modified your import content.");
content = js_result;
}
}

// 解析并添加 profile
rawUpdater->update(content);

Expand Down
4 changes: 4 additions & 0 deletions translations/zh_CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@
<source>Ignore TLS errors when updating subscription</source>
<translation>更新订阅时忽略 TLS 错误</translation>
</message>
<message>
<source>Enable hook.js</source>
<translation>启用 hook.js 功能</translation>
</message>
</context>
<context>
<name>DialogEditGroup</name>
Expand Down
2 changes: 2 additions & 0 deletions ui/dialog_basic_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ DialogBasicSettings::DialogBasicSettings(QWidget *parent)

D_LOAD_BOOL(insecure_hint)
D_LOAD_BOOL(skip_cert)
D_LOAD_BOOL(enable_js_hook)
}

DialogBasicSettings::~DialogBasicSettings() {
Expand Down Expand Up @@ -278,6 +279,7 @@ void DialogBasicSettings::accept() {

D_SAVE_BOOL(insecure_hint)
D_SAVE_BOOL(skip_cert)
D_SAVE_BOOL(enable_js_hook)

MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore");
QDialog::accept();
Expand Down
7 changes: 7 additions & 0 deletions ui/dialog_basic_settings.ui
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enable_js_hook">
<property name="text">
<string>Enable hook.js</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
Expand Down

0 comments on commit 4bbbd45

Please sign in to comment.