diff --git a/fmt/Bean2CoreObj_box.cpp b/fmt/Bean2CoreObj_box.cpp
index d09c396..f6e4018 100644
--- a/fmt/Bean2CoreObj_box.cpp
+++ b/fmt/Bean2CoreObj_box.cpp
@@ -52,6 +52,15 @@ namespace NekoGui_fmt {
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 a37a36b..7681af3 100644
--- a/fmt/Bean2Link.cpp
+++ b/fmt/Bean2Link.cpp
@@ -60,6 +60,12 @@ namespace NekoGui_fmt {
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
@@ -156,6 +162,11 @@ 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 ebd64a2..d7b6f85 100644
--- a/fmt/Link2Bean.cpp
+++ b/fmt/Link2Bean.cpp
@@ -93,6 +93,12 @@ 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->host = GetQueryValue(query, "host", "");
+ stream->path = GetQueryValue(query, "path", "");
+ }
}
// mux
@@ -171,6 +177,7 @@ 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") {
@@ -239,6 +246,12 @@ 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 5e582a6..3a8d80e 100644
--- a/fmt/V2RayStreamSettings.hpp
+++ b/fmt/V2RayStreamSettings.hpp
@@ -13,6 +13,7 @@ namespace NekoGui_fmt {
QString host = "";
QString method = "";
QString headers = "";
+ QString header_type = "";
QString sni = "";
QString alpn = "";
@@ -38,6 +39,7 @@ namespace NekoGui_fmt {
_add(new configItem("cert", &certificate, itemType::string));
_add(new configItem("insecure", &allow_insecure, itemType::boolean));
_add(new configItem("headers", &headers, itemType::string));
+ _add(new configItem("h_type", &header_type, 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));
diff --git a/sub/GroupUpdater.cpp b/sub/GroupUpdater.cpp
index b86943b..93f7a16 100644
--- a/sub/GroupUpdater.cpp
+++ b/sub/GroupUpdater.cpp
@@ -459,6 +459,23 @@ 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 a1a9186..9977884 100644
--- a/ui/edit/dialog_edit_profile.cpp
+++ b/ui/edit/dialog_edit_profile.cpp
@@ -32,7 +32,20 @@ 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 == "grpc") {
+ if (txt == "tcp") {
+ ui->header_type->setVisible(true);
+ ui->header_type_l->setVisible(true);
+ 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 if (txt == "grpc") {
+ ui->header_type->setVisible(false);
+ ui->header_type_l->setVisible(false);
ui->headers->setVisible(false);
ui->headers_l->setVisible(false);
ui->method->setVisible(false);
@@ -42,6 +55,8 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
ui->host->setVisible(false);
ui->host_l->setVisible(false);
} else if (txt == "ws" || txt == "httpupgrade") {
+ ui->header_type->setVisible(false);
+ ui->header_type_l->setVisible(false);
ui->headers->setVisible(true);
ui->headers_l->setVisible(true);
ui->method->setVisible(false);
@@ -51,6 +66,8 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
ui->host->setVisible(true);
ui->host_l->setVisible(true);
} else if (txt == "http") {
+ ui->header_type->setVisible(false);
+ ui->header_type_l->setVisible(false);
ui->headers->setVisible(true);
ui->headers_l->setVisible(true);
ui->method->setVisible(true);
@@ -60,6 +77,8 @@ DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId,
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);
@@ -236,7 +255,6 @@ 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);
@@ -252,6 +270,7 @@ 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));
@@ -318,9 +337,11 @@ 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);
@@ -382,6 +403,7 @@ bool DialogEditProfile::onEnd() {
stream->utlsFingerprint = ui->utlsFingerprint->currentText();
stream->allow_insecure = ui->insecure->isChecked();
stream->headers = ui->headers->text();
+ stream->header_type = ui->header_type->currentText();
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();
diff --git a/ui/edit/dialog_edit_profile.ui b/ui/edit/dialog_edit_profile.ui
index 6a56ea3..45946a5 100644
--- a/ui/edit/dialog_edit_profile.ui
+++ b/ui/edit/dialog_edit_profile.ui
@@ -525,56 +525,14 @@
Network Settings (%1)
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- <html><head/><body><p>http host (ws/http/httpUpgrade)</p></body></html>
-
-
- Host
-
-
-
- -
-
-
- -
-
-
- EarlyData Length
-
-
-
- -
-
-
- <html><head/><body><p>http path (ws/http/httpUpgrade)</p><p>serviceName (gRPC)</p></body></html>
-
-
- Path
-
-
-
- -
+
-
EarlyData Name
- -
-
-
- -
+
-
@@ -593,7 +551,39 @@
- -
+
-
+
+
+ EarlyData Length
+
+
+
+ -
+
+
+ -
+
+
+ <html><head/><body><p>http path (ws/http/httpUpgrade)</p><p>serviceName (gRPC)</p></body></html>
+
+
+ Path
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
<html><head/><body><p>Method of http request, will be converted to uppercase</p></body></html>
@@ -603,8 +593,39 @@
+ -
+
+
+ <html><head/><body><p>http host (ws/http/httpUpgrade)</p></body></html>
+
+
+ Host
+
+
+
-
-
+
+
+ -
+
+
-
+
+
+
+
+ -
+
+ http
+
+
+
+
+ -
+
+
+ header type
+
+