diff --git a/Build.Counter b/Build.Counter index 8903fed2..1d3a8c61 100644 --- a/Build.Counter +++ b/Build.Counter @@ -1 +1 @@ -3403 +3423 diff --git a/Qv2ray.pro b/Qv2ray.pro index 7630adb4..9157864f 100644 --- a/Qv2ray.pro +++ b/Qv2ray.pro @@ -120,9 +120,9 @@ Qv2rayAddSource(components, proxy, QvProxyConfigurator, cpp, hpp) Qv2rayAddSource(components, tcping, QvTCPing, cpp, hpp) Qv2rayAddSource(core, config, ConfigBackend, cpp, hpp) Qv2rayAddSource(core, config, ConfigUpgrade, cpp) -Qv2rayAddSource(core, connection, ConnectionConfig_Convertion, cpp) -Qv2rayAddSource(core, connection, ConnectionConfig_Generation, cpp) -Qv2rayAddSource(core, connection, ConnectionConfigOperations, cpp, hpp) +Qv2rayAddSource(core, connection, ConnectionIO, cpp, hpp) +Qv2rayAddSource(core, connection, Generation, cpp, hpp) +Qv2rayAddSource(core, connection, Serialization, cpp, hpp) Qv2rayAddSource(core, _, CoreUtils, cpp, hpp) Qv2rayAddSource(core, kernel, QvKernelInteractions, cpp, hpp) Qv2rayAddSource(ui, editors, w_InboundEditor, cpp, hpp, ui) diff --git a/src/base/Qv2rayBase.hpp b/src/base/Qv2rayBase.hpp index 92602efd..dc2a87ea 100644 --- a/src/base/Qv2rayBase.hpp +++ b/src/base/Qv2rayBase.hpp @@ -93,6 +93,18 @@ extern const bool isDebugBuild; #define QSTRN(num) QString::number(num) + +#define OUTBOUND_TAG_DIRECT "outBound_DIRECT" +#define OUTBOUND_TAG_PROXY "outBound_PROXY" +#define OUTBOUND_TAG_FORWARD_PROXY "_QV2RAY_FORWARD_PROXY_" + +#define API_TAG_DEFAULT "_QV2RAY_API_" +#define API_TAG_INBOUND "_QV2RAY_API_INBOUND_" + +#define QV2RAY_USE_FPROXY_KEY "_QV2RAY_USE_GLOBAL_FORWARD_PROXY_" + +#define JSON_ROOT_TRY_REMOVE(obj) if (root.contains(obj)) { root.remove(obj); } + namespace Qv2ray { // Extra header for QvConfigUpgrade.cpp diff --git a/src/core/CoreUtils.cpp b/src/core/CoreUtils.cpp index 9503f709..59d042c7 100644 --- a/src/core/CoreUtils.cpp +++ b/src/core/CoreUtils.cpp @@ -53,5 +53,13 @@ namespace Qv2ray::core return false; } } + + bool CheckIsComplexConfig(CONFIGROOT root) + { + bool cRouting = root.contains("routing"); + bool cRule = cRouting && root["routing"].toObject().contains("rules"); + bool cRules = cRule && root["routing"].toObject()["rules"].toArray().count() > 0; + return cRules; + } } diff --git a/src/core/CoreUtils.hpp b/src/core/CoreUtils.hpp index 4b8e75ab..edbed5f3 100644 --- a/src/core/CoreUtils.hpp +++ b/src/core/CoreUtils.hpp @@ -22,6 +22,7 @@ namespace Qv2ray::core tuple GetConnectionInfo(const CONFIGROOT &alias); bool GetOutboundData(const OUTBOUND &out, QString *host, int *port, QString *protocol); + bool CheckIsComplexConfig(CONFIGROOT root); } using namespace Qv2ray::core; diff --git a/src/core/connection/ConnectionConfigOperations.cpp b/src/core/connection/ConnectionConfigOperations.cpp deleted file mode 100644 index 77a15b1f..00000000 --- a/src/core/connection/ConnectionConfigOperations.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "ConnectionConfigOperations.hpp" -#include "common/QvHelpers.hpp" - -namespace Qv2ray::core::connection -{ - CONFIGROOT _ReadConnection(const QString &connection) - { - QString jsonString = StringFromFile(new QFile(connection)); - auto conf = CONFIGROOT(JsonFromString(jsonString)); - - if (conf.count() == 0) { - LOG(MODULE_CONFIG, "WARN: Possible file corruption, failed to load file: " + connection + " --> File might be empty.") - } - - return conf; - } - - QMap GetRegularConnections(QStringList connectionNames) - { - QMap list; - - for (auto conn : connectionNames) { - list.insert(conn, _ReadConnection(QV2RAY_CONFIG_DIR + conn + QV2RAY_CONFIG_FILE_EXTENSION)); - } - - return list; - } - - QMap GetSubscriptionConnection(QString subscription) - { - auto _files = GetFileList(QV2RAY_SUBSCRIPTION_DIR + subscription); - QMap _config; - - for (auto _file : _files) { - // check if is proper connection file. - if (_file.endsWith(QV2RAY_CONFIG_FILE_EXTENSION)) { - auto confName = _file; - // Remove the extension - confName.chop(sizeof(QV2RAY_CONFIG_FILE_EXTENSION) - 1); - _config[confName] = _ReadConnection(QV2RAY_SUBSCRIPTION_DIR + subscription + "/" + _file); - } else { - LOG(MODULE_SUBSCRIPTION, "Found a file in subscription folder but without proper suffix: " + _file) - } - } - - if (_config.isEmpty()) { - LOG(MODULE_SUBSCRIPTION, "WARN: Maybe loading an empty subscrption: " + subscription) - } - - return _config; - } - - QMap> GetSubscriptionConnections(QStringList subscriptions) - { - // SUB-NAME CONN-NAME CONN-ROOT - QMap> list; - - for (auto singleSub : subscriptions) { - LOG(MODULE_SUBSCRIPTION, "Processing subscription: " + singleSub) - list[singleSub] = GetSubscriptionConnection(singleSub); - } - - return list; - } - bool CheckIsComplexConfig(CONFIGROOT root) - { - bool cRouting = root.contains("routing"); - bool cRule = cRouting && root["routing"].toObject().contains("rules"); - bool cRules = cRule && root["routing"].toObject()["rules"].toArray().count() > 0; - return cRules; - } -} diff --git a/src/core/connection/ConnectionConfigOperations.hpp b/src/core/connection/ConnectionConfigOperations.hpp deleted file mode 100644 index bb56b024..00000000 --- a/src/core/connection/ConnectionConfigOperations.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include "base/Qv2rayBase.hpp" - -#define OUTBOUND_TAG_DIRECT "outBound_DIRECT" -#define OUTBOUND_TAG_PROXY "outBound_PROXY" -#define OUTBOUND_TAG_FORWARD_PROXY "_QV2RAY_FORWARD_PROXY_" - -#define API_TAG_DEFAULT "_QV2RAY_API_" -#define API_TAG_INBOUND "_QV2RAY_API_INBOUND_" - -#define QV2RAY_USE_FPROXY_KEY "_QV2RAY_USE_GLOBAL_FORWARD_PROXY_" - -#define JSON_ROOT_TRY_REMOVE(obj) if (root.contains(obj)) { root.remove(obj); } - -namespace Qv2ray::core::connection -{ - // -------------------------- BEGIN GENERAL FUNCTIONS ---------------------------------------------- - - QMap GetRegularConnections(QStringList connections); - QMap GetSubscriptionConnection(QString subscription); - QMap> GetSubscriptionConnections(QStringList subscriptions); - bool CheckIsComplexConfig(CONFIGROOT root); - - // - // -------------------------- BEGIN CONFIG CONVERSIONS -------------------------- - inline namespace Convertion - { - //int VerifyVMessProtocolString(QString vmess); - QString DecodeSubscriptionString(QByteArray arr); - // - // Save Connection Config - bool SaveConnectionConfig(CONFIGROOT obj, QString *alias, bool canOverrideExisting); - bool SaveSubscriptionConfig(CONFIGROOT obj, const QString &subscription, QString *name); - bool RemoveConnection(const QString &alias); - bool RemoveSubscriptionConnection(const QString &subsName, const QString &name); - bool RenameConnection(const QString &originalName, const QString &newName); - bool RenameSubscription(const QString &originalName, const QString &newName); - - // VMess URI Protocol - CONFIGROOT ConvertConfigFromVMessString(const QString &vmess, QString *alias, QString *errMessage); - CONFIGROOT ConvertConfigFromFile(QString sourceFilePath, bool keepInbounds); - QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &serverConfig, const QString &alias); - } - - // - // -------------------------- BEGIN CONFIG GENERATIONS --------------------------------------------- - inline namespace Generation - { - ROUTING GenerateRoutes(bool enableProxy, bool cnProxy); - ROUTERULE GenerateSingleRouteRule(QStringList list, bool isDomain, QString outboundTag, QString type = "field"); - QJsonObject GenerateDNS(bool withLocalhost, QStringList dnsServers); - QJsonObject GenerateAPIEntry(QString tag, bool withHandler = true, bool withLogger = true, bool withStats = true); - // - // Outbound Protocols - OUTBOUNDSETTING GenerateFreedomOUT(QString domainStrategy, QString redirect, int userLevel); - OUTBOUNDSETTING GenerateBlackHoleOUT(bool useHTTP); - OUTBOUNDSETTING GenerateShadowSocksServerOUT(QString email, QString address, int port, QString method, QString password, bool ota, int level); - OUTBOUNDSETTING GenerateShadowSocksOUT(QList servers); - OUTBOUNDSETTING GenerateHTTPSOCKSOut(QString address, int port, bool useAuth, QString username, QString password); - // - // Inbounds Protocols - INBOUNDSETTING GenerateDokodemoIN(QString address, int port, QString network, int timeout, bool followRedirect, int userLevel); - INBOUNDSETTING GenerateHTTPIN(QList accounts, int timeout = 300, bool allowTransparent = true, int userLevel = 0); - INBOUNDSETTING GenerateSocksIN(QString auth, QList _accounts, bool udp = false, QString ip = "127.0.0.1", int userLevel = 0); - // - // Generate FINAL Configs - CONFIGROOT GenerateRuntimeConfig(CONFIGROOT root); - OUTBOUND GenerateOutboundEntry(QString protocol, OUTBOUNDSETTING settings, QJsonObject streamSettings, QJsonObject mux = QJsonObject(), QString sendThrough = "0.0.0.0", QString tag = ""); - INBOUND GenerateInboundEntry(QString listen, int port, QString protocol, INBOUNDSETTING settings, QString tag, QJsonObject sniffing = QJsonObject(), QJsonObject allocate = QJsonObject()); - } -} - -using namespace Qv2ray::core; -using namespace Qv2ray::core::connection; diff --git a/src/core/connection/ConnectionIO.cpp b/src/core/connection/ConnectionIO.cpp new file mode 100644 index 00000000..43eae605 --- /dev/null +++ b/src/core/connection/ConnectionIO.cpp @@ -0,0 +1,172 @@ +#include "ConnectionIO.hpp" +#include "common/QvHelpers.hpp" + +namespace Qv2ray::core::connection +{ + namespace ConnectionIO + { + CONFIGROOT _ReadConnection(const QString &connection) + { + QString jsonString = StringFromFile(new QFile(connection)); + auto conf = CONFIGROOT(JsonFromString(jsonString)); + + if (conf.count() == 0) { + LOG(MODULE_CONFIG, "WARN: Possible file corruption, failed to load file: " + connection + " --> File might be empty.") + } + + return conf; + } + + QMap GetRegularConnections(QStringList connectionNames) + { + QMap list; + + for (auto conn : connectionNames) { + list.insert(conn, _ReadConnection(QV2RAY_CONFIG_DIR + conn + QV2RAY_CONFIG_FILE_EXTENSION)); + } + + return list; + } + + QMap GetSubscriptionConnection(QString subscription) + { + auto _files = GetFileList(QV2RAY_SUBSCRIPTION_DIR + subscription); + QMap _config; + + for (auto _file : _files) { + // check if is proper connection file. + if (_file.endsWith(QV2RAY_CONFIG_FILE_EXTENSION)) { + auto confName = _file; + // Remove the extension + confName.chop(sizeof(QV2RAY_CONFIG_FILE_EXTENSION) - 1); + _config[confName] = _ReadConnection(QV2RAY_SUBSCRIPTION_DIR + subscription + "/" + _file); + } else { + LOG(MODULE_SUBSCRIPTION, "Found a file in subscription folder but without proper suffix: " + _file) + } + } + + if (_config.isEmpty()) { + LOG(MODULE_SUBSCRIPTION, "WARN: Maybe loading an empty subscrption: " + subscription) + } + + return _config; + } + + QMap> GetSubscriptionConnections(QStringList subscriptions) + { + // SUB-NAME CONN-NAME CONN-ROOT + QMap> list; + + for (auto singleSub : subscriptions) { + LOG(MODULE_SUBSCRIPTION, "Processing subscription: " + singleSub) + list[singleSub] = GetSubscriptionConnection(singleSub); + } + + return list; + } + + // + // Save Connection to a place, with checking if there's existing file. + // If so, append "_N" to the name. + bool SaveConnectionConfig(CONFIGROOT obj, QString *alias, bool canOverrideExisting) + { + auto str = JsonToString(obj); + QFile *config = new QFile(QV2RAY_CONFIG_DIR + *alias + QV2RAY_CONFIG_FILE_EXTENSION); + + // If there's already a file AND we CANNOT override existing file. + if (config->exists() && !canOverrideExisting) { + // Alias is a pointer to a QString. + DeducePossibleFileName(QV2RAY_CONFIG_DIR, alias, QV2RAY_CONFIG_FILE_EXTENSION); + config = new QFile(QV2RAY_CONFIG_DIR + *alias + QV2RAY_CONFIG_FILE_EXTENSION); + } + + LOG(MODULE_CONFIG, "Saving a config named: " + *alias) + return StringToFile(&str, config); + } + + bool SaveSubscriptionConfig(CONFIGROOT obj, const QString &subscription, QString *name) + { + auto str = JsonToString(obj); + auto fName = *name; + + if (!IsValidFileName(fName)) { + fName = RemoveInvalidFileName(fName); + } + + QFile *config = new QFile(QV2RAY_SUBSCRIPTION_DIR + subscription + "/" + fName + QV2RAY_CONFIG_FILE_EXTENSION); + + // If there's already a file. THIS IS EXTREMELY RARE + if (config->exists()) { + LOG(MODULE_FILE, "Trying to overrwrite an existing subscription config file. THIS IS RARE") + } + + LOG(MODULE_CONFIG, "Saving a subscription named: " + fName) + bool result = StringToFile(&str, config); + + if (!result) { + LOG(MODULE_FILE, "Failed to save a connection config from subscription: " + subscription + ", name: " + fName) + } + + *name = fName; + return result; + } + + bool RemoveConnection(const QString &alias) + { + QFile config(QV2RAY_CONFIG_DIR + alias + QV2RAY_CONFIG_FILE_EXTENSION); + + if (!config.exists()) { + LOG(MODULE_FILE, "Trying to remove a non-existing file?") + return false; + } else { + return config.remove(); + } + } + + bool RemoveSubscriptionConnection(const QString &subsName, const QString &name) + { + QFile config(QV2RAY_SUBSCRIPTION_DIR + subsName + "/" + name + QV2RAY_CONFIG_FILE_EXTENSION); + + if (!config.exists()) { + LOG(MODULE_FILE, "Trying to remove a non-existing file?") + return false; + } else { + return config.remove(); + } + } + + bool RenameConnection(const QString &originalName, const QString &newName) + { + LOG(MODULE_CONFIG, "[RENAME] --> ORIGINAL: " + originalName + ", NEW: " + newName) + return QFile::rename(QV2RAY_CONFIG_DIR + originalName + QV2RAY_CONFIG_FILE_EXTENSION, QV2RAY_CONFIG_DIR + newName + QV2RAY_CONFIG_FILE_EXTENSION); + } + + bool RenameSubscription(const QString &originalName, const QString &newName) + { + LOG(MODULE_SUBSCRIPTION, "[RENAME] --> ORIGINAL: " + originalName + ", NEW: " + newName) + return QDir().rename(QV2RAY_SUBSCRIPTION_DIR + originalName, QV2RAY_SUBSCRIPTION_DIR + newName); + } + + CONFIGROOT ConvertConfigFromFile(QString sourceFilePath, bool keepInbounds) + { + QFile source(sourceFilePath); + + if (!source.exists()) { + LOG(MODULE_FILE, "Trying to import from an non-existing file.") + return CONFIGROOT(); + } + + auto root = CONFIGROOT(JsonFromString(StringFromFile(&source))); + + if (!keepInbounds) { + JSON_ROOT_TRY_REMOVE("inbounds") + } + + JSON_ROOT_TRY_REMOVE("log") + JSON_ROOT_TRY_REMOVE("api") + JSON_ROOT_TRY_REMOVE("stats") + JSON_ROOT_TRY_REMOVE("dns") + return root; + } + } +} diff --git a/src/core/connection/ConnectionIO.hpp b/src/core/connection/ConnectionIO.hpp new file mode 100644 index 00000000..6f87202f --- /dev/null +++ b/src/core/connection/ConnectionIO.hpp @@ -0,0 +1,28 @@ +#include "base/Qv2rayBase.hpp" + +namespace Qv2ray::core::connection +{ + namespace ConnectionIO + { + QMap GetRegularConnections(QStringList connections); + QMap GetSubscriptionConnection(QString subscription); + QMap> GetSubscriptionConnections(QStringList subscriptions); + // + // Save Connection Config + bool SaveConnectionConfig(CONFIGROOT obj, QString *alias, bool canOverrideExisting); + bool SaveSubscriptionConfig(CONFIGROOT obj, const QString &subscription, QString *name); + // + bool RemoveConnection(const QString &alias); + bool RemoveSubscriptionConnection(const QString &subsName, const QString &name); + // + bool RenameConnection(const QString &originalName, const QString &newName); + bool RenameSubscription(const QString &originalName, const QString &newName); + + // File Protocol + CONFIGROOT ConvertConfigFromFile(QString sourceFilePath, bool keepInbounds); + } +} + +using namespace Qv2ray::core; +using namespace Qv2ray::core::connection; +using namespace Qv2ray::core::connection::ConnectionIO; diff --git a/src/core/connection/ConnectionConfig_Generation.cpp b/src/core/connection/Generation.cpp similarity index 98% rename from src/core/connection/ConnectionConfig_Generation.cpp rename to src/core/connection/Generation.cpp index b9fc3363..9df7605f 100644 --- a/src/core/connection/ConnectionConfig_Generation.cpp +++ b/src/core/connection/Generation.cpp @@ -1,12 +1,11 @@ -#include "ConnectionConfigOperations.hpp" +#include "Generation.hpp" +#include "core/CoreUtils.hpp" #include "common/QvHelpers.hpp" namespace Qv2ray::core::connection { - inline namespace Generation + namespace Generation { - // Important config generation algorithms. - static const QStringList vLogLevels = {"none", "debug", "info", "warning", "error"}; // -------------------------- BEGIN CONFIG GENERATIONS ---------------------------------------------------------------------------- ROUTING GenerateRoutes(bool enableProxy, bool proxyCN) { @@ -57,13 +56,13 @@ namespace Qv2ray::core::connection RROOT } - OUTBOUNDSETTING GenerateShadowSocksOUT(QList servers) + OUTBOUNDSETTING GenerateShadowSocksOUT(QList servers) { OUTBOUNDSETTING root; QJsonArray x; foreach (auto server, servers) { - x.append(server); + x.append(GenerateShadowSocksServerOUT(server.email, server.address, server.port, server.method, server.password, server.ota, server.level)); } root.insert("servers", x); diff --git a/src/core/connection/Generation.hpp b/src/core/connection/Generation.hpp new file mode 100644 index 00000000..0392180f --- /dev/null +++ b/src/core/connection/Generation.hpp @@ -0,0 +1,35 @@ +#include "base/Qv2rayBase.hpp" + +namespace Qv2ray::core::connection +{ + namespace Generation + { + // Important config generation algorithms. + const QStringList vLogLevels = {"none", "debug", "info", "warning", "error"}; + ROUTING GenerateRoutes(bool enableProxy, bool cnProxy); + ROUTERULE GenerateSingleRouteRule(QStringList list, bool isDomain, QString outboundTag, QString type = "field"); + QJsonObject GenerateDNS(bool withLocalhost, QStringList dnsServers); + QJsonObject GenerateAPIEntry(QString tag, bool withHandler = true, bool withLogger = true, bool withStats = true); + // + // Outbound Protocols + OUTBOUNDSETTING GenerateFreedomOUT(QString domainStrategy, QString redirect, int userLevel); + OUTBOUNDSETTING GenerateBlackHoleOUT(bool useHTTP); + OUTBOUNDSETTING GenerateShadowSocksOUT(QList servers); + OUTBOUNDSETTING GenerateShadowSocksServerOUT(QString email, QString address, int port, QString method, QString password, bool ota, int level); + OUTBOUNDSETTING GenerateHTTPSOCKSOut(QString address, int port, bool useAuth, QString username, QString password); + // + // Inbounds Protocols + INBOUNDSETTING GenerateDokodemoIN(QString address, int port, QString network, int timeout, bool followRedirect, int userLevel); + INBOUNDSETTING GenerateHTTPIN(QList accounts, int timeout = 300, bool allowTransparent = true, int userLevel = 0); + INBOUNDSETTING GenerateSocksIN(QString auth, QList _accounts, bool udp = false, QString ip = "127.0.0.1", int userLevel = 0); + // + // Generate FINAL Configs + CONFIGROOT GenerateRuntimeConfig(CONFIGROOT root); + OUTBOUND GenerateOutboundEntry(QString protocol, OUTBOUNDSETTING settings, QJsonObject streamSettings, QJsonObject mux = QJsonObject(), QString sendThrough = "0.0.0.0", QString tag = OUTBOUND_TAG_PROXY); + INBOUND GenerateInboundEntry(QString listen, int port, QString protocol, INBOUNDSETTING settings, QString tag, QJsonObject sniffing = QJsonObject(), QJsonObject allocate = QJsonObject()); + } +} + +using namespace Qv2ray::core; +using namespace Qv2ray::core::connection; +using namespace Qv2ray::core::connection::Generation; diff --git a/src/core/connection/ConnectionConfig_Convertion.cpp b/src/core/connection/Serialization.cpp similarity index 67% rename from src/core/connection/ConnectionConfig_Convertion.cpp rename to src/core/connection/Serialization.cpp index 109cd90a..8433b2b6 100644 --- a/src/core/connection/ConnectionConfig_Convertion.cpp +++ b/src/core/connection/Serialization.cpp @@ -1,9 +1,10 @@ -#include "ConnectionConfigOperations.hpp" +#include "Serialization.hpp" +#include "Generation.hpp" #include "common/QvHelpers.hpp" namespace Qv2ray::core::connection { - inline namespace Convertion + namespace Serialization { // From https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &serverConfig, const QString &alias) @@ -44,6 +45,7 @@ namespace Qv2ray::core::connection auto vmessPart = Base64Encode(JsonToString(vmessUriRoot, QJsonDocument::JsonFormat::Compact)); return "vmess://" + vmessPart; } + QString DecodeSubscriptionString(QByteArray arr) { // Some subscription providers may use plain vmess:// saperated by lines @@ -51,76 +53,118 @@ namespace Qv2ray::core::connection auto result = QString::fromUtf8(arr).trimmed(); return result.startsWith("vmess://") ? result : Base64Decode(result); } + + CONFIGROOT fromUri(const QString &ssUri, QString *alias, QString *errMessage) + { + ShadowSocksServerObject server; + QString d_name; + + //auto ssUri = _ssUri.toStdString(); + if (ssUri.length() < 5) { + LOG(MODULE_CONNECTION, "ss:// string too short") + *errMessage = QObject::tr("SS URI is too short"); + } + + auto uri = ssUri.mid(5); + auto hashPos = uri.lastIndexOf("#"); + DEBUG(MODULE_CONNECTION, "Hash sign position: " + QSTRN(hashPos)) + + if (hashPos >= 0) { + // Get the name/remark + d_name = uri.mid(uri.lastIndexOf("#") + 1); + uri.truncate(hashPos); + } + + // No plugins for Qv2ray so disable those lnes.i + //size_t pluginPos = uri.find_first_of('/'); + // + //if (pluginPos != std::string::npos) { + // // TODO: support plugins. For now, just ignore them + // uri.erase(pluginPos); + //} + auto atPos = uri.indexOf('@'); + DEBUG(MODULE_CONNECTION, "At sign position: " + QSTRN(atPos)) + + if (atPos < 0) { + // Old URI scheme + QString decoded = QByteArray::fromBase64(uri.toUtf8(), QByteArray::Base64Option::OmitTrailingEquals); + auto colonPos = decoded.indexOf(':'); + DEBUG(MODULE_CONNECTION, "Colon position: " + QSTRN(colonPos)) + + 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('@'); + DEBUG(MODULE_CONNECTION, "At sign position: " + QSTRN(atPos)) + + 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(':'); + DEBUG(MODULE_CONNECTION, "Colon position: " + QSTRN(colonPos)) + + 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 + QString userInfo(QByteArray::fromBase64(QByteArray(uri.mid(0, atPos).toUtf8(), QByteArray::Base64Option::Base64UrlEncoding))); + auto userInfoSp = userInfo.indexOf(':'); + DEBUG(MODULE_CONNECTION, "Userinfo splitter position: " + QSTRN(userInfoSp)) + + if (userInfoSp < 0) { + *errMessage = QObject::tr("Can't find the colon separator between method and password"); + } + + QString method = userInfo.mid(0, userInfoSp); + server.method = method; + server.password = userInfo.mid(userInfoSp + 1); + uri.remove(0, atPos + 1); + auto hostSpPos = uri.lastIndexOf(':'); + DEBUG(MODULE_CONNECTION, "Host splitter position: " + QSTRN(hostSpPos)) + + if (hostSpPos < 0) { + *errMessage = QObject::tr("Can't find the colon separator between hostname and port"); + } + + server.address = uri.mid(0, hostSpPos); + server.port = uri.mid(hostSpPos + 1).toInt(); + } + + CONFIGROOT root; + OUTBOUNDS outbounds; + outbounds.append(GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT(QList() << server), QJsonObject())); + JADD(outbounds) + *alias = alias->isEmpty() ? d_name : *alias + "_" + d_name; + LOG(MODULE_CONNECTION, "Deduced alias: " + *alias) + return root; + } + + QString toUri(const ShadowSocksServerObject &server, const QString &alias) + { + LOG(MODULE_CONNECTION, "Converting an ss-server config to old ss:// string format") + QString ssUri = server.method + ":" + server.password + "@" + server.address + ":" + QSTRN(server.port); + return "ss://" + ssUri.toUtf8().toBase64(QByteArray::Base64Option::OmitTrailingEquals) + "#" + alias; + } + + QString toUriSip002(const ShadowSocksServerObject &server, const QString &alias) + { + LOG(MODULE_CONNECTION, "Converting an ss-server config to Sip002 ss:// format") + QString plainUserInfo = server.method + ":" + server.password; + QString userinfo(plainUserInfo.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding).data()); + return "ss://" + userinfo + "@" + server.address + ":" + QSTRN(server.port) + "#" + alias; + } + // - // Save Connection to a place, with checking if there's existing file. - // If so, append "_N" to the name. - bool SaveConnectionConfig(CONFIGROOT obj, QString *alias, bool canOverrideExisting) - { - auto str = JsonToString(obj); - QFile *config = new QFile(QV2RAY_CONFIG_DIR + *alias + QV2RAY_CONFIG_FILE_EXTENSION); - - // If there's already a file AND we CANNOT override existing file. - if (config->exists() && !canOverrideExisting) { - // Alias is a pointer to a QString. - DeducePossibleFileName(QV2RAY_CONFIG_DIR, alias, QV2RAY_CONFIG_FILE_EXTENSION); - config = new QFile(QV2RAY_CONFIG_DIR + *alias + QV2RAY_CONFIG_FILE_EXTENSION); - } - - LOG(MODULE_CONFIG, "Saving a config named: " + *alias) - return StringToFile(&str, config); - } - - bool SaveSubscriptionConfig(CONFIGROOT obj, const QString &subscription, QString *name) - { - auto str = JsonToString(obj); - auto fName = *name; - - if (!IsValidFileName(fName)) { - fName = RemoveInvalidFileName(fName); - } - - QFile *config = new QFile(QV2RAY_SUBSCRIPTION_DIR + subscription + "/" + fName + QV2RAY_CONFIG_FILE_EXTENSION); - - // If there's already a file. THIS IS EXTREMELY RARE - if (config->exists()) { - LOG(MODULE_FILE, "Trying to overrwrite an existing subscription config file. THIS IS RARE") - } - - LOG(MODULE_CONFIG, "Saving a subscription named: " + fName) - bool result = StringToFile(&str, config); - - if (!result) { - LOG(MODULE_FILE, "Failed to save a connection config from subscription: " + subscription + ", name: " + fName) - } - - *name = fName; - return result; - } - - bool RemoveConnection(const QString &alias) - { - QFile config(QV2RAY_CONFIG_DIR + alias + QV2RAY_CONFIG_FILE_EXTENSION); - - if (!config.exists()) { - LOG(MODULE_FILE, "Trying to remove a non-existing file?") - return false; - } else { - return config.remove(); - } - } - - bool RemoveSubscriptionConnection(const QString &subsName, const QString &name) - { - QFile config(QV2RAY_SUBSCRIPTION_DIR + subsName + "/" + name + QV2RAY_CONFIG_FILE_EXTENSION); - - if (!config.exists()) { - LOG(MODULE_FILE, "Trying to remove a non-existing file?") - return false; - } else { - return config.remove(); - } - } - // This generates global config containing only one outbound.... CONFIGROOT ConvertConfigFromVMessString(const QString &vmessStr, QString *alias, QString *errMessage) { @@ -294,39 +338,5 @@ namespace Qv2ray::core::connection #undef default return root; } - - CONFIGROOT ConvertConfigFromFile(QString sourceFilePath, bool keepInbounds) - { - QFile source(sourceFilePath); - - if (!source.exists()) { - LOG(MODULE_FILE, "Trying to import from an non-existing file.") - return CONFIGROOT(); - } - - auto root = CONFIGROOT(JsonFromString(StringFromFile(&source))); - - if (!keepInbounds) { - JSON_ROOT_TRY_REMOVE("inbounds") - } - - JSON_ROOT_TRY_REMOVE("log") - JSON_ROOT_TRY_REMOVE("api") - JSON_ROOT_TRY_REMOVE("stats") - JSON_ROOT_TRY_REMOVE("dns") - return root; - } - - bool RenameConnection(const QString &originalName, const QString &newName) - { - LOG(MODULE_CONFIG, "[RENAME] --> ORIGINAL: " + originalName + ", NEW: " + newName) - return QFile::rename(QV2RAY_CONFIG_DIR + originalName + QV2RAY_CONFIG_FILE_EXTENSION, QV2RAY_CONFIG_DIR + newName + QV2RAY_CONFIG_FILE_EXTENSION); - } - - bool RenameSubscription(const QString &originalName, const QString &newName) - { - LOG(MODULE_SUBSCRIPTION, "[RENAME] --> ORIGINAL: " + originalName + ", NEW: " + newName) - return QDir().rename(QV2RAY_SUBSCRIPTION_DIR + originalName, QV2RAY_SUBSCRIPTION_DIR + newName); - } } } diff --git a/src/core/connection/Serialization.hpp b/src/core/connection/Serialization.hpp new file mode 100644 index 00000000..537583ca --- /dev/null +++ b/src/core/connection/Serialization.hpp @@ -0,0 +1,22 @@ +#include "base/Qv2rayBase.hpp" + +namespace Qv2ray::core::connection +{ + namespace Serialization + { + //int VerifyVMessProtocolString(QString vmess); + QString DecodeSubscriptionString(QByteArray arr); + + // VMess URI Protocol + CONFIGROOT ConvertConfigFromVMessString(const QString &vmess, QString *alias, QString *errMessage); + QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &serverConfig, const QString &alias); + + // SS URI Protocol + CONFIGROOT ConvertConfigFromSSString(const QString &ss, QString *alias, QString *errMessage); + QString ConvertConfigToSSString(const ShadowSocksServerObject &serverConfig, const QString &alias); + } +} + +using namespace Qv2ray::core; +using namespace Qv2ray::core::connection; +using namespace Qv2ray::core::connection::Serialization; diff --git a/src/core/kernel/QvKernelInteractions.cpp b/src/core/kernel/QvKernelInteractions.cpp index b90943ae..c140a24e 100644 --- a/src/core/kernel/QvKernelInteractions.cpp +++ b/src/core/kernel/QvKernelInteractions.cpp @@ -3,7 +3,7 @@ #include #include "common/QvHelpers.hpp" #include "QvKernelInteractions.hpp" -#include "core/connection/ConnectionConfigOperations.hpp" +#include "core/connection/ConnectionIO.hpp" #ifdef WITH_LIB_GRPCPP using namespace v2ray::core::app::stats::command; diff --git a/src/ui/editors/w_InboundEditor.cpp b/src/ui/editors/w_InboundEditor.cpp index 82683738..40e44c75 100644 --- a/src/ui/editors/w_InboundEditor.cpp +++ b/src/ui/editors/w_InboundEditor.cpp @@ -1,7 +1,7 @@ #include "w_InboundEditor.hpp" #include "core/CoreUtils.hpp" #include "common/QvHelpers.hpp" -#include "core/connection/ConnectionConfigOperations.hpp" +#include "core/connection/ConnectionIO.hpp" static bool isLoading = false; #define CHECKLOADING if(isLoading) return; diff --git a/src/ui/editors/w_OutboundEditor.cpp b/src/ui/editors/w_OutboundEditor.cpp index a86cd4e8..d173f346 100644 --- a/src/ui/editors/w_OutboundEditor.cpp +++ b/src/ui/editors/w_OutboundEditor.cpp @@ -7,6 +7,7 @@ #include "ui/w_MainWindow.hpp" #include "ui/editors/w_JsonEditor.hpp" #include "ui/editors/w_RoutesEditor.hpp" +#include "core/connection/Generation.hpp" OutboundEditor::OutboundEditor(QWidget *parent) : QDialog(parent), diff --git a/src/ui/editors/w_RoutesEditor.cpp b/src/ui/editors/w_RoutesEditor.cpp index 56c6541b..43a3647b 100644 --- a/src/ui/editors/w_RoutesEditor.cpp +++ b/src/ui/editors/w_RoutesEditor.cpp @@ -4,7 +4,8 @@ #pragma once #include "w_RoutesEditor.hpp" -#include "core/connection/ConnectionConfigOperations.hpp" +#include "core/connection/ConnectionIO.hpp" +#include "core/connection/Generation.hpp" #include "w_OutboundEditor.hpp" #include "w_JsonEditor.hpp" #include "w_InboundEditor.hpp" diff --git a/src/ui/w_ExportConfig.cpp b/src/ui/w_ExportConfig.cpp index c3c2e338..5bc4d780 100644 --- a/src/ui/w_ExportConfig.cpp +++ b/src/ui/w_ExportConfig.cpp @@ -16,19 +16,22 @@ ConfigExporter::~ConfigExporter() UNREGISTER_WINDOW } -ConfigExporter::ConfigExporter(const QImage &img, QWidget *parent): ConfigExporter(parent) +ConfigExporter::ConfigExporter(const CONFIGROOT &root, QWidget *parent) { - image = img; - message = tr("Empty"); -} -ConfigExporter::ConfigExporter(const QString &data, QWidget *parent): ConfigExporter(parent) -{ - QZXingEncoderConfig conf; - conf.border = true; - conf.imageSize = QSize(400, 400); - auto img = qzxing.encodeData(data, conf); - image = img.copy(); - message = data; + // + //auto vmessServer = StructFromJsonString(JsonToString(outBoundRoot["settings"].toObject()["vnext"].toArray().first().toObject())); + //auto transport = StructFromJsonString(JsonToString(outBoundRoot["streamSettings"].toObject())); + //auto vmess = ConvertConfigToVMessString(transport, vmessServer, _identifier.connectionName); + // + //image = img; + //message = tr("Empty"); + //// + //QZXingEncoderConfig conf; + //conf.border = true; + //conf.imageSize = QSize(400, 400); + //auto img = qzxing.encodeData(data, conf); + //image = img.copy(); + //message = data; } void ConfigExporter::OpenExport() diff --git a/src/ui/w_ExportConfig.hpp b/src/ui/w_ExportConfig.hpp index 31a639bd..1715ba20 100644 --- a/src/ui/w_ExportConfig.hpp +++ b/src/ui/w_ExportConfig.hpp @@ -1,6 +1,7 @@ #pragma once #include "ui_w_ExportConfig.h" +#include "base/Qv2rayBase.hpp" #include "3rdparty/qzxing/src/QZXing.h" class ConfigExporter : public QDialog, private Ui::ExportConfigWindow @@ -8,8 +9,7 @@ class ConfigExporter : public QDialog, private Ui::ExportConfigWindow Q_OBJECT public: - explicit ConfigExporter(const QImage &img, QWidget *parent = nullptr); - explicit ConfigExporter(const QString &data, QWidget *parent = nullptr); + explicit ConfigExporter(const CONFIGROOT &root, QWidget *parent = nullptr); ~ConfigExporter(); void OpenExport(); protected: diff --git a/src/ui/w_ImportConfig.cpp b/src/ui/w_ImportConfig.cpp index cfb05897..e8d9ba1c 100644 --- a/src/ui/w_ImportConfig.cpp +++ b/src/ui/w_ImportConfig.cpp @@ -9,7 +9,8 @@ #include "core/CoreUtils.hpp" #include "core/kernel/QvKernelInteractions.hpp" -#include "core/connection/ConnectionConfigOperations.hpp" +#include "core/connection/ConnectionIO.hpp" +#include "core/connection/Serialization.hpp" #include "w_ScreenShot_Core.hpp" #include "ui/editors/w_OutboundEditor.hpp" diff --git a/src/ui/w_MainWindow.cpp b/src/ui/w_MainWindow.cpp index 1ae93e59..31897738 100644 --- a/src/ui/w_MainWindow.cpp +++ b/src/ui/w_MainWindow.cpp @@ -26,6 +26,8 @@ #include "components/plugins/toolbar/QvToolbar.hpp" #include "components/pac/QvPACHandler.hpp" +#include "core/connection/ConnectionIO.hpp" + // MainWindow.cpp --> Main MainWindow source file, handles mostly UI-related operations. #define TRAY_TOOLTIP_PREFIX "Qv2ray " QV2RAY_VERSION_STRING @@ -1004,17 +1006,13 @@ void MainWindow::on_shareBtn_clicked() auto _identifier = ItemConnectionIdentifier(connectionListWidget->currentItem()); auto root = connections[_identifier].config; - auto outBoundRoot = root["outbounds"].toArray().first().toObject(); - auto outboundType = outBoundRoot["protocol"].toString(); + auto type = get<2>(GetConnectionInfo(root)); - if (!CheckIsComplexConfig(root) && outboundType == "vmess") { - auto vmessServer = StructFromJsonString(JsonToString(outBoundRoot["settings"].toObject()["vnext"].toArray().first().toObject())); - auto transport = StructFromJsonString(JsonToString(outBoundRoot["streamSettings"].toObject())); - auto vmess = ConvertConfigToVMessString(transport, vmessServer, _identifier.connectionName); - ConfigExporter v(vmess, this); + if (!CheckIsComplexConfig(root) && (type == "vmess" || type == "shadowsocks")) { + ConfigExporter v(root, this); v.OpenExport(); } else { - QvMessageBoxWarn(this, tr("Share Connection"), tr("There're no support of sharing configs other than vmess")); + QvMessageBoxWarn(this, tr("Share Connection"), tr("There're no support of sharing configs other than vmess and shadowsocks")); } } void MainWindow::on_action_RCM_ShareQR_triggered() @@ -1086,11 +1084,12 @@ void MainWindow::on_duplicateBtn_clicked() CONFIGROOT conf; // Alias may change. QString alias = _identifier.connectionName; + bool isComplex = CheckIsComplexConfig(connections[_identifier].config); if (connections[_identifier].configType == CONNECTION_REGULAR) { - conf = ConvertConfigFromFile(QV2RAY_CONFIG_DIR + _identifier.connectionName + QV2RAY_CONFIG_FILE_EXTENSION, false); + conf = ConvertConfigFromFile(QV2RAY_CONFIG_DIR + _identifier.connectionName + QV2RAY_CONFIG_FILE_EXTENSION, isComplex); } else { - conf = ConvertConfigFromFile(QV2RAY_SUBSCRIPTION_DIR + _identifier.subscriptionName + "/" + _identifier.connectionName + QV2RAY_CONFIG_FILE_EXTENSION, false); + conf = ConvertConfigFromFile(QV2RAY_SUBSCRIPTION_DIR + _identifier.subscriptionName + "/" + _identifier.connectionName + QV2RAY_CONFIG_FILE_EXTENSION, isComplex); alias = _identifier.subscriptionName + "_" + _identifier.connectionName; } diff --git a/src/ui/w_MainWindow.hpp b/src/ui/w_MainWindow.hpp index d19b151e..7c546b7a 100644 --- a/src/ui/w_MainWindow.hpp +++ b/src/ui/w_MainWindow.hpp @@ -10,7 +10,7 @@ #include "core/CoreUtils.hpp" #include "core/kernel/QvKernelInteractions.hpp" -#include "core/connection/ConnectionConfigOperations.hpp" +#include "core/connection/ConnectionIO.hpp" #include "components/pac/QvPACHandler.hpp" #include "common/LogHighlighter.hpp" diff --git a/src/ui/w_MainWindow_extra.cpp b/src/ui/w_MainWindow_extra.cpp index ff2f75ac..f2dc3c50 100644 --- a/src/ui/w_MainWindow_extra.cpp +++ b/src/ui/w_MainWindow_extra.cpp @@ -3,6 +3,7 @@ // We NEED to include the cpp file to define the macros. #include "w_MainWindow.cpp" #include "components/proxy/QvProxyConfigurator.hpp" +#include "core/connection/Generation.hpp" QTreeWidgetItem *MainWindow::FindItemByIdentifier(QvConfigIdentifier identifier) { diff --git a/src/ui/w_PreferencesWindow.cpp b/src/ui/w_PreferencesWindow.cpp index 3f427fba..9b146891 100644 --- a/src/ui/w_PreferencesWindow.cpp +++ b/src/ui/w_PreferencesWindow.cpp @@ -8,8 +8,8 @@ #include "common/QvHelpers.hpp" #include "common/HTTPRequestHelper.hpp" #include "core/config/ConfigBackend.hpp" +#include "core/connection/ConnectionIO.hpp" #include "core/kernel/QvKernelInteractions.hpp" -#include "core/connection/ConnectionConfigOperations.hpp" #include "components/plugins/toolbar/QvToolbar.hpp" #include "components/autolaunch/QvAutoLaunch.hpp" diff --git a/src/ui/w_SubscriptionManager.cpp b/src/ui/w_SubscriptionManager.cpp index d115cd70..47ee4838 100644 --- a/src/ui/w_SubscriptionManager.cpp +++ b/src/ui/w_SubscriptionManager.cpp @@ -1,7 +1,9 @@ #include "w_SubscriptionManager.hpp" #include "common/QvHelpers.hpp" #include "core/CoreUtils.hpp" -#include "core/connection/ConnectionConfigOperations.hpp" + +#include "core/connection/ConnectionIO.hpp" +#include "core/connection/Serialization.hpp" SubscribeEditor::SubscribeEditor(QWidget *parent) : QDialog(parent)