mirror of
https://github.com/Mahdi-zarei/nekoray.git
synced 2026-01-05 03:59:02 +08:00
feat: support xhttp transport
This commit is contained in:
parent
7a32e4144c
commit
2e0556ec81
@ -19,7 +19,7 @@ require (
|
||||
google.golang.org/protobuf v1.36.10
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing-box => github.com/throneproj/sing-box v1.11.16-0.20251027170654-efe4b5f5b1e4
|
||||
replace github.com/sagernet/sing-box => github.com/throneproj/sing-box v1.11.16-0.20251117211316-75fea0c5db6d
|
||||
|
||||
replace github.com/sagernet/wireguard-go => github.com/throneproj/wireguard-go v0.0.1-beta.7.0.20250728063157-408bba78ad26
|
||||
|
||||
@ -82,6 +82,7 @@ require (
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/prometheus-community/pro-bing v0.4.0 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.55.0 // indirect
|
||||
github.com/safchain/ethtool v0.3.0 // indirect
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect
|
||||
github.com/sagernet/cors v1.2.1 // indirect
|
||||
|
||||
@ -141,6 +141,8 @@ github.com/prometheus-community/pro-bing v0.4.0 h1:YMbv+i08gQz97OZZBwLyvmmQEEzyf
|
||||
github.com/prometheus-community/pro-bing v0.4.0/go.mod h1:b7wRYZtCcPmt4Sz319BykUU241rWLe1VFXyiyWK/dH4=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=
|
||||
github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
|
||||
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
|
||||
@ -216,8 +218,8 @@ github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
|
||||
github.com/tc-hib/winres v0.2.1/go.mod h1:C/JaNhH3KBvhNKVbvdlDWkbMDO9H4fKKDaN7/07SSuk=
|
||||
github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c=
|
||||
github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY=
|
||||
github.com/throneproj/sing-box v1.11.16-0.20251027170654-efe4b5f5b1e4 h1:tQumipJlxqgMXvbuOJR8qMU4HoM0qbcU6RPA0DeAV9M=
|
||||
github.com/throneproj/sing-box v1.11.16-0.20251027170654-efe4b5f5b1e4/go.mod h1:4hUwNgXeaqRWAuYxixxVBOEGRFIamyw12lrpx8hbZBc=
|
||||
github.com/throneproj/sing-box v1.11.16-0.20251117211316-75fea0c5db6d h1:GqxlDvZI+Fd/sQHywvxZ8Bz9Sh+leK5HZb6aEWQ4SJo=
|
||||
github.com/throneproj/sing-box v1.11.16-0.20251117211316-75fea0c5db6d/go.mod h1:o6kl5QH2V1yzOS0P95jVaVH7hv2sa5QURHs1Ha6O6No=
|
||||
github.com/throneproj/wireguard-go v0.0.1-beta.7.0.20250728063157-408bba78ad26 h1:bBzqh7xTshvPjTFz4URNj/xbPA/d0BOwUM2R83FEMGU=
|
||||
github.com/throneproj/wireguard-go v0.0.1-beta.7.0.20250728063157-408bba78ad26/go.mod h1:akc2Wh+rX9bFFNnHJGsQ8VIV3eJI1LXJYgx2Y+8lcW8=
|
||||
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
|
||||
@ -247,6 +249,8 @@ go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
|
||||
@ -23,6 +23,10 @@ namespace Configs
|
||||
// gRPC
|
||||
QString service_name;
|
||||
|
||||
// xhttp
|
||||
QString xhttp_mode;
|
||||
QString xhttp_extra;
|
||||
|
||||
Transport()
|
||||
{
|
||||
_add(new configItem("type", &type, string));
|
||||
@ -35,6 +39,8 @@ namespace Configs
|
||||
_add(new configItem("max_early_data", &max_early_data, integer));
|
||||
_add(new configItem("early_data_header_name", &early_data_header_name, string));
|
||||
_add(new configItem("service_name", &service_name, string));
|
||||
_add(new configItem("xhttp_mode", &xhttp_mode, string));
|
||||
_add(new configItem("xhttp_extra", &xhttp_extra, string));
|
||||
}
|
||||
|
||||
QString getHeadersString();
|
||||
|
||||
256
include/global/XhttpExtraConverter.hpp
Normal file
256
include/global/XhttpExtraConverter.hpp
Normal file
@ -0,0 +1,256 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QString>
|
||||
|
||||
class XhttpExtraConverter
|
||||
{
|
||||
public:
|
||||
static void mergeQJsonObject(QJsonObject &obj1, const QJsonObject &obj2)
|
||||
{
|
||||
for (auto it = obj2.constBegin(); it != obj2.constEnd(); it++) {
|
||||
obj1.insert(it.key(), it.value());
|
||||
}
|
||||
}
|
||||
|
||||
static QJsonObject xrayToSingBox(const QString &xrayExtra)
|
||||
{
|
||||
if (xrayExtra.trimmed().isEmpty()) return {};
|
||||
|
||||
QJsonParseError err{};
|
||||
const auto doc = QJsonDocument::fromJson(xrayExtra.toUtf8(), &err);
|
||||
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QJsonObject xray = doc.object();
|
||||
if (isSingBoxFormat(xray)) return xray;
|
||||
|
||||
QJsonObject singBox;
|
||||
|
||||
convertField(xray, singBox, "xPaddingBytes", "x_padding_bytes");
|
||||
convertField(xray, singBox, "scMaxEachPostBytes", "sc_max_each_post_bytes");
|
||||
convertField(xray, singBox, "scMinPostsIntervalMs", "sc_min_posts_interval_ms");
|
||||
convertField(xray, singBox, "noGRPCHeader", "no_grpc_header");
|
||||
|
||||
// xmux
|
||||
if (xray.contains("xmux") && xray["xmux"].isObject()) {
|
||||
singBox["xmux"] = convertXrayXmux(xray["xmux"].toObject());
|
||||
}
|
||||
|
||||
// downloadSettings → download
|
||||
if (xray.contains("downloadSettings") && xray["downloadSettings"].isObject()) {
|
||||
const QJsonObject xDown = xray["downloadSettings"].toObject();
|
||||
QJsonObject sDown;
|
||||
|
||||
// xhttpSettings (mode/host/path)
|
||||
if (xDown.contains("xhttpSettings") && xDown["xhttpSettings"].isObject()) {
|
||||
const QJsonObject xhttp = xDown["xhttpSettings"].toObject();
|
||||
convertField(xhttp, sDown, "mode", "mode");
|
||||
convertField(xhttp, sDown, "host", "host");
|
||||
convertField(xhttp, sDown, "path", "path");
|
||||
}
|
||||
|
||||
convertField(xDown, sDown, "address", "server");
|
||||
convertField(xDown, sDown, "port", "server_port");
|
||||
|
||||
// TLS / Reality
|
||||
if (xDown.contains("security") && xDown["security"].isString()) {
|
||||
QJsonObject tls{ {"enabled", true} };
|
||||
const QString security = xDown["security"].toString();
|
||||
|
||||
if (security == "tls") {
|
||||
if (xDown.contains("tlsSettings") && xDown["tlsSettings"].isObject()) {
|
||||
const QJsonObject tlsSet = xDown["tlsSettings"].toObject();
|
||||
convertField(tlsSet, tls, "serverName", "server_name");
|
||||
convertField(tlsSet, tls, "alpn", "alpn");
|
||||
convertField(tlsSet, tls, "allowInsecure", "insecure");
|
||||
|
||||
if (tlsSet.contains("fingerprint") && !tlsSet["fingerprint"].toString().isEmpty()) {
|
||||
QJsonObject utls{ {"enabled", true}, {"fingerprint", tlsSet["fingerprint"]} };
|
||||
tls["utls"] = utls;
|
||||
}
|
||||
}
|
||||
} else if (security == "reality") {
|
||||
if (xDown.contains("realitySettings") && xDown["realitySettings"].isObject()) {
|
||||
const QJsonObject realSet = xDown["realitySettings"].toObject();
|
||||
convertField(realSet, tls, "serverName", "server_name");
|
||||
|
||||
QJsonObject reality{ {"enabled", true} };
|
||||
convertField(realSet, reality, "publicKey", "public_key");
|
||||
convertField(realSet, reality, "shortId", "short_id");
|
||||
tls["reality"] = reality;
|
||||
|
||||
if (realSet.contains("fingerprint") && !realSet["fingerprint"].toString().isEmpty()) {
|
||||
QJsonObject utls{ {"enabled", true}, {"fingerprint", realSet["fingerprint"]} };
|
||||
tls["utls"] = utls;
|
||||
}
|
||||
}
|
||||
}
|
||||
sDown["tls"] = tls;
|
||||
}
|
||||
|
||||
// downloadSettings.xhttpSettings.extra → download + xmux
|
||||
if (xDown.contains("xhttpSettings") && xDown["xhttpSettings"].isObject()) {
|
||||
const QJsonObject xhttp = xDown["xhttpSettings"].toObject();
|
||||
if (xhttp.contains("extra") && xhttp["extra"].isObject()) {
|
||||
const QJsonObject extra = xhttp["extra"].toObject();
|
||||
|
||||
convertField(extra, sDown, "xPaddingBytes", "x_padding_bytes");
|
||||
convertField(extra, sDown, "scMaxEachPostBytes", "sc_max_each_post_bytes");
|
||||
convertField(extra, sDown, "scMinPostsIntervalMs", "sc_min_posts_interval_ms");
|
||||
convertField(extra, sDown, "noGRPCHeader", "no_grpc_header");
|
||||
|
||||
if (extra.contains("xmux") && extra["xmux"].isObject()) {
|
||||
sDown["xmux"] = convertXrayXmux(extra["xmux"].toObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sDown.isEmpty()) {
|
||||
singBox["download"] = sDown;
|
||||
}
|
||||
}
|
||||
|
||||
return singBox;
|
||||
}
|
||||
|
||||
static QString singBoxToXray(const QJsonObject &sing)
|
||||
{
|
||||
if (isXrayFormat(sing)) return QJsonDocument(sing).toJson(QJsonDocument::Indented).replace("\\/", "/");
|
||||
|
||||
QJsonObject xray;
|
||||
|
||||
convertField(sing, xray, "x_padding_bytes", "xPaddingBytes");
|
||||
convertField(sing, xray, "sc_max_each_post_bytes", "scMaxEachPostBytes");
|
||||
convertField(sing, xray, "sc_min_posts_interval_ms", "scMinPostsIntervalMs");
|
||||
convertField(sing, xray, "no_grpc_header", "noGRPCHeader");
|
||||
|
||||
if (sing.contains("xmux") && sing["xmux"].isObject()) {
|
||||
xray["xmux"] = convertSingBoxXmux(sing["xmux"].toObject());
|
||||
}
|
||||
|
||||
// download → downloadSettings
|
||||
if (sing.contains("download") && sing["download"].isObject()) {
|
||||
const QJsonObject sDown = sing["download"].toObject();
|
||||
QJsonObject xDown;
|
||||
|
||||
convertField(sDown, xDown, "server", "address");
|
||||
convertField(sDown, xDown, "server_port", "port");
|
||||
xDown["network"] = "xhttp";
|
||||
|
||||
// TLS / Reality
|
||||
if (sDown.contains("tls") && sDown["tls"].isObject()) {
|
||||
const QJsonObject tls = sDown["tls"].toObject();
|
||||
|
||||
if (tls.contains("reality") && tls["reality"].isObject() &&
|
||||
tls["reality"].toObject().value("enabled").toBool(false)) {
|
||||
|
||||
xDown["security"] = "reality";
|
||||
QJsonObject realitySettings;
|
||||
convertField(tls, realitySettings, "server_name", "serverName");
|
||||
|
||||
const QJsonObject reality = tls["reality"].toObject();
|
||||
convertField(reality, realitySettings, "public_key", "publicKey");
|
||||
convertField(reality, realitySettings, "short_id", "shortId");
|
||||
|
||||
if (tls.contains("utls") && tls["utls"].isObject()) {
|
||||
convertField(tls["utls"].toObject(), realitySettings, "fingerprint", "fingerprint");
|
||||
}
|
||||
xDown["realitySettings"] = realitySettings;
|
||||
} else {
|
||||
xDown["security"] = "tls";
|
||||
QJsonObject tlsSettings;
|
||||
convertField(tls, tlsSettings, "server_name", "serverName");
|
||||
convertField(tls, tlsSettings, "alpn", "alpn");
|
||||
convertField(tls, tlsSettings, "insecure", "allowInsecure");
|
||||
|
||||
if (tls.contains("utls") && tls["utls"].isObject()) {
|
||||
convertField(tls["utls"].toObject(), tlsSettings, "fingerprint", "fingerprint");
|
||||
}
|
||||
xDown["tlsSettings"] = tlsSettings;
|
||||
}
|
||||
}
|
||||
|
||||
// mode/host/path + extra
|
||||
QJsonObject xhttpSettings;
|
||||
convertField(sDown, xhttpSettings, "mode", "mode");
|
||||
convertField(sDown, xhttpSettings, "host", "host");
|
||||
convertField(sDown, xhttpSettings, "path", "path");
|
||||
|
||||
QJsonObject xhttpExtra;
|
||||
convertField(sDown, xhttpExtra, "x_padding_bytes", "xPaddingBytes");
|
||||
convertField(sDown, xhttpExtra, "sc_max_each_post_bytes", "scMaxEachPostBytes");
|
||||
convertField(sDown, xhttpExtra, "sc_min_posts_interval_ms", "scMinPostsIntervalMs");
|
||||
convertField(sDown, xhttpExtra, "no_grpc_header", "noGRPCHeader");
|
||||
|
||||
if (sDown.contains("xmux") && sDown["xmux"].isObject()) {
|
||||
xhttpExtra["xmux"] = convertSingBoxXmux(sDown["xmux"].toObject());
|
||||
}
|
||||
|
||||
if (!xhttpExtra.isEmpty()) {
|
||||
xhttpSettings["extra"] = xhttpExtra;
|
||||
}
|
||||
xDown["xhttpSettings"] = xhttpSettings;
|
||||
|
||||
if (!xDown.isEmpty()) {
|
||||
xray["downloadSettings"] = xDown;
|
||||
}
|
||||
}
|
||||
|
||||
return QJsonDocument(xray).toJson(QJsonDocument::Indented).replace("\\/", "/");
|
||||
}
|
||||
|
||||
private:
|
||||
static bool isSingBoxFormat(const QJsonObject &json)
|
||||
{
|
||||
return json.contains("x_padding_bytes") ||
|
||||
json.contains("sc_max_each_post_bytes") ||
|
||||
json.contains("sc_min_posts_interval_ms") ||
|
||||
json.contains("sc_stream_up_server_secs") ||
|
||||
json.contains("download");
|
||||
}
|
||||
|
||||
static bool isXrayFormat(const QJsonObject &json)
|
||||
{
|
||||
return json.contains("xPaddingBytes") ||
|
||||
json.contains("scMaxEachPostBytes") ||
|
||||
json.contains("scMinPostsIntervalMs") ||
|
||||
json.contains("scStreamUpServerSecs") ||
|
||||
json.contains("downloadSettings");
|
||||
}
|
||||
|
||||
static void convertField(const QJsonObject &from, QJsonObject &to,
|
||||
const QString &fromKey, const QString &toKey)
|
||||
{
|
||||
if (from.contains(fromKey)) {
|
||||
to[toKey] = from[fromKey];
|
||||
}
|
||||
}
|
||||
|
||||
static QJsonObject convertXrayXmux(const QJsonObject &xrayXmux)
|
||||
{
|
||||
QJsonObject sing;
|
||||
convertField(xrayXmux, sing, "maxConcurrency", "max_concurrency");
|
||||
convertField(xrayXmux, sing, "maxConnections", "max_connections");
|
||||
convertField(xrayXmux, sing, "cMaxReuseTimes", "c_max_reuse_times");
|
||||
convertField(xrayXmux, sing, "hMaxRequestTimes", "h_max_request_times");
|
||||
convertField(xrayXmux, sing, "hMaxReusableSecs", "h_max_reusable_secs");
|
||||
convertField(xrayXmux, sing, "hKeepAlivePeriod", "h_keep_alive_period");
|
||||
return sing;
|
||||
}
|
||||
|
||||
static QJsonObject convertSingBoxXmux(const QJsonObject &singXmux)
|
||||
{
|
||||
QJsonObject xray;
|
||||
convertField(singXmux, xray, "max_concurrency", "maxConcurrency");
|
||||
convertField(singXmux, xray, "max_connections", "maxConnections");
|
||||
convertField(singXmux, xray, "c_max_reuse_times", "cMaxReuseTimes");
|
||||
convertField(singXmux, xray, "h_max_request_times","hMaxRequestTimes");
|
||||
convertField(singXmux, xray, "h_max_reusable_secs","hMaxReusableSecs");
|
||||
convertField(singXmux, xray, "h_keep_alive_period","hKeepAlivePeriod");
|
||||
return xray;
|
||||
}
|
||||
};
|
||||
@ -247,6 +247,11 @@
|
||||
<string notr="true">quic</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">xhttp</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -434,6 +439,47 @@
|
||||
<item row="5" column="1">
|
||||
<widget class="MyLineEdit" name="ws_early_data_name"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="xhttp_mode_l">
|
||||
<property name="text">
|
||||
<string notr="true">Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="xhttp_mode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">auto</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">packet-up</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">stream-up</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">stream-one</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="xhttp_extra_l">
|
||||
<property name="text">
|
||||
<string notr="true">Extra</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="MyLineEdit" name="xhttp_extra"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -713,6 +759,8 @@
|
||||
<tabstop>host</tabstop>
|
||||
<tabstop>ws_early_data_length</tabstop>
|
||||
<tabstop>ws_early_data_name</tabstop>
|
||||
<tabstop>xhttp_mode</tabstop>
|
||||
<tabstop>xhttp_extra</tabstop>
|
||||
<tabstop>insecure</tabstop>
|
||||
<tabstop>certificate_edit</tabstop>
|
||||
<tabstop>sni</tabstop>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <QJsonArray>
|
||||
#include <QUrlQuery>
|
||||
#include <include/global/Utils.hpp>
|
||||
#include <include/global/XhttpExtraConverter.hpp>
|
||||
|
||||
#include "include/configs/common/utils.h"
|
||||
|
||||
@ -81,6 +82,8 @@ namespace Configs {
|
||||
if (query.hasQueryItem("max_early_data")) max_early_data = query.queryItemValue("max_early_data").toInt();
|
||||
if (query.hasQueryItem("early_data_header_name")) early_data_header_name = query.queryItemValue("early_data_header_name");
|
||||
if (query.hasQueryItem("serviceName")) service_name = query.queryItemValue("serviceName");
|
||||
if (query.hasQueryItem("mode")) xhttp_mode = query.queryItemValue("mode");
|
||||
if (query.hasQueryItem("extra")) xhttp_extra = query.queryItemValue("extra");
|
||||
return true;
|
||||
}
|
||||
bool Transport::ParseFromJson(const QJsonObject& object)
|
||||
@ -98,6 +101,10 @@ namespace Configs {
|
||||
if (object.contains("max_early_data")) max_early_data = object["max_early_data"].toInt();
|
||||
if (object.contains("early_data_header_name")) early_data_header_name = object["early_data_header_name"].toString();
|
||||
if (object.contains("service_name")) service_name = object["service_name"].toString();
|
||||
if (object.contains("mode")) {
|
||||
xhttp_mode = object["mode"].toString();
|
||||
xhttp_extra = XhttpExtraConverter::singBoxToXray(object);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
QString Transport::ExportToLink()
|
||||
@ -113,6 +120,8 @@ namespace Configs {
|
||||
if (max_early_data > 0) query.addQueryItem("max_early_data", QString::number(max_early_data));
|
||||
if (!early_data_header_name.isEmpty()) query.addQueryItem("early_data_header_name", early_data_header_name);
|
||||
if (!service_name.isEmpty()) query.addQueryItem("serviceName", service_name);
|
||||
if (!xhttp_mode.isEmpty()) query.addQueryItem("mode", xhttp_mode);
|
||||
if (!xhttp_extra.isEmpty()) query.addQueryItem("extra", xhttp_extra);
|
||||
return query.toString();
|
||||
}
|
||||
QJsonObject Transport::ExportToJson()
|
||||
@ -130,6 +139,8 @@ namespace Configs {
|
||||
if (max_early_data > 0) object["max_early_data"] = max_early_data;
|
||||
if (!early_data_header_name.isEmpty()) object["early_data_header_name"] = early_data_header_name;
|
||||
if (!service_name.isEmpty()) object["service_name"] = service_name;
|
||||
if (!xhttp_mode.isEmpty()) object["mode"] = xhttp_mode;
|
||||
if (!xhttp_extra.isEmpty()) XhttpExtraConverter::mergeQJsonObject(object, XhttpExtraConverter::xrayToSingBox(xhttp_extra));
|
||||
return object;
|
||||
}
|
||||
BuildResult Transport::Build()
|
||||
|
||||
@ -66,6 +66,15 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
|
||||
ui->path_l->setVisible(true);
|
||||
ui->host->setVisible(true);
|
||||
ui->host_l->setVisible(true);
|
||||
} else if (txt == "xhttp") {
|
||||
ui->headers->setVisible(false);
|
||||
ui->headers_l->setVisible(false);
|
||||
ui->method->setVisible(false);
|
||||
ui->method_l->setVisible(false);
|
||||
ui->path->setVisible(true);
|
||||
ui->path_l->setVisible(true);
|
||||
ui->host->setVisible(true);
|
||||
ui->host_l->setVisible(true);
|
||||
} else {
|
||||
ui->headers->setVisible(false);
|
||||
ui->headers_l->setVisible(false);
|
||||
@ -87,6 +96,17 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
|
||||
ui->ws_early_data_name->setVisible(false);
|
||||
ui->ws_early_data_name_l->setVisible(false);
|
||||
}
|
||||
if (txt == "xhttp") {
|
||||
ui->xhttp_mode->setVisible(true);
|
||||
ui->xhttp_mode_l->setVisible(true);
|
||||
ui->xhttp_extra->setVisible(true);
|
||||
ui->xhttp_extra_l->setVisible(true);
|
||||
} else {
|
||||
ui->xhttp_mode->setVisible(false);
|
||||
ui->xhttp_mode_l->setVisible(false);
|
||||
ui->xhttp_extra->setVisible(false);
|
||||
ui->xhttp_extra_l->setVisible(false);
|
||||
}
|
||||
if (!ui->utlsFingerprint->count()) ui->utlsFingerprint->addItems(Preset::SingBox::UtlsFingerPrint);
|
||||
int networkBoxVisible = 0;
|
||||
for (auto label: ui->network_box->findChildren<QLabel *>()) {
|
||||
@ -302,6 +322,8 @@ void DialogEditProfile::typeSelected(const QString &newType) {
|
||||
ui->headers->setText(transport->getHeadersString());
|
||||
ui->ws_early_data_name->setText(transport->early_data_header_name);
|
||||
ui->ws_early_data_length->setText(Int2String(transport->max_early_data));
|
||||
ui->xhttp_mode->setCurrentText(transport->xhttp_mode);
|
||||
ui->xhttp_extra->setText(transport->xhttp_extra);
|
||||
ui->reality_pbk->setText(tls->reality->public_key);
|
||||
ui->reality_sid->setText(tls->reality->short_id);
|
||||
CACHE.certificate = tls->certificate;
|
||||
@ -412,6 +434,8 @@ bool DialogEditProfile::onEnd() {
|
||||
transport->method = ui->method->text();
|
||||
transport->early_data_header_name = ui->ws_early_data_name->text();
|
||||
transport->max_early_data = ui->ws_early_data_length->text().toInt();
|
||||
transport->xhttp_mode = ui->xhttp_mode->currentText();
|
||||
transport->xhttp_extra = ui->xhttp_extra->text();
|
||||
tls->reality->public_key = ui->reality_pbk->text();
|
||||
tls->reality->short_id = ui->reality_sid->text();
|
||||
tls->certificate = CACHE.certificate;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user