fix: fix hysteria(2) url parser and generator for port hopping

This commit is contained in:
parhelia512 2025-09-29 23:45:23 +08:00
parent e73fd0dc4a
commit d655b14f69
4 changed files with 186 additions and 34 deletions

96
3rdparty/URLParser/url_parser.h vendored Normal file
View File

@ -0,0 +1,96 @@
//
// URL Parser for C++
// Created Oct 22, 2017.
// Website : https://github.com/dongbum/URLParser
// Usage : Just include this header file.
//
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include "url_parser_function.h"
class URLParser
{
public:
struct HTTP_URL
{
std::string scheme;
std::string host;
std::string port;
std::vector<std::string> path;
std::string query_string;
std::unordered_map<std::string, std::string> query;
};
public:
static HTTP_URL Parse(const std::string& input_url)
{
HTTP_URL http_url;
size_t st = 0;
size_t before = 0;
URLParserFunction::FindKeyword(input_url, st, before, "://", http_url.scheme);
URLParserFunction::FindKeyword(input_url, st, before, "/", http_url.host);
size_t temp_st = 0;
size_t temp_before = 0;
std::string temp_ip;
std::string temp_port;
if (true == URLParserFunction::FindKeyword(http_url.host, temp_st, temp_before, ":", temp_ip))
{
http_url.port = std::string(&http_url.host[temp_before]);
http_url.host = temp_ip;
}
while (true)
{
std::string path;
if (false == URLParserFunction::FindKeyword(input_url, st, before, "/", path))
break;
http_url.path.push_back(path);
}
std::string path;
if (false == URLParserFunction::FindKeyword(input_url, st, before, "?", path))
{
path = std::string(&input_url[st + 1]);
http_url.path.push_back(path);
return http_url;
}
if (st < input_url.length())
{
http_url.query_string = std::string(&input_url[st + 1]);
if (false == http_url.query_string.empty())
{
std::string query;
st = 0;
before = 0;
while (true)
{
std::string key, value;
if (false == URLParserFunction::FindKeyword(http_url.query_string, st, before, "&", query))
{
URLParserFunction::SplitQueryString(std::string(&http_url.query_string[before]), "=", key, value);
http_url.query.insert(std::unordered_map<std::string, std::string>::value_type(key, value));
break;
}
URLParserFunction::SplitQueryString(query, "=", key, value);
http_url.query.insert(std::unordered_map<std::string, std::string>::value_type(key, value));
}
}
}
return http_url;
};
};

View File

@ -0,0 +1,47 @@
#pragma once
#include <string>
#include <cstring>
class URLParserFunction
{
public:
static bool FindKeyword(const std::string& input_url, size_t& st, size_t& before, const std::string& delim, std::string& result)
{
char temp[1024] = { 0, };
size_t temp_st = st;
memcpy(&temp_st, &st, sizeof(temp_st));
st = input_url.find(delim, before);
if (st == std::string::npos)
{
st = temp_st;
return false;
}
memcpy(&temp[0], &input_url[before], st - before);
before = st + delim.length();
result = std::string(temp);
if (result.empty())
return false;
return true;
};
static bool SplitQueryString(const std::string& str, const std::string& delim, std::string& key, std::string& value)
{
char first[1024] = { 0, };
char second[1024] = { 0, };
size_t st = str.find(delim, 0);
memcpy(first, &str[0], st);
memcpy(second, &str[st + 1], str.length() - st);
key = std::string(first);
value = std::string(second);
return true;
};
};

View File

@ -220,10 +220,11 @@ namespace Configs {
QString QUICBean::ToShareLink() { QString QUICBean::ToShareLink() {
QUrl url; QUrl url;
QString portRange;
if (proxy_type == proxy_Hysteria) { if (proxy_type == proxy_Hysteria) {
url.setScheme("hysteria"); url.setScheme("hysteria");
url.setHost(serverAddress); url.setHost(serverAddress);
url.setPort(serverPort); url.setPort(0);
QUrlQuery q; QUrlQuery q;
q.addQueryItem("upmbps", Int2String(uploadMbps)); q.addQueryItem("upmbps", Int2String(uploadMbps));
q.addQueryItem("downmbps", Int2String(downloadMbps)); q.addQueryItem("downmbps", Int2String(downloadMbps));
@ -239,15 +240,16 @@ namespace Configs {
if (!alpn.isEmpty()) q.addQueryItem("alpn", alpn); if (!alpn.isEmpty()) q.addQueryItem("alpn", alpn);
if (connectionReceiveWindow > 0) q.addQueryItem("recv_window", Int2String(connectionReceiveWindow)); if (connectionReceiveWindow > 0) q.addQueryItem("recv_window", Int2String(connectionReceiveWindow));
if (streamReceiveWindow > 0) q.addQueryItem("recv_window_conn", Int2String(streamReceiveWindow)); if (streamReceiveWindow > 0) q.addQueryItem("recv_window_conn", Int2String(streamReceiveWindow));
if (!serverPorts.empty()) if (!serverPorts.empty()) {
{
QStringList portList; QStringList portList;
for (const auto& range : serverPorts) for (const auto& range : serverPorts) {
{ QString modifiedRange = range;
portList.append(range.split(":")); modifiedRange.replace(":", "-");
portList.append(modifiedRange);
} }
q.addQueryItem("server_ports", portList.join("-")); portRange = portList.join(",");
} } else
url.setPort(serverPort);
if (!hop_interval.isEmpty()) q.addQueryItem("hop_interval", hop_interval); if (!hop_interval.isEmpty()) q.addQueryItem("hop_interval", hop_interval);
if (!q.isEmpty()) url.setQuery(q); if (!q.isEmpty()) url.setQuery(q);
if (!name.isEmpty()) url.setFragment(name); if (!name.isEmpty()) url.setFragment(name);
@ -270,7 +272,7 @@ namespace Configs {
} else if (proxy_type == proxy_Hysteria2) { } else if (proxy_type == proxy_Hysteria2) {
url.setScheme("hy2"); url.setScheme("hy2");
url.setHost(serverAddress); url.setHost(serverAddress);
url.setPort(serverPort); url.setPort(0);
if (password.contains(":")) { if (password.contains(":")) {
url.setUserName(SubStrBefore(password, ":")); url.setUserName(SubStrBefore(password, ":"));
url.setPassword(SubStrAfter(password, ":")); url.setPassword(SubStrAfter(password, ":"));
@ -284,20 +286,24 @@ namespace Configs {
} }
if (allowInsecure) q.addQueryItem("insecure", "1"); if (allowInsecure) q.addQueryItem("insecure", "1");
if (!sni.isEmpty()) q.addQueryItem("sni", sni); if (!sni.isEmpty()) q.addQueryItem("sni", sni);
if (!serverPorts.empty()) if (!serverPorts.empty()) {
{
QStringList portList; QStringList portList;
for (const auto& range : serverPorts) for (const auto& range : serverPorts) {
{ QString modifiedRange = range;
portList.append(range.split(":")); modifiedRange.replace(":", "-");
portList.append(modifiedRange);
} }
q.addQueryItem("server_ports", portList.join("-")); portRange = portList.join(",");
} } else
url.setPort(serverPort);
if (!hop_interval.isEmpty()) q.addQueryItem("hop_interval", hop_interval); if (!hop_interval.isEmpty()) q.addQueryItem("hop_interval", hop_interval);
if (!q.isEmpty()) url.setQuery(q); if (!q.isEmpty()) url.setQuery(q);
if (!name.isEmpty()) url.setFragment(name); if (!name.isEmpty()) url.setFragment(name);
} }
return url.toString(QUrl::FullyEncoded); if (portRange.isEmpty())
return url.toString(QUrl::FullyEncoded);
else
return url.toString(QUrl::FullyEncoded).replace(":0?", ":" + portRange + "?");
} }
QString WireguardBean::ToShareLink() { QString WireguardBean::ToShareLink() {

View File

@ -1,5 +1,6 @@
#include "include/dataStore/ProxyEntity.hpp" #include "include/dataStore/ProxyEntity.hpp"
#include "include/configs/proxy/includes.h" #include "include/configs/proxy/includes.h"
#include "3rdparty/URLParser/url_parser.h"
#include <QUrlQuery> #include <QUrlQuery>
@ -302,8 +303,16 @@ namespace Configs {
bool QUICBean::TryParseLink(const QString &link) { bool QUICBean::TryParseLink(const QString &link) {
auto url = QUrl(link); auto url = QUrl(link);
if (!url.isValid()) {
if(!url.errorString().startsWith("Invalid port"))
return false;
serverPort = 0;
serverPorts = QString::fromStdString(URLParser::Parse((link.split("?")[0] + "/").toStdString()).port).split(",");
for (int i=0; i < serverPorts.size(); i++) {
serverPorts[i].replace("-", ":");
}
}
auto query = QUrlQuery(url.query()); auto query = QUrlQuery(url.query());
if (url.host().isEmpty() || url.port() == -1) return false;
if (url.scheme() == "hysteria") { if (url.scheme() == "hysteria") {
// https://hysteria.network/docs/uri-scheme/ // https://hysteria.network/docs/uri-scheme/
@ -311,7 +320,7 @@ namespace Configs {
name = url.fragment(QUrl::FullyDecoded); name = url.fragment(QUrl::FullyDecoded);
serverAddress = url.host(); serverAddress = url.host();
serverPort = url.port(); if (serverPort > 0) serverPort = url.port();
obfsPassword = QUrl::fromPercentEncoding(query.queryItemValue("obfsParam").toUtf8()); obfsPassword = QUrl::fromPercentEncoding(query.queryItemValue("obfsParam").toUtf8());
allowInsecure = QStringList{"1", "true"}.contains(query.queryItemValue("insecure")); allowInsecure = QStringList{"1", "true"}.contains(query.queryItemValue("insecure"));
uploadMbps = query.queryItemValue("upmbps").toInt(); uploadMbps = query.queryItemValue("upmbps").toInt();
@ -335,13 +344,10 @@ namespace Configs {
connectionReceiveWindow = query.queryItemValue("recv_window").toInt(); connectionReceiveWindow = query.queryItemValue("recv_window").toInt();
streamReceiveWindow = query.queryItemValue("recv_window_conn").toInt(); streamReceiveWindow = query.queryItemValue("recv_window_conn").toInt();
if (query.hasQueryItem("server_ports")) if (query.hasQueryItem("mport")) {
{ serverPorts = query.queryItemValue("mport").split(",");
auto portList = query.queryItemValue("server_ports").split("-"); for (int i=0; i < serverPorts.size(); i++) {
for (int i=0;i<portList.size();i+=2) serverPorts[i].replace("-", ":");
{
if (i+1 >= portList.size()) break;
serverPorts += portList[i]+":"+portList[i+1];
} }
} }
hop_interval = query.queryItemValue("hop_interval"); hop_interval = query.queryItemValue("hop_interval");
@ -366,7 +372,7 @@ namespace Configs {
} else if (QStringList{"hy2", "hysteria2"}.contains(url.scheme())) { } else if (QStringList{"hy2", "hysteria2"}.contains(url.scheme())) {
name = url.fragment(QUrl::FullyDecoded); name = url.fragment(QUrl::FullyDecoded);
serverAddress = url.host(); serverAddress = url.host();
serverPort = url.port(); if (serverPort > 0) serverPort = url.port();
obfsPassword = QUrl::fromPercentEncoding(query.queryItemValue("obfs-password").toUtf8()); obfsPassword = QUrl::fromPercentEncoding(query.queryItemValue("obfs-password").toUtf8());
allowInsecure = QStringList{"1", "true"}.contains(query.queryItemValue("insecure")); allowInsecure = QStringList{"1", "true"}.contains(query.queryItemValue("insecure"));
@ -375,13 +381,10 @@ namespace Configs {
} else { } else {
password = url.userName() + ":" + url.password(); password = url.userName() + ":" + url.password();
} }
if (query.hasQueryItem("server_ports")) if (query.hasQueryItem("mport")) {
{ serverPorts = query.queryItemValue("mport").split(",");
auto portList = query.queryItemValue("server_ports").split("-"); for (int i=0; i < serverPorts.size(); i++) {
for (int i=0;i<portList.size();i+=2) serverPorts[i].replace("-", ":");
{
if (i+1 >= portList.size()) break;
serverPorts += portList[i]+":"+portList[i+1];
} }
} }
hop_interval = query.queryItemValue("hop_interval"); hop_interval = query.queryItemValue("hop_interval");