mirror of
https://github.com/amnezia-vpn/xray-serialization.git
synced 2026-05-17 00:16:21 +03:00
Add xray config serialization
This commit is contained in:
38
inbound.cpp
Normal file
38
inbound.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::inbounds
|
||||||
|
{
|
||||||
|
|
||||||
|
//"inbounds": [
|
||||||
|
// {
|
||||||
|
// "listen": "127.0.0.1",
|
||||||
|
// "port": 10808,
|
||||||
|
// "protocol": "socks",
|
||||||
|
// "settings": {
|
||||||
|
// "udp": true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//],
|
||||||
|
|
||||||
|
const static QString listen = "127.0.0.1";
|
||||||
|
const static int port = 10808;
|
||||||
|
const static QString protocol = "socks";
|
||||||
|
|
||||||
|
QJsonObject GenerateInboundEntry()
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonIO::SetValue(root, listen, "listen");
|
||||||
|
QJsonIO::SetValue(root, port, "port");
|
||||||
|
QJsonIO::SetValue(root, protocol, "protocol");
|
||||||
|
QJsonIO::SetValue(root, true, "settings", "udp");
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::inbounds
|
||||||
|
|
||||||
122
outbound.cpp
Normal file
122
outbound.cpp
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::outbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(domainStrategy, redirect)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateBlackHoleOUT(bool useHTTP)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonObject resp;
|
||||||
|
resp.insert("type", useHTTP ? "http" : "none");
|
||||||
|
root.insert("response", resp);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(address, port, method, password)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &_servers)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray x;
|
||||||
|
|
||||||
|
for (const auto &server : _servers)
|
||||||
|
{
|
||||||
|
x.append(GenerateShadowSocksServerOUT(server.address, server.port, server.method, server.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.insert("servers", x);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateHTTPSOCKSOut(const QString &addr, int port, bool useAuth, const QString &username, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonIO::SetValue(root, addr, "servers", 0, "address");
|
||||||
|
QJsonIO::SetValue(root, port, "servers", 0, "port");
|
||||||
|
if (useAuth)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(root, username, "servers", 0, "users", 0, "user");
|
||||||
|
QJsonIO::SetValue(root, password, "servers", 0, "users", 0, "pass");
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateOutboundEntry(const QString &tag, const QString &protocol, const QJsonObject &settings, const QJsonObject &streamSettings,
|
||||||
|
const QJsonObject &mux, const QString &sendThrough)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(sendThrough, protocol, settings, tag, streamSettings, mux)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &_servers)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray x;
|
||||||
|
|
||||||
|
for (const auto &server : _servers)
|
||||||
|
{
|
||||||
|
x.append(GenerateTrojanServerOUT(server.address, server.port, server.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.insert("servers", x);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(address, port, password)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::outbounds
|
||||||
|
|
||||||
66
serialization.h
Normal file
66
serialization.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef SERIALIZATION_H
|
||||||
|
#define SERIALIZATION_H
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include "transfer.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization
|
||||||
|
{
|
||||||
|
namespace vmess
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||||
|
} // namespace vmess
|
||||||
|
|
||||||
|
namespace vmess_new
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||||
|
} // namespace vmess_new
|
||||||
|
|
||||||
|
namespace vless
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
||||||
|
} // namespace vless
|
||||||
|
|
||||||
|
namespace ss
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &ss, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool isSip002);
|
||||||
|
} // namespace ss
|
||||||
|
|
||||||
|
namespace ssd
|
||||||
|
{
|
||||||
|
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList);
|
||||||
|
} // namespace ssd
|
||||||
|
|
||||||
|
namespace trojan
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &trojan, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const TrojanObject &server, const QString &alias);
|
||||||
|
} // namespace trojan
|
||||||
|
|
||||||
|
namespace outbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect);
|
||||||
|
QJsonObject GenerateBlackHoleOUT(bool useHTTP);
|
||||||
|
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &servers);
|
||||||
|
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password);
|
||||||
|
QJsonObject GenerateHTTPSOCKSOut(const QString &address, int port, bool useAuth, const QString &username, const QString &password);
|
||||||
|
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &servers);
|
||||||
|
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password);
|
||||||
|
QJsonObject GenerateOutboundEntry(const QString &tag, //
|
||||||
|
const QString &protocol, //
|
||||||
|
const QJsonObject &settings, //
|
||||||
|
const QJsonObject &streamSettings, //
|
||||||
|
const QJsonObject &mux = {}, //
|
||||||
|
const QString &sendThrough = "0.0.0.0");
|
||||||
|
} // namespace outbounds
|
||||||
|
|
||||||
|
namespace inbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateInboundEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SERIALIZATION_H
|
||||||
142
ss.cpp
Normal file
142
ss.cpp
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||||
|
|
||||||
|
namespace amnezia::serialization::ss
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &ssUri, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
ShadowSocksServerObject server;
|
||||||
|
QString d_name;
|
||||||
|
|
||||||
|
// auto ssUri = _ssUri.toStdString();
|
||||||
|
if (ssUri.length() < 5)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("SS URI is too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto uri = ssUri.mid(5);
|
||||||
|
auto hashPos = uri.lastIndexOf("#");
|
||||||
|
|
||||||
|
if (hashPos >= 0)
|
||||||
|
{
|
||||||
|
// Get the name/remark
|
||||||
|
d_name = uri.mid(uri.lastIndexOf("#") + 1);
|
||||||
|
uri.truncate(hashPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto atPos = uri.indexOf('@');
|
||||||
|
|
||||||
|
if (atPos < 0)
|
||||||
|
{
|
||||||
|
// Old URI scheme
|
||||||
|
QString decoded = QByteArray::fromBase64(uri.toUtf8(), QByteArray::Base64Option::OmitTrailingEquals);
|
||||||
|
auto colonPos = decoded.indexOf(':');
|
||||||
|
|
||||||
|
if (colonPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.method = decoded.left(colonPos);
|
||||||
|
decoded.remove(0, colonPos + 1);
|
||||||
|
atPos = decoded.lastIndexOf('@');
|
||||||
|
|
||||||
|
if (atPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the at separator between password and hostname");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.password = decoded.mid(0, atPos);
|
||||||
|
decoded.remove(0, atPos + 1);
|
||||||
|
colonPos = decoded.lastIndexOf(':');
|
||||||
|
|
||||||
|
if (colonPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between hostname and port");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.address = decoded.mid(0, colonPos);
|
||||||
|
server.port = decoded.mid(colonPos + 1).toInt();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// SIP002 URI scheme
|
||||||
|
auto x = QUrl::fromUserInput(uri);
|
||||||
|
server.address = x.host();
|
||||||
|
server.port = x.port();
|
||||||
|
const auto userInfo = Utils::SafeBase64Decode(x.userName());
|
||||||
|
const auto userInfoSp = userInfo.indexOf(':');
|
||||||
|
|
||||||
|
if (userInfoSp < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||||
|
return QJsonObject{};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto method = userInfo.mid(0, userInfoSp);
|
||||||
|
server.method = method;
|
||||||
|
server.password = userInfo.mid(userInfoSp + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
d_name = QUrl::fromPercentEncoding(d_name.toUtf8());
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ server }), {}));
|
||||||
|
JADD(outbounds)
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
*alias = alias->isEmpty() ? d_name : *alias + "_" + d_name;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool)
|
||||||
|
{
|
||||||
|
QUrl url;
|
||||||
|
const auto plainUserInfo = server.method + ":" + server.password;
|
||||||
|
const auto userinfo = plainUserInfo.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
url.setUserInfo(userinfo);
|
||||||
|
url.setScheme("ss");
|
||||||
|
url.setHost(server.address);
|
||||||
|
url.setPort(server.port);
|
||||||
|
url.setFragment(alias);
|
||||||
|
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::ss
|
||||||
|
|
||||||
244
ssd.cpp
Normal file
244
ssd.cpp
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Naive SSD Decoder for Qv2ray
|
||||||
|
*
|
||||||
|
* @author DuckSoft <realducksoft@gmail.com>
|
||||||
|
* @copyright Licensed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
const inline QString QV2RAY_SSD_DEFAULT_NAME_PATTERN = "%1 - %2 (rate %3)";
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::ssd
|
||||||
|
{
|
||||||
|
// These below are super strict checking schemes, but necessary.
|
||||||
|
#define MUST_EXIST(fieldName) \
|
||||||
|
if (!obj.contains((fieldName)) || obj[(fieldName)].isUndefined() || obj[(fieldName)].isNull()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must exist").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_PORT(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (int value = obj[(fieldName)].toInt(-1); value < 0 || value > 65535) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be valid port number"); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_STRING(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (!obj[(fieldName)].isString()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be of type 'string'").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_ARRAY(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (!obj[(fieldName)].isArray()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be an array").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SERVER_SHOULD_BE_OBJECT(server) \
|
||||||
|
if (!server.isObject()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: server must be an object"); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
#define SHOULD_EXIST(fieldName) \
|
||||||
|
if (serverObject[(fieldName)].isUndefined()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: missing required field %1").arg(fieldName); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
#define SHOULD_STRING(fieldName) \
|
||||||
|
SHOULD_EXIST(fieldName); \
|
||||||
|
if (!serverObject[(fieldName)].isString()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: field %1 should be of type 'string'").arg(fieldName); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList)
|
||||||
|
{
|
||||||
|
// ssd links should begin with "ssd://"
|
||||||
|
if (!uri.startsWith("ssd://"))
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: should begin with ssd://");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode base64
|
||||||
|
const auto ssdURIBody = uri.mid(6, uri.length() - 6); //(&uri, 6, uri.length() - 6);
|
||||||
|
const auto decodedJSON = Utils::SafeBase64Decode(ssdURIBody).toUtf8();
|
||||||
|
|
||||||
|
if (decodedJSON.length() == 0)
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: base64 parse failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto decodeError = Utils::VerifyJsonString(decodedJSON);
|
||||||
|
if (!decodeError.isEmpty())
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json parse failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// casting to object
|
||||||
|
const auto obj = Utils::JsonFromString(decodedJSON);
|
||||||
|
|
||||||
|
// obj.airport
|
||||||
|
MUST_STRING("airport");
|
||||||
|
*groupName = obj["airport"].toString();
|
||||||
|
|
||||||
|
// obj.port
|
||||||
|
MUST_PORT("port");
|
||||||
|
const int port = obj["port"].toInt();
|
||||||
|
|
||||||
|
// obj.encryption
|
||||||
|
MUST_STRING("encryption");
|
||||||
|
const auto encryption = obj["encryption"].toString();
|
||||||
|
|
||||||
|
// check: rc4-md5 is not supported by v2ray-core
|
||||||
|
// TODO: more checks, including all algorithms
|
||||||
|
if (encryption.toLower() == "rc4-md5")
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: rc4-md5 encryption is not supported by v2ray-core");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// obj.password
|
||||||
|
MUST_STRING("password");
|
||||||
|
const auto password = obj["password"].toString();
|
||||||
|
// obj.servers
|
||||||
|
MUST_ARRAY("servers");
|
||||||
|
//
|
||||||
|
QList<std::pair<QString, QJsonObject>> serverList;
|
||||||
|
//
|
||||||
|
|
||||||
|
// iterate through the servers
|
||||||
|
for (const auto &server : obj["servers"].toArray())
|
||||||
|
{
|
||||||
|
SERVER_SHOULD_BE_OBJECT(server);
|
||||||
|
const auto serverObject = server.toObject();
|
||||||
|
ShadowSocksServerObject ssObject;
|
||||||
|
|
||||||
|
// encryption
|
||||||
|
ssObject.method = encryption;
|
||||||
|
|
||||||
|
// password
|
||||||
|
ssObject.password = password;
|
||||||
|
|
||||||
|
// address :-> "server"
|
||||||
|
SHOULD_STRING("server");
|
||||||
|
const auto serverAddress = serverObject["server"].toString();
|
||||||
|
ssObject.address = serverAddress;
|
||||||
|
|
||||||
|
// port selection:
|
||||||
|
// normal: use global settings
|
||||||
|
// overriding: use current config
|
||||||
|
if (serverObject["port"].isUndefined())
|
||||||
|
{
|
||||||
|
ssObject.port = port;
|
||||||
|
}
|
||||||
|
else if (auto currPort = serverObject["port"].toInt(-1); (currPort >= 0 && currPort <= 65535))
|
||||||
|
{
|
||||||
|
ssObject.port = currPort;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ssObject.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
// name decision:
|
||||||
|
// untitled: using server:port as name
|
||||||
|
// entitled: using given name
|
||||||
|
QString nodeName;
|
||||||
|
if (serverObject["remarks"].isUndefined())
|
||||||
|
{
|
||||||
|
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||||
|
}
|
||||||
|
else if (serverObject["remarks"].isString())
|
||||||
|
{
|
||||||
|
nodeName = serverObject["remarks"].toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ratio decision:
|
||||||
|
// unspecified: ratio = 1
|
||||||
|
// specified: use given value
|
||||||
|
double ratio = 1.0;
|
||||||
|
if (auto currRatio = serverObject["ratio"].toDouble(-1.0); currRatio != -1.0)
|
||||||
|
{
|
||||||
|
ratio = currRatio;
|
||||||
|
}
|
||||||
|
// else if (!serverObject["ratio"].isUndefined())
|
||||||
|
// {
|
||||||
|
// //*logList << QObject::tr("Invalid ratio encountered. using fallback value.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// format the total name of the node.
|
||||||
|
const auto finalName = QV2RAY_SSD_DEFAULT_NAME_PATTERN.arg(*groupName, nodeName).arg(ratio);
|
||||||
|
// appending to the total list
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ ssObject }), {}));
|
||||||
|
root["outbounds"] = outbounds;
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
serverList.append({ finalName, root });
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the current result
|
||||||
|
return serverList;
|
||||||
|
}
|
||||||
|
#undef MUST_EXIST
|
||||||
|
#undef MUST_PORT
|
||||||
|
#undef MUST_ARRAY
|
||||||
|
#undef MUST_STRING
|
||||||
|
#undef SERVER_SHOULD_BE_OBJECT
|
||||||
|
#undef SHOULD_EXIST
|
||||||
|
#undef SHOULD_STRING
|
||||||
|
} // namespace amnezia::serialization::ssd
|
||||||
|
|
||||||
313
transfer.h
Normal file
313
transfer.h
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
#ifndef TRANSFER_H
|
||||||
|
#define TRANSFER_H
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
|
||||||
|
#define JADDEx(field) root.insert(#field, field);
|
||||||
|
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||||
|
|
||||||
|
constexpr auto VMESS_USER_ALTERID_DEFAULT = 0;
|
||||||
|
|
||||||
|
namespace amnezia::serialization {
|
||||||
|
|
||||||
|
struct ShadowSocksServerObject
|
||||||
|
{
|
||||||
|
QString address;
|
||||||
|
QString method;
|
||||||
|
QString password;
|
||||||
|
int port;
|
||||||
|
JSONSTRUCT_COMPARE(ShadowSocksServerObject, address, method, password)
|
||||||
|
JSONSTRUCT_REGISTER(ShadowSocksServerObject, F(address, port, method, password))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct VMessServerObject
|
||||||
|
{
|
||||||
|
struct UserObject
|
||||||
|
{
|
||||||
|
QString id;
|
||||||
|
int alterId = VMESS_USER_ALTERID_DEFAULT;
|
||||||
|
QString security = "auto";
|
||||||
|
int level = 0;
|
||||||
|
JSONSTRUCT_COMPARE(UserObject, id, alterId, security, level)
|
||||||
|
JSONSTRUCT_REGISTER(UserObject, F(id, alterId, security, level))
|
||||||
|
};
|
||||||
|
|
||||||
|
QString address;
|
||||||
|
int port;
|
||||||
|
QList<UserObject> users;
|
||||||
|
JSONSTRUCT_COMPARE(VMessServerObject, address, port, users)
|
||||||
|
JSONSTRUCT_REGISTER(VMessServerObject, F(address, port, users))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace transfer
|
||||||
|
{
|
||||||
|
|
||||||
|
struct HTTPRequestObject
|
||||||
|
{
|
||||||
|
QString version = "1.1";
|
||||||
|
QString method = "GET";
|
||||||
|
QList<QString> path = { "/" };
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
HTTPRequestObject()
|
||||||
|
{
|
||||||
|
headers = {
|
||||||
|
{ "Host", { "www.baidu.com", "www.bing.com" } },
|
||||||
|
{ "User-Agent",
|
||||||
|
{ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46" } },
|
||||||
|
{ "Accept-Encoding", { "gzip, deflate" } },
|
||||||
|
{ "Connection", { "keep-alive" } },
|
||||||
|
{ "Pragma", { "no-cache" } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
JSONSTRUCT_COMPARE(HTTPRequestObject, version, method, path, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HTTPRequestObject, F(version, method, path, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct HTTPResponseObject
|
||||||
|
{
|
||||||
|
QString version = "1.1";
|
||||||
|
QString status = "200";
|
||||||
|
QString reason = "OK";
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
HTTPResponseObject()
|
||||||
|
{
|
||||||
|
headers = { { "Content-Type", { "application/octet-stream", "video/mpeg" } }, //
|
||||||
|
{ "Transfer-Encoding", { "chunked" } }, //
|
||||||
|
{ "Connection", { "keep-alive" } }, //
|
||||||
|
{ "Pragma", { "no-cache" } } };
|
||||||
|
}
|
||||||
|
JSONSTRUCT_COMPARE(HTTPResponseObject, version, status, reason, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HTTPResponseObject, F(version, status, reason, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TCPHeader_Internal
|
||||||
|
{
|
||||||
|
QString type = "none";
|
||||||
|
HTTPRequestObject request;
|
||||||
|
HTTPResponseObject response;
|
||||||
|
JSONSTRUCT_COMPARE(TCPHeader_Internal, type, request, response)
|
||||||
|
JSONSTRUCT_REGISTER(TCPHeader_Internal, A(type), F(request, response))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct ObfsHeaderObject
|
||||||
|
{
|
||||||
|
QString type = "none";
|
||||||
|
JSONSTRUCT_COMPARE(ObfsHeaderObject, type)
|
||||||
|
JSONSTRUCT_REGISTER(ObfsHeaderObject, F(type))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TCPObject
|
||||||
|
{
|
||||||
|
TCPHeader_Internal header;
|
||||||
|
JSONSTRUCT_COMPARE(TCPObject, header)
|
||||||
|
JSONSTRUCT_REGISTER(TCPObject, F(header))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct KCPObject
|
||||||
|
{
|
||||||
|
int mtu = 1350;
|
||||||
|
int tti = 50;
|
||||||
|
int uplinkCapacity = 5;
|
||||||
|
int downlinkCapacity = 20;
|
||||||
|
bool congestion = false;
|
||||||
|
int readBufferSize = 2;
|
||||||
|
int writeBufferSize = 2;
|
||||||
|
QString seed;
|
||||||
|
ObfsHeaderObject header;
|
||||||
|
KCPObject(){};
|
||||||
|
JSONSTRUCT_COMPARE(KCPObject, mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, seed, header)
|
||||||
|
JSONSTRUCT_REGISTER(KCPObject, F(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header, seed))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct WebSocketObject
|
||||||
|
{
|
||||||
|
QString path = "/";
|
||||||
|
QMap<QString, QString> headers;
|
||||||
|
int maxEarlyData = 0;
|
||||||
|
bool useBrowserForwarding = false;
|
||||||
|
QString earlyDataHeaderName;
|
||||||
|
JSONSTRUCT_COMPARE(WebSocketObject, path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName)
|
||||||
|
JSONSTRUCT_REGISTER(WebSocketObject, F(path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct HttpObject
|
||||||
|
{
|
||||||
|
QList<QString> host;
|
||||||
|
QString path = "/";
|
||||||
|
QString method = "PUT";
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
JSONSTRUCT_COMPARE(HttpObject, host, path, method, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HttpObject, F(host, path, method, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct DomainSocketObject
|
||||||
|
{
|
||||||
|
QString path = "/";
|
||||||
|
JSONSTRUCT_COMPARE(DomainSocketObject, path)
|
||||||
|
JSONSTRUCT_REGISTER(DomainSocketObject, F(path))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct QuicObject
|
||||||
|
{
|
||||||
|
QString security = "none";
|
||||||
|
QString key;
|
||||||
|
ObfsHeaderObject header;
|
||||||
|
JSONSTRUCT_COMPARE(QuicObject, security, key, header)
|
||||||
|
JSONSTRUCT_REGISTER(QuicObject, F(security, key, header))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct gRPCObject
|
||||||
|
{
|
||||||
|
QString serviceName;
|
||||||
|
bool multiMode = false;
|
||||||
|
JSONSTRUCT_COMPARE(gRPCObject, serviceName, multiMode)
|
||||||
|
JSONSTRUCT_REGISTER(gRPCObject, F(serviceName, multiMode))
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct SockoptObject
|
||||||
|
{
|
||||||
|
int mark = 0;
|
||||||
|
bool tcpFastOpen = false;
|
||||||
|
QString tproxy = "off";
|
||||||
|
int tcpKeepAliveInterval = 0;
|
||||||
|
JSONSTRUCT_COMPARE(SockoptObject, mark, tcpFastOpen, tproxy, tcpKeepAliveInterval)
|
||||||
|
JSONSTRUCT_REGISTER(SockoptObject, F(mark, tcpFastOpen, tproxy, tcpKeepAliveInterval))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct CertificateObject
|
||||||
|
{
|
||||||
|
QString usage = "encipherment";
|
||||||
|
QString certificateFile;
|
||||||
|
QString keyFile;
|
||||||
|
QList<QString> certificate;
|
||||||
|
QList<QString> key;
|
||||||
|
JSONSTRUCT_COMPARE(CertificateObject, usage, certificateFile, keyFile, certificate, key)
|
||||||
|
JSONSTRUCT_REGISTER(CertificateObject, F(usage, certificateFile, keyFile, certificate, key))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TLSObject
|
||||||
|
{
|
||||||
|
QString serverName;
|
||||||
|
bool allowInsecure = false;
|
||||||
|
bool enableSessionResumption = false;
|
||||||
|
bool disableSystemRoot = false;
|
||||||
|
QList<QString> alpn;
|
||||||
|
QList<QString> pinnedPeerCertificateChainSha256;
|
||||||
|
QList<CertificateObject> certificates;
|
||||||
|
JSONSTRUCT_COMPARE(TLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||||
|
pinnedPeerCertificateChainSha256, certificates)
|
||||||
|
JSONSTRUCT_REGISTER(TLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||||
|
pinnedPeerCertificateChainSha256, certificates))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct XTLSObject
|
||||||
|
{
|
||||||
|
QString serverName;
|
||||||
|
bool allowInsecure = false;
|
||||||
|
bool enableSessionResumption = false;
|
||||||
|
bool disableSystemRoot = false;
|
||||||
|
QList<QString> alpn;
|
||||||
|
QList<CertificateObject> certificates;
|
||||||
|
JSONSTRUCT_COMPARE(XTLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates)
|
||||||
|
JSONSTRUCT_REGISTER(XTLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates))
|
||||||
|
};
|
||||||
|
} // namespace transfer
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TrojanObject
|
||||||
|
{
|
||||||
|
quint16 port;
|
||||||
|
QString address;
|
||||||
|
QString password;
|
||||||
|
QString sni;
|
||||||
|
bool ignoreCertificate = false;
|
||||||
|
bool ignoreHostname = false;
|
||||||
|
bool reuseSession = false;
|
||||||
|
bool sessionTicket = false;
|
||||||
|
bool reusePort = false;
|
||||||
|
bool tcpFastOpen = false;
|
||||||
|
|
||||||
|
#define _X(name) json[#name] = name
|
||||||
|
QJsonObject toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject json;
|
||||||
|
_X(port);
|
||||||
|
_X(address);
|
||||||
|
_X(password);
|
||||||
|
_X(sni);
|
||||||
|
_X(ignoreCertificate);
|
||||||
|
_X(ignoreHostname);
|
||||||
|
_X(reuseSession);
|
||||||
|
_X(reusePort);
|
||||||
|
_X(sessionTicket);
|
||||||
|
_X(tcpFastOpen);
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
#undef _X
|
||||||
|
|
||||||
|
#define _X(name, type) name = root[#name].to##type()
|
||||||
|
void loadJson(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
_X(port, Int);
|
||||||
|
_X(address, String);
|
||||||
|
_X(password, String);
|
||||||
|
_X(sni, String);
|
||||||
|
_X(ignoreHostname, Bool);
|
||||||
|
_X(ignoreCertificate, Bool);
|
||||||
|
_X(reuseSession, Bool);
|
||||||
|
_X(reusePort, Bool);
|
||||||
|
_X(sessionTicket, Bool);
|
||||||
|
_X(tcpFastOpen, Bool);
|
||||||
|
}
|
||||||
|
#undef _X
|
||||||
|
|
||||||
|
[[nodiscard]] static TrojanObject fromJson(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
TrojanObject o;
|
||||||
|
o.loadJson(root);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StreamSettingsObject
|
||||||
|
{
|
||||||
|
QString network = "tcp";
|
||||||
|
QString security = "none";
|
||||||
|
transfer::SockoptObject sockopt;
|
||||||
|
transfer::TLSObject tlsSettings;
|
||||||
|
transfer::XTLSObject xtlsSettings;
|
||||||
|
transfer::TCPObject tcpSettings;
|
||||||
|
transfer::KCPObject kcpSettings;
|
||||||
|
transfer::WebSocketObject wsSettings;
|
||||||
|
transfer::HttpObject httpSettings;
|
||||||
|
transfer::DomainSocketObject dsSettings;
|
||||||
|
transfer::QuicObject quicSettings;
|
||||||
|
transfer::gRPCObject grpcSettings;
|
||||||
|
JSONSTRUCT_COMPARE(StreamSettingsObject, network, security, sockopt, //
|
||||||
|
tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings)
|
||||||
|
JSONSTRUCT_REGISTER(StreamSettingsObject, F(network, security, sockopt),
|
||||||
|
F(tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings))
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //TRANSFER_H
|
||||||
271
trojan.cpp
Normal file
271
trojan.cpp
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::trojan
|
||||||
|
{
|
||||||
|
|
||||||
|
const QString Serialize(const TrojanObject &object, const QString &alias)
|
||||||
|
{
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
if (object.ignoreHostname)
|
||||||
|
query.addQueryItem("allowInsecureHostname", "1");
|
||||||
|
if (object.ignoreCertificate)
|
||||||
|
query.addQueryItem("allowInsecureCertificate", "1");
|
||||||
|
if (object.sessionTicket)
|
||||||
|
query.addQueryItem("sessionTicket", "1");
|
||||||
|
if (object.ignoreCertificate || object.ignoreHostname)
|
||||||
|
query.addQueryItem("allowInsecure", "1");
|
||||||
|
if (object.tcpFastOpen)
|
||||||
|
query.addQueryItem("tfo", "1");
|
||||||
|
|
||||||
|
if (!object.sni.isEmpty())
|
||||||
|
query.addQueryItem("sni", object.sni);
|
||||||
|
|
||||||
|
QUrl link;
|
||||||
|
if (!object.password.isEmpty())
|
||||||
|
link.setUserName(object.password, QUrl::DecodedMode);
|
||||||
|
link.setPort(object.port);
|
||||||
|
link.setHost(object.address);
|
||||||
|
link.setFragment(alias);
|
||||||
|
link.setQuery(query);
|
||||||
|
link.setScheme("trojan");
|
||||||
|
|
||||||
|
return link.toString(QUrl::FullyEncoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject Deserialize(const QString &trojanUri, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
const QString prefix = "trojan://";
|
||||||
|
if (!trojanUri.startsWith(prefix))
|
||||||
|
{
|
||||||
|
*errMessage = ("Invalid Trojan URI");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//
|
||||||
|
const auto trueList = QStringList{ "true", "1", "yes", "y" };
|
||||||
|
const QUrl trojanUrl(trojanUri.trimmed());
|
||||||
|
const QUrlQuery query(trojanUrl.query());
|
||||||
|
*alias = trojanUrl.fragment(QUrl::FullyDecoded);
|
||||||
|
|
||||||
|
auto getQueryValue = [&](const QString &key) {
|
||||||
|
return query.queryItemValue(key, QUrl::FullyDecoded);
|
||||||
|
};
|
||||||
|
//
|
||||||
|
TrojanObject result;
|
||||||
|
result.address = trojanUrl.host();
|
||||||
|
result.password = QUrl::fromPercentEncoding(trojanUrl.userInfo().toUtf8());
|
||||||
|
result.port = trojanUrl.port();
|
||||||
|
// process sni (and also "peer")
|
||||||
|
if (query.hasQueryItem("sni"))
|
||||||
|
{
|
||||||
|
result.sni = getQueryValue("sni");
|
||||||
|
}
|
||||||
|
else if (query.hasQueryItem("peer"))
|
||||||
|
{
|
||||||
|
// This is evil and may be removed in a future version.
|
||||||
|
qWarning() << "use of 'peer' in trojan url is deprecated";
|
||||||
|
result.sni = getQueryValue("peer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use the hostname
|
||||||
|
result.sni = result.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
result.tcpFastOpen = trueList.contains(getQueryValue("tfo").toLower());
|
||||||
|
result.sessionTicket = trueList.contains(getQueryValue("sessionTicket").toLower());
|
||||||
|
//
|
||||||
|
bool allowAllInsecure = trueList.contains(getQueryValue("allowInsecure").toLower());
|
||||||
|
result.ignoreHostname = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureHostname").toLower());
|
||||||
|
result.ignoreCertificate = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureCertificate").toLower());
|
||||||
|
|
||||||
|
QJsonObject stream;
|
||||||
|
// handle type
|
||||||
|
const auto hasType = query.hasQueryItem("type");
|
||||||
|
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||||
|
if (type != "tcp")
|
||||||
|
QJsonIO::SetValue(stream, type, "network");
|
||||||
|
|
||||||
|
|
||||||
|
// type-wise settings
|
||||||
|
if (type == "kcp")
|
||||||
|
{
|
||||||
|
const auto hasSeed = query.hasQueryItem("seed");
|
||||||
|
if (hasSeed)
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
else if (type == "http")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||||
|
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "ws")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "quic")
|
||||||
|
{
|
||||||
|
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||||
|
if (hasQuicSecurity)
|
||||||
|
{
|
||||||
|
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||||
|
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||||
|
|
||||||
|
if (quicSecurity != "none")
|
||||||
|
{
|
||||||
|
const auto key = query.queryItemValue("key");
|
||||||
|
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "grpc")
|
||||||
|
{
|
||||||
|
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||||
|
if (hasServiceName)
|
||||||
|
{
|
||||||
|
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasMode = query.hasQueryItem("mode");
|
||||||
|
if (hasMode)
|
||||||
|
{
|
||||||
|
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||||
|
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tls-wise settings
|
||||||
|
const auto hasSecurity = query.hasQueryItem("security");
|
||||||
|
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||||
|
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||||
|
if (security != "none")
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, security, "security");
|
||||||
|
}
|
||||||
|
// sni
|
||||||
|
const auto hasSNI = query.hasQueryItem("sni");
|
||||||
|
if (hasSNI)
|
||||||
|
{
|
||||||
|
const auto sni = query.queryItemValue("sni");
|
||||||
|
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||||
|
}
|
||||||
|
// alpn
|
||||||
|
const auto hasALPN = query.hasQueryItem("alpn");
|
||||||
|
if (hasALPN)
|
||||||
|
{
|
||||||
|
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||||
|
QStringList aplnElems = alpnRaw.split(",");
|
||||||
|
// h2 protocol is not supported by xray
|
||||||
|
aplnElems.removeAll("h2");
|
||||||
|
if (!aplnElems.isEmpty()) {
|
||||||
|
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||||
|
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (security == "reality")
|
||||||
|
{
|
||||||
|
if (query.hasQueryItem("fp"))
|
||||||
|
{
|
||||||
|
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("pbk"))
|
||||||
|
{
|
||||||
|
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("spiderX"))
|
||||||
|
{
|
||||||
|
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("sid"))
|
||||||
|
{
|
||||||
|
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
QJsonObject outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "trojan", outbounds::GenerateTrojanOUT({ result }), {});
|
||||||
|
outbound["streamSettings"] = stream;
|
||||||
|
outbounds.append(outbound);
|
||||||
|
JADD(outbounds)
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::trojan
|
||||||
|
|
||||||
256
vless.cpp
Normal file
256
vless.cpp
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::vless
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
// must start with vless://
|
||||||
|
if (!str.startsWith("vless://"))
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("VLESS link should start with vless://");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse url
|
||||||
|
QUrl url(str);
|
||||||
|
if (!url.isValid())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("link parse failed: %1").arg(url.errorString());
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch host
|
||||||
|
const auto hostRaw = url.host();
|
||||||
|
if (hostRaw.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("empty host");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
const auto host = (hostRaw.startsWith('[') && hostRaw.endsWith(']')) ? hostRaw.mid(1, hostRaw.length() - 2) : hostRaw;
|
||||||
|
|
||||||
|
// fetch port
|
||||||
|
const auto port = url.port();
|
||||||
|
if (port == -1)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("missing port");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch remarks
|
||||||
|
const auto remarks = url.fragment();
|
||||||
|
if (!remarks.isEmpty())
|
||||||
|
{
|
||||||
|
*alias = remarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch uuid
|
||||||
|
const auto uuid = url.userInfo();
|
||||||
|
if (uuid.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("missing uuid");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize QJsonObject with basic info
|
||||||
|
QJsonObject outbound;
|
||||||
|
QJsonObject stream;
|
||||||
|
|
||||||
|
QJsonIO::SetValue(outbound, "vless", "protocol");
|
||||||
|
QJsonIO::SetValue(outbound, host, { "settings", "vnext", 0, "address" });
|
||||||
|
QJsonIO::SetValue(outbound, port, { "settings", "vnext", 0, "port" });
|
||||||
|
QJsonIO::SetValue(outbound, uuid, { "settings", "vnext", 0, "users", 0, "id" });
|
||||||
|
|
||||||
|
// parse query
|
||||||
|
QUrlQuery query(url.query());
|
||||||
|
|
||||||
|
// handle type
|
||||||
|
const auto hasType = query.hasQueryItem("type");
|
||||||
|
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||||
|
if (type != "tcp")
|
||||||
|
QJsonIO::SetValue(stream, type, "network");
|
||||||
|
|
||||||
|
// handle encryption
|
||||||
|
const auto hasEncryption = query.hasQueryItem("encryption");
|
||||||
|
const auto encryption = hasEncryption ? query.queryItemValue("encryption") : "none";
|
||||||
|
QJsonIO::SetValue(outbound, encryption, { "settings", "vnext", 0, "users", 0, "encryption" });
|
||||||
|
|
||||||
|
// type-wise settings
|
||||||
|
if (type == "kcp")
|
||||||
|
{
|
||||||
|
const auto hasSeed = query.hasQueryItem("seed");
|
||||||
|
if (hasSeed)
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
else if (type == "http")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||||
|
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "ws")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "quic")
|
||||||
|
{
|
||||||
|
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||||
|
if (hasQuicSecurity)
|
||||||
|
{
|
||||||
|
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||||
|
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||||
|
|
||||||
|
if (quicSecurity != "none")
|
||||||
|
{
|
||||||
|
const auto key = query.queryItemValue("key");
|
||||||
|
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "grpc")
|
||||||
|
{
|
||||||
|
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||||
|
if (hasServiceName)
|
||||||
|
{
|
||||||
|
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasMode = query.hasQueryItem("mode");
|
||||||
|
if (hasMode)
|
||||||
|
{
|
||||||
|
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||||
|
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tls-wise settings
|
||||||
|
const auto hasSecurity = query.hasQueryItem("security");
|
||||||
|
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||||
|
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||||
|
if (security != "none")
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, security, "security");
|
||||||
|
}
|
||||||
|
// sni
|
||||||
|
const auto hasSNI = query.hasQueryItem("sni");
|
||||||
|
if (hasSNI)
|
||||||
|
{
|
||||||
|
const auto sni = query.queryItemValue("sni");
|
||||||
|
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||||
|
}
|
||||||
|
// alpn
|
||||||
|
const auto hasALPN = query.hasQueryItem("alpn");
|
||||||
|
if (hasALPN)
|
||||||
|
{
|
||||||
|
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||||
|
QStringList aplnElems = alpnRaw.split(",");
|
||||||
|
// h2 protocol is not supported by xray
|
||||||
|
aplnElems.removeAll("h2");
|
||||||
|
if (!aplnElems.isEmpty()) {
|
||||||
|
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||||
|
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// xtls-specific
|
||||||
|
if (security == "xtls" || security == "reality")
|
||||||
|
{
|
||||||
|
const auto flow = query.queryItemValue("flow");
|
||||||
|
QJsonIO::SetValue(outbound, flow, { "settings", "vnext", 0, "users", 0, "flow" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (security == "reality")
|
||||||
|
{
|
||||||
|
if (query.hasQueryItem("fp"))
|
||||||
|
{
|
||||||
|
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("pbk"))
|
||||||
|
{
|
||||||
|
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("spiderX"))
|
||||||
|
{
|
||||||
|
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("sid"))
|
||||||
|
{
|
||||||
|
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assembling config
|
||||||
|
QJsonObject root;
|
||||||
|
outbound["streamSettings"] = stream;
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["outbounds"] = QJsonArray{ outbound };
|
||||||
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::vless
|
||||||
|
|
||||||
344
vmess.cpp
Normal file
344
vmess.cpp
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define nothing
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::vmess
|
||||||
|
{
|
||||||
|
// From https://github.com/2dust/v2rayN/wiki/分享链接格式说明(ver-2)
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias)
|
||||||
|
{
|
||||||
|
QJsonObject vmessUriRoot;
|
||||||
|
// Constant
|
||||||
|
vmessUriRoot["v"] = 2;
|
||||||
|
vmessUriRoot["ps"] = alias;
|
||||||
|
vmessUriRoot["add"] = server.address;
|
||||||
|
vmessUriRoot["port"] = server.port;
|
||||||
|
vmessUriRoot["id"] = server.users.front().id;
|
||||||
|
vmessUriRoot["aid"] = server.users.front().alterId;
|
||||||
|
const auto scy = server.users.front().security;
|
||||||
|
vmessUriRoot["scy"] = (scy == "aes-128-gcm" || scy == "chacha20-poly1305" || scy == "none" || scy == "zero") ? scy : "auto";
|
||||||
|
vmessUriRoot["net"] = transfer.network == "http" ? "h2" : transfer.network;
|
||||||
|
vmessUriRoot["tls"] = (transfer.security == "tls" || transfer.security == "xtls") ? "tls" : "none";
|
||||||
|
if (transfer.security == "tls")
|
||||||
|
{
|
||||||
|
vmessUriRoot["sni"] = transfer.tlsSettings.serverName;
|
||||||
|
}
|
||||||
|
else if (transfer.security == "xtls")
|
||||||
|
{
|
||||||
|
vmessUriRoot["sni"] = transfer.xtlsSettings.serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transfer.network == "tcp")
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = transfer.tcpSettings.header.type;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "kcp")
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = transfer.kcpSettings.header.type;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "quic")
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = transfer.quicSettings.header.type;
|
||||||
|
vmessUriRoot["host"] = transfer.quicSettings.security;
|
||||||
|
vmessUriRoot["path"] = transfer.quicSettings.key;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "ws")
|
||||||
|
{
|
||||||
|
auto x = transfer.wsSettings.headers;
|
||||||
|
auto host = x.contains("host");
|
||||||
|
auto CapHost = x.contains("Host");
|
||||||
|
auto realHost = host ? x["host"] : (CapHost ? x["Host"] : "");
|
||||||
|
//
|
||||||
|
vmessUriRoot["host"] = realHost;
|
||||||
|
vmessUriRoot["path"] = transfer.wsSettings.path;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "h2" || transfer.network == "http")
|
||||||
|
{
|
||||||
|
vmessUriRoot["host"] = transfer.httpSettings.host.join(",");
|
||||||
|
vmessUriRoot["path"] = transfer.httpSettings.path;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "grpc")
|
||||||
|
{
|
||||||
|
vmessUriRoot["path"] = transfer.grpcSettings.serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vmessUriRoot.contains("type") || vmessUriRoot["type"].toString().isEmpty())
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
QString jString = Utils::JsonToString(vmessUriRoot, QJsonDocument::JsonFormat::Compact);
|
||||||
|
auto vmessPart = jString.toUtf8().toBase64();
|
||||||
|
return "vmess://" + vmessPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This generates global config containing only one outbound....
|
||||||
|
QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
#define default QJsonObject()
|
||||||
|
QString vmess = vmessStr;
|
||||||
|
|
||||||
|
if (vmess.trimmed() != vmess)
|
||||||
|
{
|
||||||
|
vmess = vmessStr.trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset errMessage
|
||||||
|
*errMessage = "";
|
||||||
|
|
||||||
|
if (!vmess.toLower().startsWith("vmess://"))
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("VMess string should start with 'vmess://'");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto b64Str = vmess.mid(8, vmess.length() - 8);
|
||||||
|
if (b64Str.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("VMess string should be a valid base64 string");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vmessString = Utils::SafeBase64Decode(b64Str);
|
||||||
|
auto jsonErr = Utils::VerifyJsonString(vmessString);
|
||||||
|
|
||||||
|
if (!jsonErr.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = jsonErr;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vmessConf = Utils::JsonFromString(vmessString);
|
||||||
|
|
||||||
|
if (vmessConf.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("JSON should not be empty");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
QJsonObject root;
|
||||||
|
QString ps, add, id, net, type, host, path, tls, scy, sni;
|
||||||
|
int port, aid;
|
||||||
|
//
|
||||||
|
// __vmess_checker__func(key, values)
|
||||||
|
//
|
||||||
|
// - Key = Key in JSON and the variable name.
|
||||||
|
// - Values = Candidate variable list, if not match, the first one is used as default.
|
||||||
|
//
|
||||||
|
// - [[val.size() <= 1]] is used when only the default value exists.
|
||||||
|
//
|
||||||
|
// - It can be empty, if so, if the key is not in the JSON, or the value is empty, report an error.
|
||||||
|
// - Else if it contains one thing. if the key is not in the JSON, or the value is empty, use that one.
|
||||||
|
// - Else if it contains many things, when the key IS in the JSON but not within the THINGS, use the first in the THINGS
|
||||||
|
// - Else -------------------------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> use the JSON value
|
||||||
|
//
|
||||||
|
#define __vmess_checker__func(key, values) \
|
||||||
|
{ \
|
||||||
|
auto val = QStringList() values; \
|
||||||
|
if (vmessConf.contains(#key) && !vmessConf[#key].toVariant().toString().trimmed().isEmpty() && \
|
||||||
|
(val.size() <= 1 || val.contains(vmessConf[#key].toVariant().toString()))) \
|
||||||
|
{ \
|
||||||
|
key = vmessConf[#key].toVariant().toString(); \
|
||||||
|
} \
|
||||||
|
else if (!val.isEmpty()) \
|
||||||
|
{ \
|
||||||
|
key = val.first(); \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
*errMessage = QObject::tr(#key " does not exist."); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
// vmess v1 upgrader
|
||||||
|
if (!vmessConf.contains("v"))
|
||||||
|
{
|
||||||
|
qDebug() << "Detected deprecated vmess v1. Trying to upgrade...";
|
||||||
|
if (const auto network = vmessConf["net"].toString(); network == "ws" || network == "h2")
|
||||||
|
{
|
||||||
|
const QStringList hostComponents = vmessConf["host"].toString().replace(" ", "").split(";");
|
||||||
|
if (const auto nParts = hostComponents.length(); nParts == 1)
|
||||||
|
vmessConf["path"] = hostComponents[0], vmessConf["host"] = "";
|
||||||
|
else if (nParts == 2)
|
||||||
|
vmessConf["path"] = hostComponents[0], vmessConf["host"] = hostComponents[1];
|
||||||
|
else
|
||||||
|
vmessConf["path"] = "/", vmessConf["host"] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strict check of VMess protocol, to check if the specified value
|
||||||
|
// is in the correct range.
|
||||||
|
//
|
||||||
|
// Get Alias (AKA ps) from address and port.
|
||||||
|
{
|
||||||
|
// Some idiot vmess:// links are using alterId...
|
||||||
|
aid = vmessConf.contains("aid") ? vmessConf.value("aid").toInt(VMESS_USER_ALTERID_DEFAULT) :
|
||||||
|
vmessConf.value("alterId").toInt(VMESS_USER_ALTERID_DEFAULT);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
__vmess_checker__func(ps, << vmessConf["add"].toVariant().toString() + ":" + vmessConf["port"].toVariant().toString()); //
|
||||||
|
__vmess_checker__func(add, nothing); //
|
||||||
|
__vmess_checker__func(id, nothing); //
|
||||||
|
__vmess_checker__func(scy, << "aes-128-gcm" //
|
||||||
|
<< "chacha20-poly1305" //
|
||||||
|
<< "auto" //
|
||||||
|
<< "none" //
|
||||||
|
<< "zero"); //
|
||||||
|
//
|
||||||
|
__vmess_checker__func(type, << "none" //
|
||||||
|
<< "http" //
|
||||||
|
<< "srtp" //
|
||||||
|
<< "utp" //
|
||||||
|
<< "wechat-video"); //
|
||||||
|
//
|
||||||
|
__vmess_checker__func(net, << "tcp" //
|
||||||
|
<< "http" //
|
||||||
|
<< "h2" //
|
||||||
|
<< "ws" //
|
||||||
|
<< "kcp" //
|
||||||
|
<< "quic" //
|
||||||
|
<< "grpc"); //
|
||||||
|
//
|
||||||
|
__vmess_checker__func(tls, << "none" //
|
||||||
|
<< "tls"); //
|
||||||
|
//
|
||||||
|
path = vmessConf.contains("path") ? vmessConf["path"].toVariant().toString() : (net == "quic" ? "" : "/");
|
||||||
|
host = vmessConf.contains("host") ? vmessConf["host"].toVariant().toString() : (net == "quic" ? "none" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respect connection type rather than obfs type
|
||||||
|
if (QStringList{ "srtp", "utp", "wechat-video" }.contains(type)) //
|
||||||
|
{ //
|
||||||
|
if (net != "quic" && net != "kcp") //
|
||||||
|
{ //
|
||||||
|
type = "none"; //
|
||||||
|
} //
|
||||||
|
}
|
||||||
|
|
||||||
|
port = vmessConf["port"].toVariant().toInt();
|
||||||
|
aid = vmessConf["aid"].toVariant().toInt();
|
||||||
|
//
|
||||||
|
// Apply the settings.
|
||||||
|
// User
|
||||||
|
VMessServerObject::UserObject user;
|
||||||
|
user.id = id;
|
||||||
|
user.alterId = aid;
|
||||||
|
user.security = scy;
|
||||||
|
//
|
||||||
|
// Server
|
||||||
|
VMessServerObject serv;
|
||||||
|
serv.port = port;
|
||||||
|
serv.address = add;
|
||||||
|
serv.users.push_back(user);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Stream Settings
|
||||||
|
StreamSettingsObject streaming;
|
||||||
|
|
||||||
|
if (net == "tcp")
|
||||||
|
{
|
||||||
|
streaming.tcpSettings.header.type = type;
|
||||||
|
}
|
||||||
|
else if (net == "http" || net == "h2")
|
||||||
|
{
|
||||||
|
// Fill hosts for HTTP
|
||||||
|
for (const auto &_host : host.split(','))
|
||||||
|
{
|
||||||
|
if (!_host.isEmpty())
|
||||||
|
{
|
||||||
|
streaming.httpSettings.host << _host.trimmed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
streaming.httpSettings.path = path;
|
||||||
|
}
|
||||||
|
else if (net == "ws")
|
||||||
|
{
|
||||||
|
if (!host.isEmpty())
|
||||||
|
streaming.wsSettings.headers["Host"] = host;
|
||||||
|
streaming.wsSettings.path = path;
|
||||||
|
}
|
||||||
|
else if (net == "kcp")
|
||||||
|
{
|
||||||
|
streaming.kcpSettings.header.type = type;
|
||||||
|
}
|
||||||
|
else if (net == "quic")
|
||||||
|
{
|
||||||
|
streaming.quicSettings.security = host;
|
||||||
|
streaming.quicSettings.header.type = type;
|
||||||
|
streaming.quicSettings.key = path;
|
||||||
|
}
|
||||||
|
else if (net == "grpc")
|
||||||
|
{
|
||||||
|
streaming.grpcSettings.serviceName = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
streaming.security = tls;
|
||||||
|
if (tls == "tls")
|
||||||
|
{
|
||||||
|
if (sni.isEmpty() && !host.isEmpty())
|
||||||
|
sni = host;
|
||||||
|
streaming.tlsSettings.serverName = sni;
|
||||||
|
streaming.tlsSettings.allowInsecure = false;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Network type
|
||||||
|
// NOTE(DuckSoft): Damn vmess:// just don't write 'http' properly
|
||||||
|
if (net == "h2")
|
||||||
|
net = "http";
|
||||||
|
streaming.network = net;
|
||||||
|
//
|
||||||
|
// VMess root config
|
||||||
|
QJsonObject vConf;
|
||||||
|
vConf["vnext"] = QJsonArray{ serv.toJson() };
|
||||||
|
const auto outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "vmess", vConf, streaming.toJson());
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["outbounds"] = QJsonArray{ outbound };
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
// If previous alias is empty, just the PS is needed, else, append a "_"
|
||||||
|
*alias = alias->trimmed().isEmpty() ? ps : *alias + "_" + ps;
|
||||||
|
return root;
|
||||||
|
#undef default
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::vmess
|
||||||
|
|
||||||
172
vmess_new.cpp
Normal file
172
vmess_new.cpp
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::vmess_new
|
||||||
|
{
|
||||||
|
const static QStringList NetworkType{ "tcp", "http", "ws", "kcp", "quic", "grpc" };
|
||||||
|
const static QStringList QuicSecurityTypes{ "none", "aes-128-gcm", "chacha20-poly1305" };
|
||||||
|
const static QStringList QuicKcpHeaderTypes{ "none", "srtp", "utp", "wechat-video", "dtls", "wireguard" };
|
||||||
|
const static QStringList FalseTypes{ "false", "False", "No", "Off", "0" };
|
||||||
|
|
||||||
|
QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
QUrl url{ vmessStr };
|
||||||
|
QUrlQuery query{ url };
|
||||||
|
//
|
||||||
|
#define default QJsonObject()
|
||||||
|
if (!url.isValid())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("vmess:// url is invalid");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If previous alias is empty, just the PS is needed, else, append a "_"
|
||||||
|
const auto name = url.fragment(QUrl::FullyDecoded).trimmed();
|
||||||
|
*alias = alias->isEmpty() ? name : (*alias + "_" + name);
|
||||||
|
|
||||||
|
VMessServerObject server;
|
||||||
|
server.users << VMessServerObject::UserObject{};
|
||||||
|
|
||||||
|
StreamSettingsObject stream;
|
||||||
|
QString net;
|
||||||
|
bool tls = false;
|
||||||
|
// Check streamSettings
|
||||||
|
{
|
||||||
|
for (const auto &_protocol : url.userName().split("+"))
|
||||||
|
{
|
||||||
|
if (_protocol == "tls")
|
||||||
|
tls = true;
|
||||||
|
else
|
||||||
|
net = _protocol;
|
||||||
|
}
|
||||||
|
if (!NetworkType.contains(net))
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Invalid streamSettings protocol: ") + net;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
stream.network = net;
|
||||||
|
stream.security = tls ? "tls" : "";
|
||||||
|
}
|
||||||
|
// Host Port UUID AlterID
|
||||||
|
{
|
||||||
|
const auto host = url.host();
|
||||||
|
int port = url.port();
|
||||||
|
QString uuid;
|
||||||
|
int aid;
|
||||||
|
{
|
||||||
|
const auto pswd = url.password();
|
||||||
|
const auto index = pswd.lastIndexOf("-");
|
||||||
|
uuid = pswd.mid(0, index);
|
||||||
|
aid = pswd.right(pswd.length() - index - 1).toInt();
|
||||||
|
}
|
||||||
|
server.address = host;
|
||||||
|
server.port = port;
|
||||||
|
server.users.first().id = uuid;
|
||||||
|
server.users.first().alterId = aid;
|
||||||
|
server.users.first().security = "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
|
||||||
|
if (query.hasQueryItem(key))
|
||||||
|
return query.queryItemValue(key, QUrl::FullyDecoded);
|
||||||
|
else
|
||||||
|
return defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Begin transport settings parser
|
||||||
|
{
|
||||||
|
if (net == "tcp")
|
||||||
|
{
|
||||||
|
stream.tcpSettings.header.type = getQueryValue("type", "none");
|
||||||
|
}
|
||||||
|
else if (net == "http")
|
||||||
|
{
|
||||||
|
stream.httpSettings.host.append(getQueryValue("host", ""));
|
||||||
|
stream.httpSettings.path = getQueryValue("path", "/");
|
||||||
|
}
|
||||||
|
else if (net == "ws")
|
||||||
|
{
|
||||||
|
stream.wsSettings.headers["Host"] = getQueryValue("host", "");
|
||||||
|
stream.wsSettings.path = getQueryValue("path", "/");
|
||||||
|
}
|
||||||
|
else if (net == "kcp")
|
||||||
|
{
|
||||||
|
stream.kcpSettings.seed = getQueryValue("seed", "");
|
||||||
|
stream.kcpSettings.header.type = getQueryValue("type", "none");
|
||||||
|
}
|
||||||
|
else if (net == "quic")
|
||||||
|
{
|
||||||
|
stream.quicSettings.security = getQueryValue("security", "none");
|
||||||
|
stream.quicSettings.key = getQueryValue("key", "");
|
||||||
|
stream.quicSettings.header.type = getQueryValue("type", "none");
|
||||||
|
}
|
||||||
|
else if (net == "grpc")
|
||||||
|
{
|
||||||
|
stream.grpcSettings.serviceName = getQueryValue("serviceName", "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Unknown transport method: ") + net;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef default
|
||||||
|
if (tls)
|
||||||
|
{
|
||||||
|
stream.tlsSettings.allowInsecure = !FalseTypes.contains(getQueryValue("allowInsecure", "false"));
|
||||||
|
stream.tlsSettings.serverName = getQueryValue("tlsServerName", "");
|
||||||
|
}
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonObject vConf;
|
||||||
|
QJsonArray vnextArray;
|
||||||
|
vnextArray.append(server.toJson());
|
||||||
|
vConf["vnext"] = vnextArray;
|
||||||
|
auto outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "vmess", vConf, stream.toJson());
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
|
||||||
|
//
|
||||||
|
root["outbounds"] = QJsonArray{ outbound };
|
||||||
|
root["inbound"] = QJsonArray{ inbound };
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::vmess_new
|
||||||
Reference in New Issue
Block a user