diff --git a/fmt/Bean2CoreObj_box.cpp b/fmt/Bean2CoreObj_box.cpp index c832c3d..ba7ff11 100644 --- a/fmt/Bean2CoreObj_box.cpp +++ b/fmt/Bean2CoreObj_box.cpp @@ -19,6 +19,12 @@ namespace NekoGui_fmt { transport["early_data_header_name"] = "Sec-WebSocket-Protocol"; } } + bool ok; + auto headerMap = GetHeaderPairs(&ok); + if (!ok) { + MW_show_log("Warning: headers could not be parsed, they will not be used"); + } + transport["headers"] = QMapString2QJsonObject(headerMap); if (ws_early_data_length > 0) { transport["max_early_data"] = ws_early_data_length; transport["early_data_header_name"] = ws_early_data_name; @@ -26,22 +32,26 @@ namespace NekoGui_fmt { } else if (network == "http") { if (!path.isEmpty()) transport["path"] = path; if (!host.isEmpty()) transport["host"] = QListStr2QJsonArray(host.split(",")); + if (!method.isEmpty()) transport["method"] = method.toUpper(); + bool ok; + auto headerMap = GetHeaderPairs(&ok); + if (!ok) { + MW_show_log("Warning: headers could not be parsed, they will not be used"); + } + transport["headers"] = QMapString2QJsonObject(headerMap); } else if (network == "grpc") { if (!path.isEmpty()) transport["service_name"] = path; } else if (network == "httpupgrade") { if (!path.isEmpty()) transport["path"] = path; if (!host.isEmpty()) transport["host"] = host; + bool ok; + auto headerMap = GetHeaderPairs(&ok); + if (!ok) { + MW_show_log("Warning: headers could not be parsed, they will not be used"); + } + transport["headers"] = QMapString2QJsonObject(headerMap); } outbound->insert("transport", transport); - } else if (header_type == "http") { - // TCP + headerType - QJsonObject transport{ - {"type", "http"}, - {"method", "GET"}, - {"path", path}, - {"headers", QJsonObject{{"Host", QListStr2QJsonArray(host.split(","))}}}, - }; - outbound->insert("transport", transport); } // 对应字段 tls diff --git a/fmt/Bean2Link.cpp b/fmt/Bean2Link.cpp index e792a6c..dbcde89 100644 --- a/fmt/Bean2Link.cpp +++ b/fmt/Bean2Link.cpp @@ -51,17 +51,15 @@ namespace NekoGui_fmt { // type query.addQueryItem("type", stream->network); - if (stream->network == "ws" || stream->network == "http" || stream->network == "httpupgrade") { + if (stream->network == "ws" || stream->network == "httpupgrade") { if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path); if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host); + } else if (stream->network == "http" ) { + if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path); + if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host); + if (!stream->method.isEmpty()) query.addQueryItem("method", stream->method); } else if (stream->network == "grpc") { if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path); - } else if (stream->network == "tcp") { - if (stream->header_type == "http") { - if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path); - query.addQueryItem("headerType", "http"); - query.addQueryItem("host", stream->host); - } } // mux @@ -158,11 +156,6 @@ namespace NekoGui_fmt { if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host); } else if (stream->network == "grpc") { if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path); - } else if (stream->network == "tcp") { - if (stream->header_type == "http") { - query.addQueryItem("headerType", "http"); - query.addQueryItem("host", stream->host); - } } // mux diff --git a/fmt/Link2Bean.cpp b/fmt/Link2Bean.cpp index 202d792..c8f1867 100644 --- a/fmt/Link2Bean.cpp +++ b/fmt/Link2Bean.cpp @@ -87,17 +87,12 @@ namespace NekoGui_fmt { } else if (stream->network == "http") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", "").replace("|", ","); + stream->method = GetQueryValue(query, "method", ""); } else if (stream->network == "httpupgrade") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", ""); } else if (stream->network == "grpc") { stream->path = GetQueryValue(query, "serviceName", ""); - } else if (stream->network == "tcp") { - if (GetQueryValue(query, "headerType") == "http") { - stream->header_type = "http"; - stream->host = GetQueryValue(query, "host", ""); - stream->path = GetQueryValue(query, "path", ""); - } } // mux @@ -176,7 +171,6 @@ namespace NekoGui_fmt { 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(); if (!net.isEmpty()) { if (net == "h2") { @@ -245,12 +239,6 @@ namespace NekoGui_fmt { stream->host = GetQueryValue(query, "host", ""); } else if (stream->network == "grpc") { stream->path = GetQueryValue(query, "serviceName", ""); - } else if (stream->network == "tcp") { - if (GetQueryValue(query, "headerType") == "http") { - stream->header_type = "http"; - stream->path = GetQueryValue(query, "path", ""); - stream->host = GetQueryValue(query, "host", ""); - } } return !(uuid.isEmpty() || serverAddress.isEmpty()); } diff --git a/fmt/V2RayStreamSettings.hpp b/fmt/V2RayStreamSettings.hpp index 89ffcb2..5e582a6 100644 --- a/fmt/V2RayStreamSettings.hpp +++ b/fmt/V2RayStreamSettings.hpp @@ -8,12 +8,12 @@ namespace NekoGui_fmt { QString network = "tcp"; QString security = ""; QString packet_encoding = ""; - // ws/http/grpc/tcp-http/httpupgrade + QString path = ""; QString host = ""; - // kcp/quic/tcp-http - QString header_type = ""; - // tls + QString method = ""; + QString headers = ""; + QString sni = ""; QString alpn = ""; QString certificate = ""; @@ -37,7 +37,8 @@ namespace NekoGui_fmt { _add(new configItem("alpn", &alpn, itemType::string)); _add(new configItem("cert", &certificate, itemType::string)); _add(new configItem("insecure", &allow_insecure, itemType::boolean)); - _add(new configItem("h_type", &header_type, itemType::string)); + _add(new configItem("headers", &headers, itemType::string)); + _add(new configItem("method", &method, itemType::string)); _add(new configItem("ed_name", &ws_early_data_name, itemType::string)); _add(new configItem("ed_len", &ws_early_data_length, itemType::integer)); _add(new configItem("utls", &utlsFingerprint, itemType::string)); @@ -47,6 +48,56 @@ namespace NekoGui_fmt { } void BuildStreamSettingsSingBox(QJsonObject *outbound); + + QMap GetHeaderPairs(bool* ok) { + bool inQuote = false; + QString curr; + QStringList list; + for (const auto &ch: headers) { + if (inQuote) { + if (ch == '"') { + inQuote = false; + list << curr; + curr = ""; + continue; + } else { + curr += ch; + continue; + } + } + if (ch == '"') { + inQuote = true; + continue; + } + if (ch == ' ') { + if (!curr.isEmpty()) { + list << curr; + curr = ""; + } + continue; + } + if (ch == '=') { + if (!curr.isEmpty()) { + list << curr; + curr = ""; + } + continue; + } + curr+=ch; + } + if (!curr.isEmpty()) list< res; + for (int i = 0; i < list.size(); i+=2) { + res[list[i]] = list[i + 1]; + } + *ok = true; + return res; + } }; inline V2rayStreamSettings *GetStreamSettings(AbstractBean *bean) { diff --git a/main/NekoGui_Utils.cpp b/main/NekoGui_Utils.cpp index 3eccce7..c8a389c 100644 --- a/main/NekoGui_Utils.cpp +++ b/main/NekoGui_Utils.cpp @@ -147,6 +147,15 @@ QJsonArray QString2QJsonArray(const QString& str) { return {}; } +QJsonObject QMapString2QJsonObject(const QMap &mp) { + QJsonObject res; + for (const auto &key: mp.keys()) { + res.insert(key, mp[key]); + } + + return res; +} + QByteArray ReadFile(const QString &path) { QFile file(path); file.open(QFile::ReadOnly); diff --git a/main/NekoGui_Utils.hpp b/main/NekoGui_Utils.hpp index d05fe48..b6e69e4 100644 --- a/main/NekoGui_Utils.hpp +++ b/main/NekoGui_Utils.hpp @@ -89,6 +89,8 @@ QJsonArray QListStr2QJsonArray(const QList &list); QList QJsonArray2QListInt(const QJsonArray &arr); +QJsonObject QMapString2QJsonObject(const QMap &mp); + #define QJSONARRAY_ADD(arr, add) \ for (const auto &a: (add)) { \ (arr) += a; \ diff --git a/sub/GroupUpdater.cpp b/sub/GroupUpdater.cpp index 68b872e..96c1936 100644 --- a/sub/GroupUpdater.cpp +++ b/sub/GroupUpdater.cpp @@ -451,24 +451,6 @@ namespace NekoGui_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 if (type == "hysteria") { auto bean = ent->QUICBean(); diff --git a/ui/edit/dialog_edit_profile.cpp b/ui/edit/dialog_edit_profile.cpp index 741339a..78ee01e 100644 --- a/ui/edit/dialog_edit_profile.cpp +++ b/ui/edit/dialog_edit_profile.cpp @@ -31,36 +31,43 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, 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") { - 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); + if (txt == "grpc") { + 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(false); ui->host_l->setVisible(false); - } else if (txt == "ws" || txt == "http" || txt == "httpupgrade") { - ui->header_type->setVisible(false); - ui->header_type_l->setVisible(false); + } else if (txt == "ws" || txt == "httpupgrade") { + ui->headers->setVisible(true); + ui->headers_l->setVisible(true); + 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 if (txt == "http") { + ui->headers->setVisible(true); + ui->headers_l->setVisible(true); + ui->method->setVisible(true); + ui->method_l->setVisible(true); ui->path->setVisible(true); ui->path_l->setVisible(true); ui->host->setVisible(true); ui->host_l->setVisible(true); } else { - ui->header_type->setVisible(false); - ui->header_type_l->setVisible(false); + ui->headers->setVisible(false); + ui->headers_l->setVisible(false); + ui->method->setVisible(false); + ui->method_l->setVisible(false); ui->path->setVisible(false); ui->path_l->setVisible(false); ui->host->setVisible(false); ui->host_l->setVisible(false); } - // 传输设置 ED if (txt == "ws") { ui->ws_early_data_length->setVisible(true); ui->ws_early_data_length_l->setVisible(true); @@ -73,7 +80,6 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, ui->ws_early_data_name_l->setVisible(false); } if (!ui->utlsFingerprint->count()) ui->utlsFingerprint->addItems(Preset::SingBox::UtlsFingerPrint); - // 传输设置 是否可见 int networkBoxVisible = 0; for (auto label: ui->network_box->findChildren()) { if (!label->isHidden()) networkBoxVisible++; @@ -88,6 +94,17 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, if (txt == "tls") { ui->security_box->setVisible(true); ui->tls_camouflage_box->setVisible(true); + ui->reality_pbk->setVisible(false); + ui->reality_pbk_l->setVisible(false); + ui->reality_sid->setVisible(false); + ui->reality_sid_l->setVisible(false); + } else if (txt == "reality") { + ui->security_box->setVisible(true); + ui->tls_camouflage_box->setVisible(true); + ui->reality_pbk->setVisible(true); + ui->reality_pbk_l->setVisible(true); + ui->reality_sid->setVisible(true); + ui->reality_sid_l->setVisible(true); } else { ui->security_box->setVisible(false); ui->tls_camouflage_box->setVisible(false); @@ -213,12 +230,14 @@ void DialogEditProfile::typeSelected(const QString &newType) { // 右边 stream auto stream = GetStreamSettings(ent->bean.get()); if (stream != nullptr) { + ui->network_box->setVisible(stream->network != "tcp"); ui->right_all_w->setVisible(true); ui->network->setCurrentText(stream->network); ui->security->setCurrentText(stream->security); ui->packet_encoding->setCurrentText(stream->packet_encoding); ui->path->setText(stream->path); ui->host->setText(stream->host); + ui->method->setText(stream->method); ui->sni->setText(stream->sni); ui->alpn->setText(stream->alpn); if (newEnt) { @@ -227,10 +246,11 @@ void DialogEditProfile::typeSelected(const QString &newType) { ui->utlsFingerprint->setCurrentText(stream->utlsFingerprint); } ui->insecure->setChecked(stream->allow_insecure); - ui->header_type->setCurrentText(stream->header_type); + ui->headers->setText(stream->headers); ui->ws_early_data_name->setText(stream->ws_early_data_name); ui->ws_early_data_length->setText(Int2String(stream->ws_early_data_length)); ui->reality_pbk->setText(stream->reality_pbk); + ui->reality_sid->setText(stream->reality_sid); ui->multiplex->setCurrentIndex(ent->bean->mux_state); ui->brutal_enable->setCheckState(ent->bean->enable_brutal ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); ui->brutal_speed->setText(Int2String(ent->bean->brutal_speed)); @@ -292,11 +312,9 @@ void DialogEditProfile::typeSelected(const QString &newType) { if (type == "vmess" || type == "vless" || type == "trojan") { ui->network_l->setVisible(true); ui->network->setVisible(true); - ui->network_box->setVisible(true); } else { ui->network_l->setVisible(false); ui->network->setVisible(false); - ui->network_box->setVisible(false); } if (type == "vmess" || type == "vless" || type == "trojan" || type == "http") { ui->security->setVisible(true); @@ -314,14 +332,12 @@ void DialogEditProfile::typeSelected(const QString &newType) { ui->multiplex_l->setVisible(false); ui->brutal_box->setVisible(false); } - // 设置 是否可见 int streamBoxVisible = 0; for (auto label: ui->stream_box->findChildren()) { - if (!label->isHidden()) streamBoxVisible++; + if (!label->isHidden() && label->parent() == ui->stream_box) streamBoxVisible++; } ui->stream_box->setVisible(streamBoxVisible); - // 载入 type 之后,有些类型没有右边的设置 auto rightNoBox = (ui->stream_box->isHidden() && ui->network_box->isHidden() && ui->security_box->isHidden()); if (rightNoBox && !ui->right_all_w->isHidden()) { ui->right_all_w->setVisible(false); @@ -359,14 +375,23 @@ bool DialogEditProfile::onEnd() { stream->alpn = ui->alpn->text(); stream->utlsFingerprint = ui->utlsFingerprint->currentText(); stream->allow_insecure = ui->insecure->isChecked(); - stream->header_type = ui->header_type->currentText(); + stream->headers = ui->headers->text(); + stream->method = ui->method->text(); stream->ws_early_data_name = ui->ws_early_data_name->text(); stream->ws_early_data_length = ui->ws_early_data_length->text().toInt(); stream->reality_pbk = ui->reality_pbk->text(); + stream->reality_sid = ui->reality_sid->text(); ent->bean->mux_state = ui->multiplex->currentIndex(); ent->bean->enable_brutal = ui->brutal_enable->isChecked(); ent->bean->brutal_speed = ui->brutal_speed->text().toInt(); stream->certificate = CACHE.certificate; + + bool validHeaders; + stream->GetHeaderPairs(&validHeaders); + if (!validHeaders) { + MW_show_log("Headers are not valid"); + return false; + } } // cached custom diff --git a/ui/edit/dialog_edit_profile.ui b/ui/edit/dialog_edit_profile.ui index add38f7..8d83957 100644 --- a/ui/edit/dialog_edit_profile.ui +++ b/ui/edit/dialog_edit_profile.ui @@ -36,7 +36,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint @@ -130,7 +130,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -270,10 +270,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok @@ -301,7 +301,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint @@ -429,6 +429,11 @@ tls + + + reality + + @@ -478,7 +483,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -520,7 +525,37 @@ Network Settings (%1) - + + + + + + + + + + + + + http host (ws/http/伪装http) +security (QUIC) + + + Host + + + + + + + + + + EarlyData Length + + + + http path (ws/http/伪装http) @@ -532,22 +567,18 @@ key (QUIC) - - - - - - - http host (ws/http/伪装http) -security (QUIC) - + + - Host + EarlyData Name + + + - + 0 @@ -557,56 +588,23 @@ security (QUIC) 伪装头部类型 (tcp/quic) + + <html><head/><body><p>Headers in format:</p><p>key1=val1 key2=&quot;multi word val2&quot; &quot;multi word key3&quot;=val3</p></body></html> + header + + + + Method + + + - - - - - - - 0 - 0 - - - - true - - - - - - - - - http - - - - - - - - - - - EarlyData Length - - - - - - - - - - EarlyData Name - - + @@ -617,6 +615,9 @@ security (QUIC) TLS Camouflage Settings + + + @@ -624,9 +625,6 @@ security (QUIC) - - - @@ -637,6 +635,13 @@ security (QUIC) + + + + Reality SID + + + @@ -650,6 +655,9 @@ security (QUIC) + + + @@ -677,7 +685,6 @@ security (QUIC) security packet_encoding multiplex - header_type path host ws_early_data_length