diff --git a/fmt/Bean2CoreObj.cpp b/fmt/Bean2CoreObj.cpp index c3c92a1..d447eb1 100644 --- a/fmt/Bean2CoreObj.cpp +++ b/fmt/Bean2CoreObj.cpp @@ -17,18 +17,23 @@ namespace NekoRay::fmt { if (network == "ws") { QJsonObject ws; - if (!path.trimmed().isEmpty()) ws["path"] = path; - if (!host.trimmed().isEmpty()) ws["headers"] = QJsonObject{{"Host", host}}; + if (!path.isEmpty()) ws["path"] = path; + if (!host.isEmpty()) ws["headers"] = QJsonObject{{"Host", host}}; streamSettings["wsSettings"] = ws; } else if (network == "h2") { QJsonObject h2; - if (!path.trimmed().isEmpty()) h2["path"] = path; - if (!host.trimmed().isEmpty()) h2["host"] = QList2QJsonArray(host.split(",")); + if (!path.isEmpty()) h2["path"] = path; + if (!host.isEmpty()) h2["host"] = QList2QJsonArray(host.split(",")); streamSettings["httpSettings"] = h2; } else if (network == "grpc") { QJsonObject grpc; - if (!path.trimmed().isEmpty()) grpc["serviceName"] = path; + if (!path.isEmpty()) grpc["serviceName"] = path; streamSettings["grpcSettings"] = grpc; + } else if (network == "quic") { + QJsonObject quic; + if (!path.isEmpty()) quic["key"] = path; + if (!host.isEmpty()) quic["security"] = host; + streamSettings["quicSettings"] = quic; } if (security == "tls") { @@ -48,6 +53,18 @@ namespace NekoRay::fmt { streamSettings["tlsSettings"] = tls; } + if (!header_type.isEmpty()) { + QJsonObject header{{"type", header_type}}; + if (header_type == "http") { + QJsonObject request{ + {"path", QList2QJsonArray(path.split(","))}, + {"headers", QJsonObject{{"Host", QList2QJsonArray(host.split(","))}}}, + }; + header["request"] = request; + } + streamSettings["header"] = header; + } + return streamSettings; } diff --git a/fmt/Bean2Link.cpp b/fmt/Bean2Link.cpp index d15a0b2..03f63c5 100644 --- a/fmt/Bean2Link.cpp +++ b/fmt/Bean2Link.cpp @@ -62,18 +62,17 @@ namespace NekoRay::fmt { QString VMessBean::ToShareLink() { QJsonObject N{ - {"v", 2}, + {"v", "2"}, {"ps", name}, {"add", serverAddress}, - {"port", serverPort}, + {"port", Int2String(serverPort)}, {"id", uuid}, - {"aid", aid}, + {"aid", Int2String(aid)}, {"net", stream->network}, {"host", stream->host}, {"path", stream->path}, {"type", stream->header_type}, {"scy", security}, - // TODO header type {"tls", stream->security == "tls" ? "tls" : ""}, {"sni", stream->sni}, }; diff --git a/fmt/Link2Bean.cpp b/fmt/Link2Bean.cpp index 8f6f574..905f0e2 100644 --- a/fmt/Link2Bean.cpp +++ b/fmt/Link2Bean.cpp @@ -113,11 +113,11 @@ namespace NekoRay::fmt { serverPort = objN["port"].toVariant().toInt(); // OPTIONAL name = objN["ps"].toString(); - auto aid_ = objN["aid"]; - aid = aid_.isString() ? aid_.toString().toInt() : aid_.toInt(); + aid = objN["aid"].toVariant().toInt(); stream->host = objN["host"].toString(); stream->path = objN["path"].toString(); stream->sni = objN["sni"].toString(); + stream->header_type = objN["type"].toString(); auto net = objN["net"].toString().replace("http", "h2"); if (!net.isEmpty()) stream->network = net; auto scy = objN["scy"].toString(); diff --git a/fmt/V2RayStreamSettings.hpp b/fmt/V2RayStreamSettings.hpp index e40f463..688cde7 100644 --- a/fmt/V2RayStreamSettings.hpp +++ b/fmt/V2RayStreamSettings.hpp @@ -8,10 +8,10 @@ namespace NekoRay::fmt { QString network = "tcp"; QString security = "none"; QString packet_encoding = ""; - // ws/h2/grpc + // ws/h2/grpc/tcp-http QString path = ""; QString host = ""; - // QUIC & KCP + // kcp/quic/tcp-http QString header_type = ""; // tls QString sni = ""; diff --git a/sub/GroupUpdater.cpp b/sub/GroupUpdater.cpp index cf34b63..11e2acd 100644 --- a/sub/GroupUpdater.cpp +++ b/sub/GroupUpdater.cpp @@ -164,6 +164,15 @@ namespace NekoRay::sub { } } + // NodeChild returns the first defined children or Null Node + YAML::Node NodeChild(const YAML::Node &n, const std::list &keys) { + for (const auto &key: keys) { + auto child = n[key]; + if (child.IsDefined()) return child; + } + return {}; + } + #endif // https://github.com/Dreamacro/clash/wiki/configuration @@ -223,7 +232,7 @@ namespace NekoRay::sub { if (Node2Bool(proxy["tls"])) bean->stream->security = "tls"; if (Node2Bool(proxy["skip-cert-verify"])) bean->stream->allow_insecure = true; - auto ws = proxy["ws-opts"]; + auto ws = NodeChild(proxy, {"ws-opts", "ws-opt"}); if (ws.IsMap()) { auto headers = ws["headers"]; for (auto header: headers) { @@ -234,12 +243,12 @@ namespace NekoRay::sub { bean->stream->path = Node2QString(ws["path"]); } - auto grpc = proxy["grpc-opts"]; + auto grpc = NodeChild(proxy, {"grpc-opts", "grpc-opt"}); if (grpc.IsMap()) { bean->stream->path = Node2QString(grpc["grpc-service-name"]); } - auto h2 = proxy["h2-opts"]; + auto h2 = NodeChild(proxy, {"h2-opts", "h2-opt"}); if (h2.IsMap()) { auto hosts = ws["host"]; for (auto host: hosts) { @@ -248,6 +257,24 @@ namespace NekoRay::sub { } bean->stream->path = Node2QString(h2["path"]); } + + auto tcp_http = NodeChild(proxy, {"http-opts", "http-opt"}); + if (tcp_http.IsMap()) { + bean->stream->network = "tcp"; + bean->stream->header_type = "http"; + auto headers = tcp_http["headers"]; + for (auto header: headers) { + if (Node2QString(header.first).toLower() == "host") { + bean->stream->host = Node2QString(header.second[0]); + } + break; + } + auto paths = tcp_http["path"]; + for (auto path: paths) { + bean->stream->path = Node2QString(path); + break; + } + } } else { continue; } diff --git a/ui/edit/dialog_edit_profile.cpp b/ui/edit/dialog_edit_profile.cpp index 46783c0..1ea7700 100644 --- a/ui/edit/dialog_edit_profile.cpp +++ b/ui/edit/dialog_edit_profile.cpp @@ -31,18 +31,35 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, // network changed network_title_base = ui->network_box->title(); connect(ui->network, &QComboBox::currentTextChanged, this, [=](const QString &txt) { + ui->network_box->setTitle(network_title_base.arg(txt)); if (txt == "tcp" || txt == "quic") { - ui->network_box->setVisible(false); + ui->header_type->setVisible(true); + ui->header_type_l->setVisible(true); + ui->path->setVisible(true); + ui->path_l->setVisible(true); + ui->host->setVisible(true); + ui->host_l->setVisible(true); + } else if (txt == "grpc") { + ui->header_type->setVisible(false); + ui->header_type_l->setVisible(false); + ui->path->setVisible(true); + ui->path_l->setVisible(true); + ui->host->setVisible(false); + ui->host_l->setVisible(false); + } else if (txt == "ws" || txt == "h2") { + ui->header_type->setVisible(false); + ui->header_type_l->setVisible(false); + ui->path->setVisible(true); + ui->path_l->setVisible(true); + ui->host->setVisible(true); + ui->host_l->setVisible(true); } else { - ui->network_box->setVisible(true); - ui->network_box->setTitle(network_title_base.arg(txt)); - if (txt == "grpc") { - ui->host->setVisible(false); - ui->host_l->setVisible(false); - } else { - ui->host->setVisible(true); - ui->host_l->setVisible(true); - } + ui->header_type->setVisible(false); + ui->header_type_l->setVisible(false); + ui->path->setVisible(false); + ui->path_l->setVisible(false); + ui->host->setVisible(false); + ui->host_l->setVisible(false); } ADJUST_SIZE }); @@ -167,6 +184,7 @@ void DialogEditProfile::typeSelected(const QString &newType) { ui->sni->setText(stream->sni); ui->alpn->setText(stream->alpn); ui->insecure->setChecked(stream->allow_insecure); + ui->header_type->setCurrentText(stream->header_type); CACHE.certificate = stream->certificate; } else { ui->right_all_w->setVisible(false); @@ -241,6 +259,7 @@ void DialogEditProfile::accept() { stream->sni = ui->sni->text(); stream->alpn = ui->alpn->text(); stream->allow_insecure = ui->insecure->isChecked(); + stream->header_type = ui->header_type->currentText(); stream->certificate = CACHE.certificate; } auto custom_item = ent->bean->_get("custom"); diff --git a/ui/edit/dialog_edit_profile.ui b/ui/edit/dialog_edit_profile.ui index 3025501..e5a28bd 100644 --- a/ui/edit/dialog_edit_profile.ui +++ b/ui/edit/dialog_edit_profile.ui @@ -315,31 +315,63 @@ Network Settings (%1) - - + + + + + - http path (ws/http) 或 serviceName (gRPC) + http path (ws/h2/伪装http) +serviceName (gRPC) +key (QUIC) Path - - + + - + - http host + http host (ws/h2/伪装http) +security (QUIC) Host - - + + + + + 0 + 0 + + + + 伪装头部 (tcp/quic) + + + headerType + + + + + + + + 0 + 0 + + + + true + +