diff --git a/CMakeLists.txt b/CMakeLists.txt index 1740bcd..b8052fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,7 @@ set(SOURCE_FILES zjuconnectmode.cpp core/corecommandbuilder.cpp core/coreoutputparser.cpp + infrastructure/settingsprofileloader.cpp zjuconnectcontroller/zjuconnectcontroller.cpp extrasettingwindow/extrasettingwindow.cpp settingwindow/settingwindow.cpp @@ -127,4 +128,12 @@ if(BUILD_TESTING) target_include_directories(coreoutputparser_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(coreoutputparser_test Qt::Core) add_test(NAME coreoutputparser_test COMMAND coreoutputparser_test) + + add_executable(settingsprofileloader_test + tests/settingsprofileloader_test.cpp + infrastructure/settingsprofileloader.cpp + ) + target_include_directories(settingsprofileloader_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + target_link_libraries(settingsprofileloader_test Qt::Core) + add_test(NAME settingsprofileloader_test COMMAND settingsprofileloader_test) endif() diff --git a/infrastructure/settingsprofileloader.cpp b/infrastructure/settingsprofileloader.cpp new file mode 100644 index 0000000..90d4493 --- /dev/null +++ b/infrastructure/settingsprofileloader.cpp @@ -0,0 +1,80 @@ +#include "settingsprofileloader.h" + +ConnectionProfile SettingsProfileLoader::load( + const QSettings &settings, + const QString &profileId, + const QString &username, + const QString &password +) +{ + ConnectionProfile profile; + profile.profileId = profileId; + profile.credentials = { + username, + password, + settings.value("Credential/TOTPSecret").toString(), + settings.value("Credential/CertFile", "").toString(), + QByteArray::fromBase64(settings.value("Credential/CertPassword", "").toByteArray()) + }; + + const QString countryCode = settings.value("ZJUConnect/PhoneCountryCode").toString(); + const QString phoneNumber = settings.value("ZJUConnect/PhoneNumber").toString(); + const QString phone = !countryCode.isEmpty() && !phoneNumber.isEmpty() + ? countryCode + "-" + phoneNumber + : QString(); + profile.endpoint = { + settings.value("ZJUConnect/Protocol").toString(), + settings.value("ZJUConnect/AuthType").toString(), + settings.value("ZJUConnect/LoginDomain").toString(), + phone, + settings.value("ZJUConnect/ServerAddress").toString(), + settings.value("ZJUConnect/ServerPort").toInt() + }; + + profile.dns = { + settings.value("ZJUConnect/DNS").toString(), + settings.value("ZJUConnect/DNSAuto").toBool(), + settings.value("ZJUConnect/SecondaryDNS").toString(), + settings.value("ZJUConnect/DNSTTL").toInt(), + settings.value("ZJUConnect/DisableZJUDNS").toBool(), + settings.value("ZJUConnect/CustomDNS", "").toString() + }; + + const QString bindPrefix = settings.value("ZJUConnect/OutsideAccess", false).toBool() + ? "[::]:" + : "127.0.0.1:"; + profile.proxy = { + bindPrefix + QString::number(settings.value("ZJUConnect/SOCKS5Port").toInt()), + bindPrefix + QString::number(settings.value("ZJUConnect/HTTPPort").toInt()), + settings.value("ZJUConnect/ShadowsocksURL").toString(), + settings.value("ZJUConnect/DialDirectProxy").toString(), + settings.value("ZJUConnect/ProxyAll").toBool(), + settings.value("ZJUConnect/CustomProxyDomain", "").toString() + }; + + profile.tunnel = { + settings.value("ZJUConnect/TUNMode").toBool(), + settings.value("ZJUConnect/AddRoute").toBool(), + settings.value("ZJUConnect/DNSHijack").toBool(), + settings.value("ZJUConnect/FakeIP").toBool(), + settings.value("ZJUConnect/TCPTunnelMode").toBool(), + settings.value("ZJUConnect/TCPPortForwarding").toString(), + settings.value("ZJUConnect/UDPPortForwarding").toString() + }; + + profile.behavior = { + settings.value("ZJUConnect/UpdateBestNodesInterval", 300).toInt(), + !settings.value("ZJUConnect/MultiLine").toBool(), + !settings.value("ZJUConnect/KeepAlive").toBool(), + settings.value("ZJUConnect/KeepAliveURL", "").toString(), + settings.value("ZJUConnect/BindInterface", "").toString(), + settings.value("ZJUConnect/AutoDetectInterface", false).toBool(), + settings.value("ZJUConnect/SkipDomainResource").toBool(), + settings.value("ZJUConnect/DisableServerConfig").toBool(), + !settings.value("ZJUConnect/ZJUDefault").toBool(), + settings.value("ZJUConnect/Debug").toBool() + }; + + profile.extraArguments = settings.value("ZJUConnect/ExtraArguments", "").toString(); + return profile; +} diff --git a/infrastructure/settingsprofileloader.h b/infrastructure/settingsprofileloader.h new file mode 100644 index 0000000..d8b2e69 --- /dev/null +++ b/infrastructure/settingsprofileloader.h @@ -0,0 +1,19 @@ +#ifndef SETTINGSPROFILELOADER_H +#define SETTINGSPROFILELOADER_H + +#include + +#include "core/connectionprofile.h" + +class SettingsProfileLoader +{ +public: + static ConnectionProfile load( + const QSettings &settings, + const QString &profileId, + const QString &username, + const QString &password + ); +}; + +#endif // SETTINGSPROFILELOADER_H diff --git a/tests/settingsprofileloader_test.cpp b/tests/settingsprofileloader_test.cpp new file mode 100644 index 0000000..ddba5e0 --- /dev/null +++ b/tests/settingsprofileloader_test.cpp @@ -0,0 +1,144 @@ +#include +#include +#include +#include + +#include "infrastructure/settingsprofileloader.h" + +namespace +{ +bool loadsSettingsIntoTypedProfile() +{ + QTemporaryDir directory; + if (!directory.isValid()) + { + qCritical() << "Unable to create temporary directory"; + return false; + } + + QSettings settings(directory.filePath("profile.ini"), QSettings::IniFormat); + settings.setValue("Credential/TOTPSecret", "totp"); + settings.setValue("Credential/CertFile", "/tmp/client.p12"); + settings.setValue( + "Credential/CertPassword", + QString(QStringLiteral("cert-password").toUtf8().toBase64()) + ); + settings.setValue("ZJUConnect/Protocol", "atrust"); + settings.setValue("ZJUConnect/AuthType", "cas"); + settings.setValue("ZJUConnect/LoginDomain", "domain"); + settings.setValue("ZJUConnect/PhoneCountryCode", "86"); + settings.setValue("ZJUConnect/PhoneNumber", "123456"); + settings.setValue("ZJUConnect/ServerAddress", "vpn.example.edu"); + settings.setValue("ZJUConnect/ServerPort", 8443); + settings.setValue("ZJUConnect/DNS", "10.0.0.1"); + settings.setValue("ZJUConnect/DNSAuto", false); + settings.setValue("ZJUConnect/SecondaryDNS", "10.0.0.2"); + settings.setValue("ZJUConnect/DNSTTL", 60); + settings.setValue("ZJUConnect/DisableZJUDNS", true); + settings.setValue("ZJUConnect/CustomDNS", "example.org=1.1.1.1"); + settings.setValue("ZJUConnect/OutsideAccess", true); + settings.setValue("ZJUConnect/SOCKS5Port", 1080); + settings.setValue("ZJUConnect/HTTPPort", 1081); + settings.setValue("ZJUConnect/ShadowsocksURL", "ss://url"); + settings.setValue("ZJUConnect/DialDirectProxy", "http://direct"); + settings.setValue("ZJUConnect/ProxyAll", true); + settings.setValue("ZJUConnect/CustomProxyDomain", "example.org"); + settings.setValue("ZJUConnect/TUNMode", true); + settings.setValue("ZJUConnect/AddRoute", true); + settings.setValue("ZJUConnect/DNSHijack", true); + settings.setValue("ZJUConnect/FakeIP", true); + settings.setValue("ZJUConnect/TCPTunnelMode", true); + settings.setValue("ZJUConnect/TCPPortForwarding", "tcp-forward"); + settings.setValue("ZJUConnect/UDPPortForwarding", "udp-forward"); + settings.setValue("ZJUConnect/UpdateBestNodesInterval", 30); + settings.setValue("ZJUConnect/MultiLine", false); + settings.setValue("ZJUConnect/KeepAlive", false); + settings.setValue("ZJUConnect/KeepAliveURL", "https://keepalive"); + settings.setValue("ZJUConnect/BindInterface", "en0"); + settings.setValue("ZJUConnect/AutoDetectInterface", true); + settings.setValue("ZJUConnect/SkipDomainResource", true); + settings.setValue("ZJUConnect/DisableServerConfig", true); + settings.setValue("ZJUConnect/ZJUDefault", false); + settings.setValue("ZJUConnect/Debug", true); + settings.setValue("ZJUConnect/ExtraArguments", "-foo bar"); + + const ConnectionProfile profile = + SettingsProfileLoader::load(settings, "campus", "alice", "secret"); + + const bool passed = + profile.profileId == "campus" + && profile.credentials.username == "alice" + && profile.credentials.password == "secret" + && profile.credentials.totpSecret == "totp" + && profile.credentials.certFile == "/tmp/client.p12" + && profile.credentials.certPassword == "cert-password" + && profile.endpoint.protocol == "atrust" + && profile.endpoint.authType == "cas" + && profile.endpoint.loginDomain == "domain" + && profile.endpoint.phone == "86-123456" + && profile.endpoint.server == "vpn.example.edu" + && profile.endpoint.port == 8443 + && profile.dns.primary == "10.0.0.1" + && !profile.dns.automatic + && profile.dns.secondary == "10.0.0.2" + && profile.dns.ttl == 60 + && profile.dns.disableZjuDns + && profile.dns.custom == "example.org=1.1.1.1" + && profile.proxy.socksBind == "[::]:1080" + && profile.proxy.httpBind == "[::]:1081" + && profile.proxy.shadowsocksUrl == "ss://url" + && profile.proxy.dialDirectProxy == "http://direct" + && profile.proxy.proxyAll + && profile.proxy.customDomains == "example.org" + && profile.tunnel.tunMode + && profile.tunnel.addRoute + && profile.tunnel.dnsHijack + && profile.tunnel.fakeIp + && profile.tunnel.tcpTunnelMode + && profile.tunnel.tcpPortForwarding == "tcp-forward" + && profile.tunnel.udpPortForwarding == "udp-forward" + && profile.behavior.updateBestNodesInterval == 30 + && profile.behavior.disableMultiLine + && profile.behavior.disableKeepAlive + && profile.behavior.keepAliveUrl == "https://keepalive" + && profile.behavior.bindInterface == "en0" + && profile.behavior.autoDetectInterface + && profile.behavior.skipDomainResource + && profile.behavior.disableServerConfig + && profile.behavior.disableZjuConfig + && profile.behavior.debugDump + && profile.extraArguments == "-foo bar"; + + if (!passed) + { + qCritical() << "loadsSettingsIntoTypedProfile failed"; + } + return passed; +} + +bool usesCompatibleDefaults() +{ + QTemporaryDir directory; + QSettings settings(directory.filePath("empty.ini"), QSettings::IniFormat); + + const ConnectionProfile profile = SettingsProfileLoader::load(settings, "", "", ""); + const bool passed = + profile.behavior.updateBestNodesInterval == 300 + && profile.behavior.disableMultiLine + && profile.behavior.disableKeepAlive + && profile.behavior.disableZjuConfig + && profile.proxy.socksBind == "127.0.0.1:0" + && profile.proxy.httpBind == "127.0.0.1:0"; + if (!passed) + { + qCritical() << "usesCompatibleDefaults failed"; + } + return passed; +} +} + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + return loadsSettingsIntoTypedProfile() && usesCompatibleDefaults() ? 0 : 1; +} diff --git a/zjuconnectcontroller/zjuconnectcontroller.cpp b/zjuconnectcontroller/zjuconnectcontroller.cpp index b4e7e95..afeac41 100644 --- a/zjuconnectcontroller/zjuconnectcontroller.cpp +++ b/zjuconnectcontroller/zjuconnectcontroller.cpp @@ -188,69 +188,11 @@ QString ZjuConnectController::copyCoreForAppImage(const QString &programPath) #endif } -void ZjuConnectController::start( - const QString& program, - const QString& protocol, - const QString& authType, - const QString& loginDomain, - const QString& username, - const QString& password, - const QString& phone, - const QString& totpSecret, - const QString& server, - int port, - const QString& dns, - bool dnsAuto, - const QString& secondaryDns, - int dnsTtl, - const QString& socksBind, - const QString& httpBind, - const QString& shadowsocksUrl, - const QString& dialDirectProxy, - int updateBestNodesInterval, - bool disableMultiLine, - bool disableKeepAlive, - const QString& keepAliveUrl, - const QString& bindInterface, - bool autoDetectInterface, - bool skipDomainResource, - bool disableServerConfig, - bool proxyAll, - bool disableZjuDns, - bool disableZjuConfig, - bool debugDump, - bool tunMode, - bool addRoute, - bool dnsHijack, - bool fakeIp, - bool tcpTunnelMode, - const QString& tcpPortForwarding, - const QString& udpPortForwarding, - const QString& customDNS, - const QString& customProxyDomain, - const QString& certFile, - const QString& certPassword, - const QString& extraArguments, - const QString& profileId -) +void ZjuConnectController::start(const ConnectionProfile &profile) { - ConnectionProfile profile; - profile.program = program; - profile.profileId = profileId; - profile.credentials = {username, password, totpSecret, certFile, certPassword}; - profile.endpoint = {protocol, authType, loginDomain, phone, server, port}; - profile.dns = {dns, dnsAuto, secondaryDns, dnsTtl, disableZjuDns, customDNS}; - profile.proxy = {socksBind, httpBind, shadowsocksUrl, dialDirectProxy, proxyAll, customProxyDomain}; - profile.tunnel = {tunMode, addRoute, dnsHijack, fakeIp, tcpTunnelMode, - tcpPortForwarding, udpPortForwarding}; - profile.behavior = {updateBestNodesInterval, disableMultiLine, disableKeepAlive, - keepAliveUrl, bindInterface, autoDetectInterface, skipDomainResource, - disableServerConfig, disableZjuConfig, debugDump}; - profile.extraArguments = extraArguments; - CoreRuntimePaths runtimePaths; - if (protocol == "atrust") + if (profile.endpoint.protocol == "atrust") { // 图形验证码文件路径 if (tempDir == nullptr) @@ -260,27 +202,27 @@ void ZjuConnectController::start( } graphFile = tempDir->filePath("graph.jpg"); runtimePaths.graphCodeFile = graphFile; - runtimePaths.clientDataFile = Utils::getClientDataPath(profileId); + runtimePaths.clientDataFile = Utils::getClientDataPath(profile.profileId); } const CoreCommand command = CoreCommandBuilder::build(profile, runtimePaths); QString timeString = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); emit outputRead(timeString + " VPN 启动!参数:" + command.loggableArguments.join(' ')); - if (!totpSecret.isEmpty()) + if (!profile.credentials.totpSecret.isEmpty()) { emit outputRead(timeString + " 使用了 TOTP"); } - if (!certFile.isEmpty()) + if (!profile.credentials.certFile.isEmpty()) { emit outputRead(timeString + " 使用了证书文件"); } - QString programToStart = program; + QString programToStart = profile.program; QStringList finalArgs = command.arguments; #if defined(Q_OS_UNIX) - if (tunMode && !Utils::isRunningAsAdmin()) + if (profile.tunnel.tunMode && !Utils::isRunningAsAdmin()) { programToStart = copyCoreForAppImage(programToStart); diff --git a/zjuconnectcontroller/zjuconnectcontroller.h b/zjuconnectcontroller/zjuconnectcontroller.h index e03cc55..35f4d96 100644 --- a/zjuconnectcontroller/zjuconnectcontroller.h +++ b/zjuconnectcontroller/zjuconnectcontroller.h @@ -3,6 +3,8 @@ #include +#include "core/connectionprofile.h" + enum class ZJU_ERROR { NONE, @@ -29,51 +31,7 @@ public: ~ZjuConnectController() override; - void start( - const QString& program, - const QString& protocol, - const QString& authType, - const QString& loginDomain, - const QString& username, - const QString& password, - const QString& phone, - const QString& totpSecret, - const QString& server, - int port, - const QString& dns, - bool dnsAuto, - const QString& secondaryDns, - int dnsTtl, - const QString& socksBind, - const QString& httpBind, - const QString& shadowsocksUrl, - const QString& dialDirectProxy, - int updateBestNodesInterval, - bool disableMultiLine, - bool disableKeepAlive, - const QString& keepAliveUrl, - const QString& bindInterface, - bool autoDetectInterface, - bool skipDomainResource, - bool disableServerConfig, - bool proxyAll, - bool disableZjuDns, - bool disableZjuConfig, - bool debugDump, - bool tunMode, - bool addRoute, - bool dnsHijack, - bool fakeIp, - bool tcpTunnelMode, - const QString& tcpPortForwarding, - const QString& udpPortForwarding, - const QString& customDNS, - const QString& customProxyDomain, - const QString& certFile, - const QString& certPassword, - const QString& extraArguments, - const QString& profileId - ); + void start(const ConnectionProfile &profile); void stop(); diff --git a/zjuconnectmode.cpp b/zjuconnectmode.cpp index 9e3df80..692de43 100644 --- a/zjuconnectmode.cpp +++ b/zjuconnectmode.cpp @@ -11,6 +11,7 @@ #include "ui_mainwindow.h" #include "sudowindow/sudowindow.h" #include "loginwindow/loginwindow.h" +#include "infrastructure/settingsprofileloader.h" #include "utils/utils.h" #include "zjuconnectcontroller/zjuconnectcontroller.h" @@ -280,9 +281,6 @@ void MainWindow::initZjuConnect() #endif auto startZjuConnect = [this](const QString &username, const QString &password) { - QString program_path = Utils::getCorePath(); - QString bind_prefix = settings->value("ZJUConnect/OutsideAccess", false).toBool() ? "[::]:" : "127.0.0.1:"; - isZjuConnectLinked = true; zjuConnectError = ZJU_ERROR::NONE; ui->pushButton1->setText("断开服务器"); @@ -295,50 +293,10 @@ void MainWindow::initZjuConnect() } isAutoReconnecting = false; - QString countryCode = settings->value("ZJUConnect/PhoneCountryCode").toString(); - QString phoneNumber = settings->value("ZJUConnect/PhoneNumber").toString(); - QString phone = !countryCode.isEmpty() && !phoneNumber.isEmpty() ? (countryCode + "-" + phoneNumber) : ""; - - zjuConnectController->start( - program_path, settings->value("ZJUConnect/Protocol").toString(), - settings->value("ZJUConnect/AuthType").toString(), - settings->value("ZJUConnect/LoginDomain").toString(), username, password, phone, - settings->value("Credential/TOTPSecret").toString(), - settings->value("ZJUConnect/ServerAddress").toString(), - settings->value("ZJUConnect/ServerPort").toInt(), - settings->value("ZJUConnect/DNS").toString(), - settings->value("ZJUConnect/DNSAuto").toBool(), - settings->value("ZJUConnect/SecondaryDNS").toString(), - settings->value("ZJUConnect/DNSTTL").toInt(), - bind_prefix + QString::number(settings->value("ZJUConnect/SOCKS5Port").toInt()), - bind_prefix + QString::number(settings->value("ZJUConnect/HTTPPort").toInt()), - settings->value("ZJUConnect/ShadowsocksURL").toString(), - settings->value("ZJUConnect/DialDirectProxy").toString(), - settings->value("ZJUConnect/UpdateBestNodesInterval", 300).toInt(), - !settings->value("ZJUConnect/MultiLine").toBool(), - !settings->value("ZJUConnect/KeepAlive").toBool(), - settings->value("ZJUConnect/KeepAliveURL", "").toString(), - settings->value("ZJUConnect/BindInterface", "").toString(), - settings->value("ZJUConnect/AutoDetectInterface", false).toBool(), - settings->value("ZJUConnect/SkipDomainResource").toBool(), - settings->value("ZJUConnect/DisableServerConfig").toBool(), - settings->value("ZJUConnect/ProxyAll").toBool(), - settings->value("ZJUConnect/DisableZJUDNS").toBool(), - !settings->value("ZJUConnect/ZJUDefault").toBool(), - settings->value("ZJUConnect/Debug").toBool(), - settings->value("ZJUConnect/TUNMode").toBool(), - settings->value("ZJUConnect/AddRoute").toBool(), - settings->value("ZJUConnect/DNSHijack").toBool(), - settings->value("ZJUConnect/FakeIP").toBool(), - settings->value("ZJUConnect/TCPTunnelMode").toBool(), - settings->value("ZJUConnect/TCPPortForwarding").toString(), - settings->value("ZJUConnect/UDPPortForwarding").toString(), - settings->value("ZJUConnect/CustomDNS", "").toString(), - settings->value("ZJUConnect/CustomProxyDomain", "").toString(), - settings->value("Credential/CertFile", "").toString(), - QByteArray::fromBase64(settings->value("Credential/CertPassword", "").toString().toUtf8()), - settings->value("ZJUConnect/ExtraArguments", "").toString(), - currentProfileId); + ConnectionProfile profile = + SettingsProfileLoader::load(*settings, currentProfileId, username, password); + profile.program = Utils::getCorePath(); + zjuConnectController->start(profile); }; if (((protocol == "atrust" && authtype == "psw") ||