Compare commits

..

56 Commits

Author SHA1 Message Date
aiamnezia
a491206972 Merge branch 'dev' into refactoring/tests 2026-05-15 18:45:40 +04:00
MrMirDan
0315be0885 python and conan installation to tests jobs 2026-05-13 11:57:21 +03:00
MrMirDan
4fd3a995d0 update: windows tests deploy 2026-05-12 16:23:54 +03:00
MrMirDan
e169d5323e Merge branch 'refactoring/tests' of https://github.com/amnezia-vpn/amnezia-client into refactoring/tests 2026-05-12 16:09:22 +03:00
MrMirDan
651f2b28c4 some fixes due merge 2026-05-12 16:09:17 +03:00
MrMirDan
25a02ebbda Merge branch 'dev' into refactoring/tests 2026-05-05 12:46:46 +03:00
MrMirDan
f7cba6dd2c proper path for some vars 2026-05-01 11:53:29 +03:00
MrMirDan
132c836777 update: added local vars file for tests 2026-05-01 11:53:29 +03:00
MrMirDan
1238920b60 update: rewrited env's, qtest include and clear clients in some tests 2026-05-01 11:53:29 +03:00
MrMirDan
8fc9fb87fb update: Android tests job 2026-05-01 11:53:29 +03:00
MrMirDan
b6407e049e update: MacOS tests job 2026-05-01 11:53:29 +03:00
MrMirDan
a1b7a98ae1 update: offscreen for linux tests 2026-05-01 11:53:29 +03:00
MrMirDan
37b8c03e33 update: linux additional dependencies 2026-05-01 11:53:29 +03:00
MrMirDan
473a968d75 update: some qverify2 messages 2026-05-01 11:53:29 +03:00
MrMirDan
c3ea4f5ee6 update: changed QVERIFY to QVERIFY2 2026-05-01 11:53:29 +03:00
MrMirDan
c965aba61d update: modified qtest include 2026-05-01 11:53:29 +03:00
MrMirDan
2f9f0ee50d update: linux 'Install dependencies' 2026-05-01 11:53:29 +03:00
MrMirDan
4c6cf9f2d4 update: linux job step 'Run tests' 2026-05-01 11:53:29 +03:00
MrMirDan
ae763187b9 update: removed unneccessary step and line 2026-05-01 11:53:29 +03:00
MrMirDan
a5deb30abb fixed issue with ssh path 2026-05-01 11:53:29 +03:00
MrMirDan
a3cff2099f update: path to ssh.dll 2026-05-01 11:53:29 +03:00
MrMirDan
d37c616d3d removed mcvs install 2026-05-01 11:51:28 +03:00
MrMirDan
3885013829 update: installing msvc and additional checks 2026-05-01 11:51:05 +03:00
MrMirDan
63f33bff83 added ctest to client cmake 2026-05-01 11:50:10 +03:00
MrMirDan
562858c9df update: changed tests dir 2026-05-01 11:50:10 +03:00
MrMirDan
a58b05214f update: removed api tests from cmake 2026-05-01 11:50:10 +03:00
MrMirDan
703384445b update: serialization test 2026-05-01 11:50:10 +03:00
MrMirDan
a02419e229 update: qt path 2026-05-01 11:50:10 +03:00
MrMirDan
ea5e76e8c8 update: set dir for tests exe 2026-05-01 11:50:09 +03:00
MrMirDan
72a8b15ac8 update: include ctest 2026-05-01 11:49:40 +03:00
MrMirDan
eb6f7a5870 update: ctest dir 2026-05-01 11:49:40 +03:00
MrMirDan
3b8adf3b6a update: dependencies only for tests executables 2026-05-01 11:49:24 +03:00
MrMirDan
5a7c7c9df9 update: changed path to deploy qt dependencies 2026-05-01 11:49:24 +03:00
MrMirDan
3426ea4e26 update: 'Build' step 2026-05-01 11:49:24 +03:00
MrMirDan
ffb1e556b1 changed path to run tests 2026-05-01 11:49:24 +03:00
MrMirDan
b2d7b3e686 search for exe files 2026-05-01 11:49:24 +03:00
MrMirDan
93e70e8d9f update: using ctest 2026-05-01 11:49:00 +03:00
MrMirDan
e9ec3d6734 update: 'get sources' step and windows shell 2026-05-01 11:48:26 +03:00
MrMirDan
e2944b1794 update: tests jobs remake 2026-05-01 11:48:26 +03:00
MrMirDan
6c7d414a64 update: added 'get sources' and changed steps order 2026-05-01 11:48:26 +03:00
MrMirDan
631d4c7fd0 update: added envs 2026-05-01 11:48:26 +03:00
MrMirDan
fbde1aab00 update: proper artifact names 2026-05-01 11:48:26 +03:00
MrMirDan
ee09913cea update: job for tests (Windows) 2026-05-01 11:48:26 +03:00
MrMirDan
a51b073692 update: job for tests (Linux) 2026-05-01 11:48:26 +03:00
MrMirDan
f6ca964ff8 update: api services ui model and controller test (incompleted) 2026-05-01 11:48:26 +03:00
MrMirDan
545e751d33 update: news ui model and controller test (incomplete) 2026-05-01 11:48:26 +03:00
MrMirDan
87c5f2a284 update: env vars and removed some lines 2026-05-01 11:48:26 +03:00
MrMirDan
23f993e2e5 update: allowed dns ui model and controller test 2026-05-01 11:48:25 +03:00
MrMirDan
ddb522a8d3 update: app ui model and controller test 2026-05-01 11:48:25 +03:00
MrMirDan
2ee792d088 update: sites test 2026-05-01 11:48:25 +03:00
MrMirDan
bccf9f5fbf update: sites UiController and Model test 2026-05-01 11:48:25 +03:00
MrMirDan
7a84f42d5c update: language model and controller test 2026-05-01 11:48:25 +03:00
MrMirDan
40a3f61532 update: vless serialization/deserialization test added 2026-05-01 11:48:25 +03:00
MrMirDan
4253272154 update: export test 2026-05-01 11:48:25 +03:00
MrMirDan
fb402eb9c8 update: native exports test 2026-05-01 11:48:25 +03:00
MrMirDan
0c9b3d5fbe fix: AUTOMOC and AUTOUIC added 2026-05-01 11:48:25 +03:00
90 changed files with 1566 additions and 6816 deletions

View File

@@ -894,3 +894,213 @@ jobs:
run: |
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
# ------------------------------------------------------
Test-Linux:
runs-on: ubuntu-latest
env:
QT_VERSION: 6.10.1
steps:
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'linux_gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
set-env: 'true'
- name: 'Install dependencies'
run: |
sudo apt-get update
sudo apt-get install -y \
cmake \
ninja-build \
libsecret-1-dev \
libxcb-cursor0 \
libxkbcommon-x11-0 \
libxcb-xinerama0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-xfixes0 \
libxcb-shape0 \
libxcb-sync1 \
libxcb-xkb1 \
libgl1 \
libglib2.0-0
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
- name: 'Configure'
run: |
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON
- name: 'Build'
run: cmake --build build
- name: 'Run tests'
run: |
QT_ROOT=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:\
$QT_ROOT/lib:\
$PWD/client/3rd-prebuilt/deploy-prebuilt/linux/x64
export QT_QPA_PLATFORM=offscreen
ctest --test-dir build/client --output-on-failure
# ------------------------------------------------------
Test-Windows:
runs-on: windows-latest
env:
QT_VERSION: 6.10.1
steps:
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2022_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
set-env: 'true'
- name: 'Setup MSVC'
uses: ilammy/msvc-dev-cmd@v1
with:
arch: 'x64'
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
- name: 'Install dependencies'
shell: bash
run: |
choco install ninja -y
- name: 'Configure'
shell: cmd
run: |
cmake -B build -G Ninja ^
-DCMAKE_BUILD_TYPE=Release ^
-DBUILD_TESTING=ON
- name: 'Build'
shell: cmd
run: cmake --build build --config Release
- name: 'Deploy Qt dependencies'
shell: cmd
run: |
for %%f in (build\client\test_*.exe) do (windeployqt %%f)
- name: 'Run tests'
shell: cmd
run: |
set PATH=%PATH%;%CD%\client\3rd-prebuilt\deploy-prebuilt\windows\x64
set PATH=%PATH%;%Qt6_DIR%\..\..\..\bin
ctest --test-dir build\client --output-on-failure
# ------------------------------------------------------
Test-MacOS:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2.0'
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Install Qt'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
set-env: 'true'
- name: 'Install dependencies'
run: |
brew update
brew install cmake ninja
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
- name: 'Configure'
run: |
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=ON
- name: 'Build'
run: cmake --build build
- name: 'Run tests'
run: |
QT_ROOT=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos
export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:\
$QT_ROOT/lib:\
$PWD/client/3rd-prebuilt/deploy-prebuilt/macos
export QT_QPA_PLATFORM=offscreen
ctest --test-dir build/client --output-on-failure

3
.gitignore vendored
View File

@@ -10,7 +10,8 @@ deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
.venv/
.cursor/
# Qt-es
/.qmake.cache

View File

@@ -193,6 +193,8 @@ elseif(APPLE)
include(cmake/macos.cmake)
endif()
include(CTest)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(tests)
endif()

View File

@@ -35,8 +35,6 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/installers/torInstaller.h
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.h
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.h
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
@@ -112,8 +110,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp
${CLIENT_ROOT_DIR}/core/installers/mtProxyInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/telemtInstaller.cpp
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
@@ -205,14 +201,12 @@ file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/*.h
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
${CLIENT_ROOT_DIR}/ui/models/services/*.h
${CLIENT_ROOT_DIR}/ui/models/utils/*.h
${CLIENT_ROOT_DIR}/ui/models/api/*.h
)
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/*.cpp
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
${CLIENT_ROOT_DIR}/ui/models/utils/*.cpp
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
)

View File

@@ -100,12 +100,6 @@ void CoreController::initModels()
m_socks5ConfigModel = new Socks5ProxyConfigModel(this);
setQmlContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel);
m_mtProxyConfigModel = new MtProxyConfigModel(this);
setQmlContextProperty("MtProxyConfigModel", m_mtProxyConfigModel);
m_telemtConfigModel = new TelemtConfigModel(this);
setQmlContextProperty("TelemtConfigModel", m_telemtConfigModel);
m_clientManagementModel = new ClientManagementModel(this);
setQmlContextProperty("ClientManagementModel", m_clientManagementModel);
@@ -175,7 +169,7 @@ void CoreController::initControllers()
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel,
#endif
m_sftpConfigModel, m_socks5ConfigModel, m_mtProxyConfigModel, m_telemtConfigModel, this);
m_sftpConfigModel, m_socks5ConfigModel, this);
setQmlContextProperty("InstallController", m_installUiController);
m_importController = new ImportUiController(m_importCoreController, this);
@@ -208,10 +202,6 @@ void CoreController::initControllers()
m_systemController = new SystemController(this);
setQmlContextProperty("SystemController", m_systemController);
m_networkReachabilityController = new NetworkReachabilityController(this);
m_engine->rootContext()->setContextProperty("NetworkReachabilityController", m_networkReachabilityController);
m_engine->rootContext()->setContextProperty("NetworkReachability", m_networkReachabilityController);
m_servicesCatalogUiController = new ServicesCatalogUiController(m_servicesCatalogController, m_apiServicesModel, this);
setQmlContextProperty("ServicesCatalogUiController", m_servicesCatalogUiController);

View File

@@ -28,7 +28,6 @@
#include "ui/controllers/languageUiController.h"
#include "ui/controllers/updateUiController.h"
#include "ui/controllers/api/servicesCatalogUiController.h"
#include "ui/controllers/networkReachabilityController.h"
#include "core/controllers/serversController.h"
#include "core/controllers/selfhosted/usersController.h"
@@ -70,9 +69,6 @@
#include "ui/models/serversModel.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/services/mtProxyConfigModel.h"
#include "ui/models/services/telemtConfigModel.h"
#include "ui/models/ipSplitTunnelingModel.h"
#include "ui/models/newsModel.h"
@@ -86,12 +82,21 @@ class TestAdminSelfHostedExport;
class TestServerEdit;
class TestDefaultServerChange;
class TestServerEdgeCases;
class TestGatewayStacks;
class TestSignalOrder;
class TestServersModelSync;
class TestComplexOperations;
class TestSettingsSignals;
class TestUiServersModelAndController;
class TestSelfHostedServerSetup;
class TestMultipleExports;
class TestSerialization;
class TestUiLanguageModelAndController;
class TestUiIpModelAndController;
class TestUiAppSTModelAndController;
class TestUiAllowedDnsModelAndController;
class TestUiNewsModelAndController;
class TestUiApiServicesModelAndController;
class CoreController : public QObject
{
@@ -102,12 +107,21 @@ class CoreController : public QObject
friend class TestServerEdit;
friend class TestDefaultServerChange;
friend class TestServerEdgeCases;
friend class TestGatewayStacks;
friend class TestSignalOrder;
friend class TestServersModelSync;
friend class TestComplexOperations;
friend class TestSettingsSignals;
friend class TestUiServersModelAndController;
friend class TestSelfHostedServerSetup;
friend class TestMultipleExports;
friend class TestSerialization;
friend class TestUiLanguageModelAndController;
friend class TestUiIpModelAndController;
friend class TestUiAppSTModelAndController;
friend class TestUiAllowedDnsModelAndController;
friend class TestUiNewsModelAndController;
friend class TestUiApiServicesModelAndController;
public:
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
@@ -160,7 +174,6 @@ private:
ServersUiController* m_serversUiController;
IpSplitTunnelingUiController* m_ipSplitTunnelingUiController;
SystemController* m_systemController;
NetworkReachabilityController* m_networkReachabilityController;
AppSplitTunnelingUiController* m_appSplitTunnelingUiController;
AllowedDnsUiController* m_allowedDnsUiController;
LanguageUiController* m_languageUiController;
@@ -213,8 +226,6 @@ private:
#endif
SftpConfigModel* m_sftpConfigModel;
Socks5ProxyConfigModel* m_socks5ConfigModel;
MtProxyConfigModel* m_mtProxyConfigModel;
TelemtConfigModel* m_telemtConfigModel;
CoreSignalHandlers* m_signalHandlers;
};

View File

@@ -19,8 +19,6 @@
#include "core/installers/openvpnInstaller.h"
#include "core/installers/sftpInstaller.h"
#include "core/installers/socks5Installer.h"
#include "core/installers/mtProxyInstaller.h"
#include "core/installers/telemtInstaller.h"
#include "core/installers/torInstaller.h"
#include "core/installers/wireguardInstaller.h"
#include "core/installers/xrayInstaller.h"
@@ -36,7 +34,6 @@
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "core/utils/utilities.h"
@@ -56,21 +53,6 @@ using namespace ProtocolUtils;
namespace
{
Logger logger("InstallController");
bool dockerDaemonContainerMissing(const QString &out, const QString &containerDockerName)
{
if (!out.contains(QLatin1String("Error response from daemon"), Qt::CaseInsensitive)) {
return false;
}
if (out.contains(QLatin1String("No such container"), Qt::CaseInsensitive)
&& out.contains(containerDockerName, Qt::CaseInsensitive)) {
return true;
}
if (out.size() < 700 && out.contains(QLatin1String("is not running"), Qt::CaseInsensitive)) {
return true;
}
return false;
}
}
InstallController::InstallController(SecureServersRepository *serversRepository,
@@ -154,15 +136,6 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
if (container == DockerContainer::MtProxy) {
ServerCredentials credentials = adminConfig->credentials();
SshSession sshSession(this);
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::Telemt) {
ServerCredentials credentials = adminConfig->credentials();
SshSession sshSession(this);
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
adminConfig->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
return ErrorCode::NoError;
@@ -192,11 +165,6 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
}
if (errorCode == ErrorCode::NoError) {
if (container == DockerContainer::MtProxy) {
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
} else if (container == DockerContainer::Telemt) {
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
clearCachedProfile(serverId, container);
adminConfig->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
@@ -440,24 +408,9 @@ ErrorCode InstallController::configureContainerWorker(const ServerCredentials &c
sshSession.replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), baseVars),
cbReadStdOut, cbReadStdErr);
if (e != ErrorCode::NoError) {
return e;
}
if (dockerDaemonContainerMissing(stdOut, ContainerUtils::containerToString(container))) {
qDebug() << "configureContainerWorker: Docker daemon reports container missing/stopped, output:" << stdOut;
return ErrorCode::ServerContainerMissingError;
}
updateContainerConfigAfterInstallation(container, config, stdOut);
if (container == DockerContainer::MtProxy) {
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
} else if (container == DockerContainer::Telemt) {
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, config);
}
return ErrorCode::NoError;
return e;
}
ErrorCode InstallController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &config, SshSession &sshSession)
@@ -610,79 +563,6 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
}
}
if (container == DockerContainer::MtProxy) {
const auto *oldMt = oldConfig.getMtProxyProtocolConfig();
const auto *newMt = newConfig.getMtProxyProtocolConfig();
if (oldMt && newMt) {
const QString oldPort =
oldMt->port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : oldMt->port;
const QString newPort =
newMt->port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : newMt->port;
if (oldPort != newPort) {
return true;
}
const QString oldTransport = oldMt->transportMode.isEmpty() ? QString(
protocols::mtProxy::transportModeStandard)
: oldMt->transportMode;
const QString newTransport = newMt->transportMode.isEmpty() ? QString(
protocols::mtProxy::transportModeStandard)
: newMt->transportMode;
if (oldTransport != newTransport) {
return true;
}
if (oldMt->tlsDomain != newMt->tlsDomain) {
return true;
}
}
}
if (container == DockerContainer::Telemt) {
const auto *oldT = oldConfig.getTelemtProtocolConfig();
const auto *newT = newConfig.getTelemtProtocolConfig();
if (oldT && newT) {
const QString oldPort =
oldT->port.isEmpty() ? QString(protocols::telemt::defaultPort) : oldT->port;
const QString newPort =
newT->port.isEmpty() ? QString(protocols::telemt::defaultPort) : newT->port;
if (oldPort != newPort) {
return true;
}
const QString oldTransport = oldT->transportMode.isEmpty()
? QString(protocols::telemt::transportModeStandard)
: oldT->transportMode;
const QString newTransport = newT->transportMode.isEmpty()
? QString(protocols::telemt::transportModeStandard)
: newT->transportMode;
if (oldTransport != newTransport) {
return true;
}
if (oldT->tlsDomain != newT->tlsDomain) {
return true;
}
if (oldT->maskEnabled != newT->maskEnabled) {
return true;
}
if (oldT->tlsEmulation != newT->tlsEmulation) {
return true;
}
if (oldT->useMiddleProxy != newT->useMiddleProxy) {
return true;
}
if (oldT->tag != newT->tag) {
return true;
}
const QString oldUser = oldT->userName.isEmpty()
? QString::fromUtf8(protocols::telemt::defaultUserName)
: oldT->userName;
const QString newUser = newT->userName.isEmpty()
? QString::fromUtf8(protocols::telemt::defaultUserName)
: newT->userName;
if (oldUser != newUser) {
return true;
}
}
}
if (container == DockerContainer::Socks5Proxy) {
return true;
}
@@ -774,7 +654,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
if (stdOut.contains("password is required"))
return ErrorCode::ServerUserPasswordRequired;
return error;
@@ -943,8 +823,6 @@ QScopedPointer<InstallerBase> InstallController::createInstaller(DockerContainer
case DockerContainer::TorWebSite: return QScopedPointer<InstallerBase>(new TorInstaller(this));
case DockerContainer::Sftp: return QScopedPointer<InstallerBase>(new SftpInstaller(this));
case DockerContainer::Socks5Proxy: return QScopedPointer<InstallerBase>(new Socks5Installer(this));
case DockerContainer::MtProxy: return QScopedPointer<InstallerBase>(new MtProxyInstaller(this));
case DockerContainer::Telemt: return QScopedPointer<InstallerBase>(new TelemtInstaller(this));
default: return QScopedPointer<InstallerBase>(new InstallerBase(this));
}
}
@@ -983,20 +861,6 @@ bool InstallController::isUpdateDockerContainerRequired(DockerContainer containe
return false;
}
}
} else if (container == DockerContainer::MtProxy) {
const auto *oldMt = oldConfig.getMtProxyProtocolConfig();
const auto *newMt = newConfig.getMtProxyProtocolConfig();
if (!oldMt || !newMt) {
return true;
}
return !oldMt->equalsDockerDeploymentSettings(*newMt);
} else if (container == DockerContainer::Telemt) {
const auto *oldT = oldConfig.getTelemtProtocolConfig();
const auto *newT = newConfig.getTelemtProtocolConfig();
if (!oldT || !newT) {
return true;
}
return !oldT->equalsDockerDeploymentSettings(*newT);
}
return true;
@@ -1300,56 +1164,6 @@ void InstallController::updateContainerConfigAfterInstallation(DockerContainer c
onion.replace("\n", "");
torProtocolConfig->serverConfig.site = onion;
}
} else if (container == DockerContainer::MtProxy) {
if (auto* mtProxyConfig = containerConfig.getMtProxyProtocolConfig()) {
qDebug() << "amnezia mtproxy" << stdOut;
static const QRegularExpression reSecret(
QStringLiteral(R"(\[\*\]\s+Secret:\s+([0-9a-fA-F]{32}))"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression reTgLink(QStringLiteral(R"(\[\*\]\s+tg://\s+link:\s+(tg://proxy\?[^\s]+))"));
static const QRegularExpression reTmeLink(
QStringLiteral(R"(\[\*\]\s+t\.me\s+link:\s+(https://t\.me/proxy\?[^\s]+))"));
const QRegularExpressionMatch mSecret = reSecret.match(stdOut);
const QRegularExpressionMatch mTgLink = reTgLink.match(stdOut);
const QRegularExpressionMatch mTmeLink = reTmeLink.match(stdOut);
if (mSecret.hasMatch()) {
mtProxyConfig->secret = mSecret.captured(1);
}
if (mTgLink.hasMatch()) {
mtProxyConfig->tgLink = mTgLink.captured(1);
}
if (mTmeLink.hasMatch()) {
mtProxyConfig->tmeLink = mTmeLink.captured(1);
}
}
} else if (container == DockerContainer::Telemt) {
if (auto *telemtConfig = containerConfig.getTelemtProtocolConfig()) {
qDebug() << "amnezia-telemt configure stdout" << stdOut;
static const QRegularExpression reSecret(
QStringLiteral(R"(\[\*\]\s+Secret:\s+([0-9a-fA-F]{32}))"),
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression reTgLink(QStringLiteral(R"(\[\*\]\s+tg://\s+link:\s+(tg://proxy\?[^\s]+))"));
static const QRegularExpression reTmeLink(
QStringLiteral(R"(\[\*\]\s+t\.me\s+link:\s+(https://t\.me/proxy\?[^\s]+))"));
const QRegularExpressionMatch mSecret = reSecret.match(stdOut);
const QRegularExpressionMatch mTgLink = reTgLink.match(stdOut);
const QRegularExpressionMatch mTmeLink = reTmeLink.match(stdOut);
if (mSecret.hasMatch()) {
telemtConfig->secret = mSecret.captured(1);
}
if (mTgLink.hasMatch()) {
telemtConfig->tgLink = mTgLink.captured(1);
}
if (mTmeLink.hasMatch()) {
telemtConfig->tmeLink = mTmeLink.captured(1);
}
}
}
}
@@ -1434,126 +1248,3 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
return ErrorCode::NoError;
}
ErrorCode InstallController::setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled)
{
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return ErrorCode::InternalError;
}
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
SshSession sshSession(this);
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
: QStringLiteral("sudo docker stop %1").arg(containerName);
const ErrorCode runError = sshSession.runScript(credentials, script);
if (runError != ErrorCode::NoError) {
return runError;
}
ContainerConfig currentConfig = adminConfig->containerConfig(container);
bool persist = false;
if (auto *mtConfig = currentConfig.getMtProxyProtocolConfig()) {
mtConfig->isEnabled = enabled;
persist = true;
} else if (auto *telemtConfig = currentConfig.getTelemtProtocolConfig()) {
telemtConfig->isEnabled = enabled;
persist = true;
}
if (persist) {
adminConfig->updateContainerConfig(container, currentConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
return ErrorCode::NoError;
}
ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut)
{
statusOut = 3;
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
SshSession sshSession(this);
const QString script = QStringLiteral(
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
.arg(containerName);
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
const QString status = stdOut.trimmed();
if (status == QLatin1String("running")) {
statusOut = 1;
} else if (status == QLatin1String("not_found") || status.isEmpty()) {
statusOut = 0;
} else if (status == QLatin1String("exited") || status == QLatin1String("created")
|| status == QLatin1String("paused")) {
statusOut = 2;
} else {
statusOut = 3;
}
return ErrorCode::NoError;
}
ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out)
{
out = {};
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return ErrorCode::InternalError;
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return ErrorCode::InternalError;
}
SshSession sshSession(this);
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
}
QString InstallController::fetchDockerContainerSecret(const QString &serverId, DockerContainer container)
{
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return {};
}
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return {};
}
ServerCredentials credentials = adminConfig->credentials();
if (!credentials.isValid()) {
return {};
}
const QString containerName = ContainerUtils::containerToString(container);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
SshSession sshSession(this);
const QString path = QStringLiteral("/data/secret");
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return {};
}
const QString secret = stdOut.trimmed();
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
return hex32.match(secret).hasMatch() ? secret : QString();
}

View File

@@ -16,7 +16,6 @@
#include "core/models/containerConfig.h"
#include "core/repositories/secureServersRepository.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/installers/mtProxyInstaller.h"
class SshSession;
class InstallerBase;
@@ -40,16 +39,6 @@ public:
ErrorCode removeAllContainers(const QString &serverId);
ErrorCode removeContainer(const QString &serverId, DockerContainer container);
ErrorCode setDockerContainerEnabledState(const QString &serverId, DockerContainer container, bool enabled);
/// statusOut: 0 = not deployed, 1 = running, 2 = stopped, 3 = error
ErrorCode queryDockerContainerStatus(const QString &serverId, DockerContainer container, int &statusOut);
ErrorCode queryMtProxyDiagnostics(const QString &serverId, DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out);
QString fetchDockerContainerSecret(const QString &serverId, DockerContainer container);
ContainerConfig generateConfig(DockerContainer container, int port, TransportProto transportProto);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap<DockerContainer, ContainerConfig> &installedContainers, SshSession &sshSession);

View File

@@ -1,16 +0,0 @@
#ifndef CONTAINERDIAGNOSTICS_H
#define CONTAINERDIAGNOSTICS_H
namespace amnezia
{
struct ContainerDiagnostics
{
bool available = false;
bool portReachable = false;
virtual ~ContainerDiagnostics() = default;
};
} // namespace amnezia
#endif // CONTAINERDIAGNOSTICS_H

View File

@@ -1,18 +0,0 @@
#ifndef MTPROXYDIAGNOSTICS_H
#define MTPROXYDIAGNOSTICS_H
#include "containerDiagnostics.h"
#include <QString>
namespace amnezia {
struct MtProxyDiagnostics : ContainerDiagnostics {
bool upstreamReachable = false;
int clientsConnected = -1;
QString lastConfigRefresh;
QString statsEndpoint;
};
} // namespace amnezia
#endif // MTPROXYDIAGNOSTICS_H

View File

@@ -1,20 +0,0 @@
#ifndef TELEMTDIAGNOSTICS_H
#define TELEMTDIAGNOSTICS_H
#include "containerDiagnostics.h"
#include <QString>
namespace amnezia
{
struct TelemtDiagnostics : ContainerDiagnostics
{
bool upstreamReachable = false;
int clientsConnected = -1;
QString lastConfigRefresh;
QString statsEndpoint;
};
} // namespace amnezia
#endif // TELEMTDIAGNOSTICS_H

View File

@@ -14,8 +14,6 @@
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/torProtocolConfig.h"
@@ -93,18 +91,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
config.protocolConfig = socks5Config;
break;
}
case Proto::MtProxy: {
MtProxyProtocolConfig mtConfig;
mtConfig.port = portStr;
config.protocolConfig = mtConfig;
break;
}
case Proto::Telemt: {
TelemtProtocolConfig telemtConfig;
telemtConfig.port = portStr;
config.protocolConfig = telemtConfig;
break;
}
case Proto::Ikev2: {
Ikev2ProtocolConfig ikev2Config;
config.protocolConfig = ikev2Config;

View File

@@ -1,130 +0,0 @@
#include "mtProxyInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QRegularExpression>
#include <QtGlobal>
using namespace amnezia;
namespace {
constexpr QLatin1String kMtProxyClientJsonPath("/data/amnezia-mtproxy-client.json");
constexpr QLatin1String kMtProxyClientJsonUploadPath("data/amnezia-mtproxy-client.json");
constexpr QLatin1String kMtProxySecretPath("/data/secret");
}
MtProxyInstaller::MtProxyInstaller(QObject *parent)
: InstallerBase(parent) {
}
ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession *sshSession, ContainerConfig &config) {
if (container != DockerContainer::MtProxy || !sshSession) {
return ErrorCode::NoError;
}
MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
if (!mt) {
return ErrorCode::NoError;
}
ErrorCode jsonErr = ErrorCode::NoError;
const QByteArray jsonRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyClientJsonPath), jsonErr);
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
QJsonParseError parseError;
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
QJsonObject merged = mt->toJson();
const QJsonObject snap = doc.object();
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
merged.insert(it.key(), it.value());
}
*mt = MtProxyProtocolConfig::fromJson(merged);
}
}
ErrorCode secretErr = ErrorCode::NoError;
const QByteArray secretRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxySecretPath), secretErr);
const QString sec = QString::fromUtf8(secretRaw).trimmed();
if (sec.length() == 32) {
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
if (hex32.match(sec).hasMatch()) {
mt->secret = sec;
}
}
return ErrorCode::NoError;
}
ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const ServerCredentials &credentials,
DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out)
{
out = {};
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return ErrorCode::InternalError;
}
const QString containerName = ContainerUtils::containerToString(container);
const QString script =
QStringLiteral(
"PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); "
"TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); "
"CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); "
"CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); "
"echo \"PORT_OK=${PORT_OK}\"; "
"echo \"TG_OK=${TG_OK}\"; "
"echo \"CLIENTS=${CLIENTS:-0}\"; "
"echo \"CONF_TIME=${CONF_TIME}\"; "
"echo \"STATS=http://localhost:2398/stats\";")
.arg(containerName)
.arg(listenPort);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
if (line.startsWith(QLatin1String("PORT_OK="))) {
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("TG_OK="))) {
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
out.clientsConnected = line.mid(8).trimmed().toInt();
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
out.lastConfigRefresh = line.mid(10).trimmed();
} else if (line.startsWith(QLatin1String("STATS="))) {
out.statsEndpoint = line.mid(6).trimmed();
}
}
return ErrorCode::NoError;
}
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
DockerContainer container, const ContainerConfig &config) {
const MtProxyProtocolConfig *mt = config.getMtProxyProtocolConfig();
if (!mt) {
return;
}
const QByteArray payload = QJsonDocument(mt->toJson()).toJson(QJsonDocument::Compact);
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
QString(kMtProxyClientJsonUploadPath));
if (err != ErrorCode::NoError) {
qWarning() << "MtProxyInstaller::uploadClientSettingsSnapshot failed" << err;
}
}

View File

@@ -1,34 +0,0 @@
#ifndef MTPROXYINSTALLER_H
#define MTPROXYINSTALLER_H
#include "installerBase.h"
#include <QString>
struct MtProxyContainerDiagnostics {
bool portReachable = false;
bool upstreamReachable = false;
int clientsConnected = -1;
QString lastConfigRefresh;
QString statsEndpoint;
};
class MtProxyInstaller : public InstallerBase {
Q_OBJECT
public:
explicit MtProxyInstaller(QObject *parent = nullptr);
amnezia::ErrorCode
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession *sshSession, amnezia::ContainerConfig &config) override;
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
const amnezia::ContainerConfig &config);
static amnezia::ErrorCode queryDiagnostics(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, int listenPort,
MtProxyContainerDiagnostics &out);
};
#endif // MTPROXYINSTALLER_H

View File

@@ -1,79 +0,0 @@
#include "telemtInstaller.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonParseError>
#include <QRegularExpression>
#include <QtGlobal>
using namespace amnezia;
namespace {
constexpr QLatin1String kTelemtClientJsonPath("/data/amnezia-telemt-client.json");
constexpr QLatin1String kTelemtClientJsonUploadPath("data/amnezia-telemt-client.json");
constexpr QLatin1String kTelemtSecretPath("/data/secret");
}
TelemtInstaller::TelemtInstaller(QObject *parent) : InstallerBase(parent) {}
ErrorCode TelemtInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
SshSession *sshSession, ContainerConfig &config) {
if (container != DockerContainer::Telemt || !sshSession) {
return ErrorCode::NoError;
}
TelemtProtocolConfig *tc = config.getTelemtProtocolConfig();
if (!tc) {
return ErrorCode::NoError;
}
ErrorCode jsonErr = ErrorCode::NoError;
const QByteArray jsonRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtClientJsonPath), jsonErr);
if (jsonErr == ErrorCode::NoError && !jsonRaw.trimmed().isEmpty()) {
QJsonParseError parseError;
const QJsonDocument doc = QJsonDocument::fromJson(jsonRaw.trimmed(), &parseError);
if (parseError.error == QJsonParseError::NoError && doc.isObject()) {
QJsonObject merged = tc->toJson();
const QJsonObject snap = doc.object();
for (auto it = snap.constBegin(); it != snap.constEnd(); ++it) {
merged.insert(it.key(), it.value());
}
*tc = TelemtProtocolConfig::fromJson(merged);
}
}
ErrorCode secretErr = ErrorCode::NoError;
const QByteArray secretRaw =
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtSecretPath), secretErr);
const QString sec = QString::fromUtf8(secretRaw).trimmed();
if (sec.length() == 32) {
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
if (hex32.match(sec).hasMatch()) {
tc->secret = sec;
}
}
return ErrorCode::NoError;
}
void TelemtInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
DockerContainer container, const ContainerConfig &config) {
const TelemtProtocolConfig *tc = config.getTelemtProtocolConfig();
if (!tc) {
return;
}
const QByteArray payload = QJsonDocument(tc->toJson()).toJson(QJsonDocument::Compact);
const ErrorCode err = sshSession.uploadTextFileToContainer(container, credentials, QString::fromUtf8(payload),
QString(kTelemtClientJsonUploadPath));
if (err != ErrorCode::NoError) {
qWarning() << "TelemtInstaller::uploadClientSettingsSnapshot failed" << err;
}
}

View File

@@ -1,20 +0,0 @@
#ifndef TELEMTINSTALLER_H
#define TELEMTINSTALLER_H
#include "installerBase.h"
class TelemtInstaller : public InstallerBase {
Q_OBJECT
public:
explicit TelemtInstaller(QObject *parent = nullptr);
amnezia::ErrorCode
extractConfigFromContainer(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
SshSession *sshSession, amnezia::ContainerConfig &config) override;
static void uploadClientSettingsSnapshot(SshSession &sshSession, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container,
const amnezia::ContainerConfig &config);
};
#endif // TELEMTINSTALLER_H

View File

@@ -113,26 +113,6 @@ const Socks5ProxyProtocolConfig* ContainerConfig::getSocks5ProxyProtocolConfig()
return protocolConfig.as<Socks5ProxyProtocolConfig>();
}
MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig()
{
return protocolConfig.as<MtProxyProtocolConfig>();
}
const MtProxyProtocolConfig* ContainerConfig::getMtProxyProtocolConfig() const
{
return protocolConfig.as<MtProxyProtocolConfig>();
}
TelemtProtocolConfig* ContainerConfig::getTelemtProtocolConfig()
{
return protocolConfig.as<TelemtProtocolConfig>();
}
const TelemtProtocolConfig* ContainerConfig::getTelemtProtocolConfig() const
{
return protocolConfig.as<TelemtProtocolConfig>();
}
Ikev2ProtocolConfig* ContainerConfig::getIkev2ProtocolConfig()
{
return protocolConfig.as<Ikev2ProtocolConfig>();

View File

@@ -57,12 +57,6 @@ struct ContainerConfig {
Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig();
const Socks5ProxyProtocolConfig* getSocks5ProxyProtocolConfig() const;
MtProxyProtocolConfig* getMtProxyProtocolConfig();
const MtProxyProtocolConfig* getMtProxyProtocolConfig() const;
TelemtProtocolConfig* getTelemtProtocolConfig();
const TelemtProtocolConfig* getTelemtProtocolConfig() const;
Ikev2ProtocolConfig* getIkev2ProtocolConfig();
const Ikev2ProtocolConfig* getIkev2ProtocolConfig() const;

View File

@@ -9,8 +9,6 @@
#include "core/utils/protocolEnum.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/dnsProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
namespace amnezia
{
@@ -40,10 +38,6 @@ Proto ProtocolConfig::type() const
return Proto::TorWebSite;
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
return Proto::Dns;
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
return Proto::MtProxy;
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
return Proto::Telemt;
}
return Proto::Unknown;
}, data);
@@ -71,10 +65,6 @@ QString ProtocolConfig::port() const
return QString();
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
return QString();
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
return arg.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : arg.port;
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
return arg.port.isEmpty() ? QString(protocols::telemt::defaultPort) : arg.port;
}
return QString();
}, data);
@@ -98,10 +88,6 @@ QString ProtocolConfig::transportProto() const
return QString();
} else if constexpr (std::is_same_v<T, DnsProtocolConfig>) {
return QString();
} else if constexpr (std::is_same_v<T, MtProxyProtocolConfig>) {
return QStringLiteral("tcp");
} else if constexpr (std::is_same_v<T, TelemtProtocolConfig>) {
return QStringLiteral("tcp");
}
return QString();
}, data);
@@ -313,10 +299,6 @@ ProtocolConfig ProtocolConfig::fromJson(const QJsonObject& json, Proto type)
return ProtocolConfig{TorProtocolConfig::fromJson(json)};
case Proto::Dns:
return ProtocolConfig{DnsProtocolConfig::fromJson(json)};
case Proto::MtProxy:
return ProtocolConfig{MtProxyProtocolConfig::fromJson(json)};
case Proto::Telemt:
return ProtocolConfig{TelemtProtocolConfig::fromJson(json)};
default:
return ProtocolConfig{AwgProtocolConfig{}};
}

View File

@@ -22,8 +22,6 @@
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "core/models/protocols/torProtocolConfig.h"
#include "core/models/protocols/dnsProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
namespace amnezia
{
@@ -38,8 +36,6 @@ struct ProtocolConfig {
XrayProtocolConfig,
SftpProtocolConfig,
Socks5ProxyProtocolConfig,
MtProxyProtocolConfig,
TelemtProtocolConfig,
Ikev2ProtocolConfig,
TorProtocolConfig,
DnsProtocolConfig

View File

@@ -1,147 +0,0 @@
#include "mtProxyProtocolConfig.h"
#include "../../../core/utils/protocolEnum.h"
#include "../../../core/protocols/protocolUtils.h"
#include "../../../core/utils/constants/configKeys.h"
#include "../../../core/utils/constants/protocolConstants.h"
#include <QJsonArray>
#include <algorithm>
using namespace amnezia;
namespace amnezia {
QJsonObject MtProxyProtocolConfig::toJson() const {
QJsonObject obj;
if (!port.isEmpty()) {
obj[configKey::port] = port;
}
if (!secret.isEmpty()) {
obj[protocols::mtProxy::secretKey] = secret;
}
if (!tag.isEmpty()) {
obj[protocols::mtProxy::tagKey] = tag;
}
if (!tgLink.isEmpty()) {
obj[protocols::mtProxy::tgLinkKey] = tgLink;
}
if (!tmeLink.isEmpty()) {
obj[protocols::mtProxy::tmeLinkKey] = tmeLink;
}
obj[protocols::mtProxy::isEnabledKey] = isEnabled;
if (!publicHost.isEmpty()) {
obj[protocols::mtProxy::publicHostKey] = publicHost;
}
if (!transportMode.isEmpty()) {
obj[protocols::mtProxy::transportModeKey] = transportMode;
}
if (!tlsDomain.isEmpty()) {
obj[protocols::mtProxy::tlsDomainKey] = tlsDomain;
}
if (!additionalSecrets.isEmpty()) {
obj[protocols::mtProxy::additionalSecretsKey] = QJsonArray::fromStringList(additionalSecrets);
}
if (!workersMode.isEmpty()) {
obj[protocols::mtProxy::workersModeKey] = workersMode;
}
if (!workers.isEmpty()) {
obj[protocols::mtProxy::workersKey] = workers;
}
obj[protocols::mtProxy::natEnabledKey] = natEnabled;
if (!natInternalIp.isEmpty()) {
obj[protocols::mtProxy::natInternalIpKey] = natInternalIp;
}
if (!natExternalIp.isEmpty()) {
obj[protocols::mtProxy::natExternalIpKey] = natExternalIp;
}
return obj;
}
MtProxyProtocolConfig MtProxyProtocolConfig::fromJson(const QJsonObject &json) {
MtProxyProtocolConfig config;
config.port = json.value(configKey::port).toString();
config.secret = json.value(protocols::mtProxy::secretKey).toString();
config.tag = json.value(protocols::mtProxy::tagKey).toString();
config.tgLink = json.value(protocols::mtProxy::tgLinkKey).toString();
config.tmeLink = json.value(protocols::mtProxy::tmeLinkKey).toString();
config.isEnabled = json.value(protocols::mtProxy::isEnabledKey).toBool(true);
config.publicHost = json.value(protocols::mtProxy::publicHostKey).toString();
config.transportMode = json.value(protocols::mtProxy::transportModeKey).toString();
config.tlsDomain = json.value(protocols::mtProxy::tlsDomainKey).toString();
for (const auto &v: json.value(protocols::mtProxy::additionalSecretsKey).toArray()) {
const QString s = v.toString();
if (!s.isEmpty()) {
config.additionalSecrets.append(s);
}
}
config.workersMode = json.value(protocols::mtProxy::workersModeKey).toString();
config.workers = json.value(protocols::mtProxy::workersKey).toString();
config.natEnabled = json.value(protocols::mtProxy::natEnabledKey).toBool(false);
config.natInternalIp = json.value(protocols::mtProxy::natInternalIpKey).toString();
config.natExternalIp = json.value(protocols::mtProxy::natExternalIpKey).toString();
return config;
}
bool MtProxyProtocolConfig::equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const {
const auto normPort = [](const QString &p) {
return p.isEmpty() ? QString(protocols::mtProxy::defaultPort) : p;
};
const auto normTransport = [](const QString &t) {
return t.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) : t;
};
const auto normWorkersMode = [](const QString &m) {
return m.isEmpty() ? QString(protocols::mtProxy::workersModeAuto) : m;
};
if (normPort(port) != normPort(other.port)) {
return false;
}
if (normTransport(transportMode) != normTransport(other.transportMode)) {
return false;
}
if (tlsDomain != other.tlsDomain) {
return false;
}
if (secret != other.secret) {
return false;
}
if (tag != other.tag) {
return false;
}
if (publicHost != other.publicHost) {
return false;
}
if (normWorkersMode(workersMode) != normWorkersMode(other.workersMode)) {
return false;
}
if (workers != other.workers) {
return false;
}
if (natEnabled != other.natEnabled) {
return false;
}
if (natInternalIp != other.natInternalIp) {
return false;
}
if (natExternalIp != other.natExternalIp) {
return false;
}
if (isEnabled != other.isEnabled) {
return false;
}
QStringList aa = additionalSecrets;
QStringList bb = other.additionalSecrets;
aa.removeAll(QString());
bb.removeAll(QString());
std::sort(aa.begin(), aa.end());
std::sort(bb.begin(), bb.end());
return aa == bb;
}
} // namespace amnezia

View File

@@ -1,38 +0,0 @@
#ifndef MTPROXYPROTOCOLCONFIG_H
#define MTPROXYPROTOCOLCONFIG_H
#include <QJsonObject>
#include <QString>
#include <QStringList>
namespace amnezia {
struct MtProxyProtocolConfig {
QString port;
QString secret;
QString tag;
QString tgLink;
QString tmeLink;
bool isEnabled = true;
QString publicHost;
QString transportMode;
QString tlsDomain;
QStringList additionalSecrets;
QString workersMode;
QString workers;
bool natEnabled = false;
QString natInternalIp;
QString natExternalIp;
QJsonObject toJson() const;
static MtProxyProtocolConfig fromJson(const QJsonObject &json);
// Port, transport, TLS, secrets, NAT, workers, isEnabled, additionalSecrets (order-independent).
// Ignores tgLink / tmeLink (derived / display).
bool equalsDockerDeploymentSettings(const MtProxyProtocolConfig &other) const;
};
} // namespace amnezia
#endif // MTPROXYPROTOCOLCONFIG_H

View File

@@ -1,162 +0,0 @@
#include "telemtProtocolConfig.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include <QJsonArray>
#include <algorithm>
using namespace amnezia;
QJsonObject TelemtProtocolConfig::toJson() const
{
QJsonObject obj;
if (!port.isEmpty()) {
obj[QString(configKey::port)] = port;
}
if (!secret.isEmpty()) {
obj[protocols::telemt::secretKey] = secret;
}
if (!tag.isEmpty()) {
obj[protocols::telemt::tagKey] = tag;
}
if (!tgLink.isEmpty()) {
obj[protocols::telemt::tgLinkKey] = tgLink;
}
if (!tmeLink.isEmpty()) {
obj[protocols::telemt::tmeLinkKey] = tmeLink;
}
obj[protocols::telemt::isEnabledKey] = isEnabled;
if (!publicHost.isEmpty()) {
obj[protocols::telemt::publicHostKey] = publicHost;
}
if (!transportMode.isEmpty()) {
obj[protocols::telemt::transportModeKey] = transportMode;
}
if (!tlsDomain.isEmpty()) {
obj[protocols::telemt::tlsDomainKey] = tlsDomain;
}
obj[protocols::telemt::maskEnabledKey] = maskEnabled;
obj[protocols::telemt::tlsEmulationKey] = tlsEmulation;
obj[protocols::telemt::useMiddleProxyKey] = useMiddleProxy;
if (!userName.isEmpty()) {
obj[protocols::telemt::userNameKey] = userName;
}
if (!additionalSecrets.isEmpty()) {
obj[protocols::telemt::additionalSecretsKey] = QJsonArray::fromStringList(additionalSecrets);
}
if (!workersMode.isEmpty()) {
obj[protocols::telemt::workersModeKey] = workersMode;
}
if (!workers.isEmpty()) {
obj[protocols::telemt::workersKey] = workers;
}
obj[protocols::telemt::natEnabledKey] = natEnabled;
if (!natInternalIp.isEmpty()) {
obj[protocols::telemt::natInternalIpKey] = natInternalIp;
}
if (!natExternalIp.isEmpty()) {
obj[protocols::telemt::natExternalIpKey] = natExternalIp;
}
return obj;
}
TelemtProtocolConfig TelemtProtocolConfig::fromJson(const QJsonObject &json)
{
TelemtProtocolConfig c;
c.port = json.value(QString(configKey::port)).toString();
c.secret = json.value(protocols::telemt::secretKey).toString();
c.tag = json.value(protocols::telemt::tagKey).toString();
c.tgLink = json.value(protocols::telemt::tgLinkKey).toString();
c.tmeLink = json.value(protocols::telemt::tmeLinkKey).toString();
c.isEnabled = json.value(protocols::telemt::isEnabledKey).toBool(true);
c.publicHost = json.value(protocols::telemt::publicHostKey).toString();
c.transportMode = json.value(protocols::telemt::transportModeKey).toString();
c.tlsDomain = json.value(protocols::telemt::tlsDomainKey).toString();
c.maskEnabled = json.value(protocols::telemt::maskEnabledKey).toBool(true);
c.tlsEmulation = json.value(protocols::telemt::tlsEmulationKey).toBool(false);
c.useMiddleProxy = json.value(protocols::telemt::useMiddleProxyKey).toBool(true);
c.userName = json.value(protocols::telemt::userNameKey).toString();
for (const auto &v : json.value(protocols::telemt::additionalSecretsKey).toArray()) {
const QString s = v.toString();
if (!s.isEmpty()) {
c.additionalSecrets.append(s);
}
}
c.workersMode = json.value(protocols::telemt::workersModeKey).toString();
c.workers = json.value(protocols::telemt::workersKey).toString();
c.natEnabled = json.value(protocols::telemt::natEnabledKey).toBool(false);
c.natInternalIp = json.value(protocols::telemt::natInternalIpKey).toString();
c.natExternalIp = json.value(protocols::telemt::natExternalIpKey).toString();
return c;
}
bool TelemtProtocolConfig::equalsDockerDeploymentSettings(const TelemtProtocolConfig &other) const
{
const auto normPort = [](const QString &p) {
return p.isEmpty() ? QString(protocols::telemt::defaultPort) : p;
};
const auto normTransport = [](const QString &t) {
return t.isEmpty() ? QString(protocols::telemt::transportModeStandard) : t;
};
const auto normWorkersMode = [](const QString &m) {
return m.isEmpty() ? QString(protocols::telemt::workersModeAuto) : m;
};
if (normPort(port) != normPort(other.port)) {
return false;
}
if (normTransport(transportMode) != normTransport(other.transportMode)) {
return false;
}
if (tlsDomain != other.tlsDomain) {
return false;
}
if (secret != other.secret) {
return false;
}
if (tag != other.tag) {
return false;
}
if (publicHost != other.publicHost) {
return false;
}
if (maskEnabled != other.maskEnabled) {
return false;
}
if (tlsEmulation != other.tlsEmulation) {
return false;
}
if (useMiddleProxy != other.useMiddleProxy) {
return false;
}
if (userName != other.userName) {
return false;
}
if (normWorkersMode(workersMode) != normWorkersMode(other.workersMode)) {
return false;
}
if (workers != other.workers) {
return false;
}
if (natEnabled != other.natEnabled) {
return false;
}
if (natInternalIp != other.natInternalIp) {
return false;
}
if (natExternalIp != other.natExternalIp) {
return false;
}
if (isEnabled != other.isEnabled) {
return false;
}
QStringList aa = additionalSecrets;
QStringList bb = other.additionalSecrets;
aa.removeAll(QString());
bb.removeAll(QString());
std::sort(aa.begin(), aa.end());
std::sort(bb.begin(), bb.end());
return aa == bb;
}

View File

@@ -1,38 +0,0 @@
#ifndef TELEMTPROTOCOLCONFIG_H
#define TELEMTPROTOCOLCONFIG_H
#include <QJsonObject>
#include <QString>
#include <QStringList>
namespace amnezia {
struct TelemtProtocolConfig {
QString port;
QString secret;
QString tag;
QString tgLink;
QString tmeLink;
bool isEnabled = true;
QString publicHost;
QString transportMode;
QString tlsDomain;
bool maskEnabled = true;
bool tlsEmulation = false;
bool useMiddleProxy = true;
QString userName;
QStringList additionalSecrets;
QString workersMode;
QString workers;
bool natEnabled = false;
QString natInternalIp;
QString natExternalIp;
QJsonObject toJson() const;
static TelemtProtocolConfig fromJson(const QJsonObject &json);
bool equalsDockerDeploymentSettings(const TelemtProtocolConfig &other) const;
};
} // namespace amnezia
#endif // TELEMTPROTOCOLCONFIG_H

View File

@@ -68,10 +68,7 @@ QMap<Proto, QString> ProtocolUtils::protocolHumanNames()
{ Proto::TorWebSite, "Website in Tor network" },
{ Proto::Dns, "DNS Service" },
{ Proto::Sftp, QObject::tr("SFTP service") },
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
{ Proto::MtProxy, QObject::tr("MTProxy (Telegram)") },
{ Proto::Telemt, QObject::tr("Telemt (Telegram)") },
};
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
}
QMap<Proto, QString> ProtocolUtils::protocolDescriptions()
@@ -95,8 +92,6 @@ ServiceType ProtocolUtils::protocolService(Proto p)
case Proto::Dns: return ServiceType::Other;
case Proto::Sftp: return ServiceType::Other;
case Proto::Socks5Proxy: return ServiceType::Other;
case Proto::MtProxy: return ServiceType::Other;
case Proto::Telemt: return ServiceType::Other;
default: return ServiceType::Other;
}
}
@@ -109,8 +104,6 @@ int ProtocolUtils::getPortForInstall(Proto p)
case OpenVpn:
case Socks5Proxy:
return QRandomGenerator::global()->bounded(30000, 50000);
case MtProxy:
case Telemt:
default:
return defaultPort(p);
}
@@ -130,8 +123,6 @@ int ProtocolUtils::defaultPort(Proto p)
case Proto::Dns: return 53;
case Proto::Sftp: return 222;
case Proto::Socks5Proxy: return 38080;
case Proto::MtProxy: return QString(protocols::mtProxy::defaultPort).toInt();
case Proto::Telemt: return QString(protocols::telemt::defaultPort).toInt();
default: return -1;
}
}
@@ -150,8 +141,6 @@ bool ProtocolUtils::defaultPortChangeable(Proto p)
case Proto::Dns: return false;
case Proto::Sftp: return true;
case Proto::Socks5Proxy: return true;
case Proto::MtProxy: return true;
case Proto::Telemt: return true;
default: return false;
}
}
@@ -172,8 +161,6 @@ TransportProto ProtocolUtils::defaultTransportProto(Proto p)
case Proto::Dns: return TransportProto::Udp;
case Proto::Sftp: return TransportProto::Tcp;
case Proto::Socks5Proxy: return TransportProto::Tcp;
case Proto::MtProxy: return TransportProto::Tcp;
case Proto::Telemt: return TransportProto::Tcp;
default: return TransportProto::Udp;
}
}
@@ -193,10 +180,9 @@ bool ProtocolUtils::defaultTransportProtoChangeable(Proto p)
case Proto::Dns: return false;
case Proto::Sftp: return false;
case Proto::Socks5Proxy: return false;
case Proto::MtProxy: return false;
case Proto::Telemt: return false;
default: return false;
}
return false;
}
QString ProtocolUtils::key_proto_config_data(Proto p)
@@ -222,3 +208,4 @@ QString ProtocolUtils::getProtocolVersionString(const QJsonObject &protocolConfi
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
return "";
}

View File

@@ -93,8 +93,6 @@ namespace amnezia
constexpr QLatin1String xray("xray");
constexpr QLatin1String ssxray("ssxray");
constexpr QLatin1String socks5proxy("socks5proxy");
constexpr QLatin1String mtproxy("mtproxy");
constexpr QLatin1String telemt("telemt");
constexpr QLatin1String splitTunnelSites("splitTunnelSites");
constexpr QLatin1String splitTunnelType("splitTunnelType");

View File

@@ -3,7 +3,6 @@
namespace amnezia
{
namespace protocols
{
@@ -175,71 +174,9 @@ namespace amnezia
constexpr char proxyConfigPath[] = "/usr/local/3proxy/conf/3proxy.cfg";
}
namespace mtProxy
{
constexpr char secretKey[] = "mtproxy_secret";
constexpr char tagKey[] = "mtproxy_tag";
constexpr char tgLinkKey[] = "mtproxy_tg_link";
constexpr char tmeLinkKey[] = "mtproxy_tme_link";
constexpr char isEnabledKey[] = "mtproxy_is_enabled";
constexpr char publicHostKey[] = "mtproxy_public_host";
constexpr char transportModeKey[] = "mtproxy_transport_mode";
constexpr char tlsDomainKey[] = "mtproxy_tls_domain";
constexpr char additionalSecretsKey[] = "mtproxy_additional_secrets";
constexpr char workersKey[] = "mtproxy_workers";
constexpr char workersModeKey[] = "mtproxy_workers_mode";
constexpr char natEnabledKey[] = "mtproxy_nat_enabled";
constexpr char natInternalIpKey[] = "mtproxy_nat_internal_ip";
constexpr char natExternalIpKey[] = "mtproxy_nat_external_ip";
constexpr char transportModeStandard[] = "standard";
constexpr char transportModeFakeTLS[] = "faketls";
constexpr char workersModeAuto[] = "auto";
constexpr char workersModeManual[] = "manual";
constexpr char defaultPort[] = "443";
constexpr char defaultWorkers[] = "2";
constexpr int maxWorkers = 32;
constexpr int botTagHexLength = 32;
constexpr char defaultTlsDomain[] = "googletagmanager.com";
}
namespace telemt
{
constexpr char secretKey[] = "telemt_secret";
constexpr char tagKey[] = "telemt_tag";
constexpr char tgLinkKey[] = "telemt_tg_link";
constexpr char tmeLinkKey[] = "telemt_tme_link";
constexpr char isEnabledKey[] = "telemt_is_enabled";
constexpr char publicHostKey[] = "telemt_public_host";
constexpr char transportModeKey[] = "telemt_transport_mode";
constexpr char tlsDomainKey[] = "telemt_tls_domain";
constexpr char maskEnabledKey[] = "telemt_mask_enabled";
constexpr char tlsEmulationKey[] = "telemt_tls_emulation";
constexpr char useMiddleProxyKey[] = "telemt_use_middle_proxy";
constexpr char userNameKey[] = "telemt_user_name";
// Stored for UI only (Telemt server ignores these; same controls as MTProxy page)
constexpr char additionalSecretsKey[] = "telemt_additional_secrets";
constexpr char workersKey[] = "telemt_workers";
constexpr char workersModeKey[] = "telemt_workers_mode";
constexpr char natEnabledKey[] = "telemt_nat_enabled";
constexpr char natInternalIpKey[] = "telemt_nat_internal_ip";
constexpr char natExternalIpKey[] = "telemt_nat_external_ip";
constexpr char transportModeStandard[] = "standard";
constexpr char transportModeFakeTLS[] = "faketls";
constexpr char defaultPort[] = "443";
constexpr char defaultTlsDomain[] = "googletagmanager.com";
constexpr char defaultUserName[] = "amnezia";
constexpr char defaultWorkers[] = "2";
constexpr char workersModeAuto[] = "auto";
constexpr char workersModeManual[] = "manual";
constexpr int maxWorkers = 32;
}
} // namespace protocols
}
#endif // PROTOCOLCONSTANTS_H

View File

@@ -23,9 +23,7 @@ namespace amnezia
TorWebSite,
Dns,
Sftp,
Socks5Proxy,
MtProxy,
Telemt,
Socks5Proxy
};
Q_ENUM_NS(DockerContainer)
} // namespace ContainerEnumNS

View File

@@ -72,10 +72,7 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
{ DockerContainer::MtProxy, QObject::tr("MTProxy (Telegram)") },
{ DockerContainer::Telemt, QObject::tr("Telemt (Telegram)") },
};
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
}
QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
@@ -105,12 +102,7 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
{ DockerContainer::Sftp,
QObject::tr("Create a file vault on your server to securely store and transfer files.") },
{ DockerContainer::Socks5Proxy,
QObject::tr("") },
{ DockerContainer::MtProxy,
QObject::tr("Telegram MTProto proxy server") },
{ DockerContainer::Telemt,
QObject::tr("Telegram MTProto proxy (Telemt, Rust)") },
};
QObject::tr("") } };
}
QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
@@ -180,15 +172,7 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
"You will be able to access it using\n FileZilla or other SFTP clients, "
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") },
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") },
{ DockerContainer::MtProxy,
QObject::tr("Telegram MTProto proxy server. "
"Allows Telegram clients to connect through your server "
"using the MTProto protocol. Supports FakeTLS mode for "
"bypassing DPI-based blocking.") },
{ DockerContainer::Telemt,
QObject::tr("Telegram MTProto proxy powered by Telemt (Rust). "
"Supports secure and TLS fronting modes with optional traffic masking.") },
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") }
};
}
@@ -213,8 +197,6 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
case DockerContainer::Dns: return Proto::Dns;
case DockerContainer::Sftp: return Proto::Sftp;
case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy;
case DockerContainer::MtProxy: return Proto::MtProxy;
case DockerContainer::Telemt: return Proto::Telemt;
default: return Proto::Unknown;
}
}
@@ -242,8 +224,6 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
default:
return false;
}
@@ -257,8 +237,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
return false;
default:
return false;
}
@@ -277,8 +256,6 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::MtProxy: return true;
case DockerContainer::Telemt: return true;
default: return false;
}
@@ -341,8 +318,6 @@ bool ContainerUtils::isShareable(DockerContainer container)
case DockerContainer::Dns: return false;
case DockerContainer::Sftp: return false;
case DockerContainer::Socks5Proxy: return false;
case DockerContainer::MtProxy: return false;
case DockerContainer::Telemt: return false;
default: return true;
}
}
@@ -371,10 +346,8 @@ int ContainerUtils::installPageOrder(DockerContainer container)
case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8;
case DockerContainer::MtProxy:
case DockerContainer::Telemt:
return 20;
default: return 0;
}
}

View File

@@ -30,9 +30,7 @@ namespace amnezia
TorWebSite,
Dns,
Sftp,
Socks5Proxy,
MtProxy,
Telemt,
Socks5Proxy
};
Q_ENUM_NS(Proto)

View File

@@ -9,6 +9,7 @@
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
@@ -19,8 +20,6 @@
#include "core/models/protocols/xrayProtocolConfig.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
#include "core/models/protocols/telemtProtocolConfig.h"
using namespace amnezia;
using namespace ProtocolUtils;
@@ -39,8 +38,6 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::Dns: return QLatin1String("dns");
case DockerContainer::Sftp: return QLatin1String("sftp");
case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy");
case DockerContainer::MtProxy: return QLatin1String("mtproxy");
case DockerContainer::Telemt: return QLatin1String("telemt");
default: return QString();
}
}
@@ -287,86 +284,6 @@ amnezia::ScriptVars amnezia::genSocks5ProxyVars(const ContainerConfig &container
return vars;
}
amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConfig) {
ScriptVars vars;
if (auto *mtProxyProtocolConfig = containerConfig.getMtProxyProtocolConfig()) {
const MtProxyProtocolConfig &c = *mtProxyProtocolConfig;
vars.append({{"$MTPROXY_PORT", c.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : c.port}});
vars.append({{"$MTPROXY_SECRET", c.secret}});
vars.append({{"$MTPROXY_TAG", c.tag}});
vars.append({{"$MTPROXY_TRANSPORT_MODE",
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard)
: c.transportMode}});
QString tlsDomain = c.tlsDomain;
if (tlsDomain.isEmpty()) {
tlsDomain = QString(protocols::mtProxy::defaultTlsDomain);
}
vars.append({{"$MTPROXY_TLS_DOMAIN", tlsDomain}});
vars.append({{"$MTPROXY_PUBLIC_HOST", c.publicHost}});
QStringList additionalList;
for (const QString &s: c.additionalSecrets) {
if (!s.isEmpty()) {
additionalList << s;
}
}
vars.append({{"$MTPROXY_ADDITIONAL_SECRETS", additionalList.join(QLatin1Char(','))}});
const QString workersMode = c.workersMode.isEmpty() ? QString(protocols::mtProxy::workersModeAuto)
: c.workersMode;
QString workers;
if (workersMode == QLatin1String(protocols::mtProxy::workersModeManual)) {
workers = c.workers.isEmpty() ? QString(protocols::mtProxy::defaultWorkers) : c.workers;
} else {
const QString transportMode =
c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) : c.transportMode;
workers = (transportMode == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) ? QStringLiteral("0")
: QStringLiteral("2");
}
vars.append({{"$MTPROXY_WORKERS", workers}});
vars.append({{"$MTPROXY_NAT_ENABLED", c.natEnabled ? QStringLiteral("1") : QStringLiteral("0")}});
vars.append({{"$MTPROXY_NAT_INTERNAL_IP", c.natInternalIp}});
vars.append({{"$MTPROXY_NAT_EXTERNAL_IP", c.natExternalIp}});
}
return vars;
}
amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfig)
{
ScriptVars vars;
if (auto *telemtProtocolConfig = containerConfig.getTelemtProtocolConfig()) {
const TelemtProtocolConfig &c = *telemtProtocolConfig;
const QString transport = c.transportMode.isEmpty() ? QString(protocols::telemt::transportModeStandard)
: c.transportMode;
const bool faketls = (transport == QLatin1String(protocols::telemt::transportModeFakeTLS));
vars.append({ { "$TELEMT_TOML_SECURE", faketls ? QLatin1String("false") : QLatin1String("true") } });
vars.append({ { "$TELEMT_TOML_TLS", faketls ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_PORT", c.port.isEmpty() ? QString(protocols::telemt::defaultPort) : c.port } });
vars.append({ { "$TELEMT_SECRET", c.secret } });
vars.append({ { "$TELEMT_TAG", c.tag } });
QString tlsDomain = c.tlsDomain;
if (tlsDomain.isEmpty()) {
tlsDomain = QString(protocols::telemt::defaultTlsDomain);
}
vars.append({ { "$TELEMT_TLS_DOMAIN", tlsDomain } });
vars.append({ { "$TELEMT_PUBLIC_HOST", c.publicHost } });
vars.append({ { "$TELEMT_USER_NAME",
c.userName.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultUserName) : c.userName } });
vars.append({ { "$TELEMT_USE_MIDDLE_PROXY", c.useMiddleProxy ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_MASK", c.maskEnabled ? QLatin1String("true") : QLatin1String("false") } });
vars.append({ { "$TELEMT_TLS_EMULATION", c.tlsEmulation ? QLatin1String("true") : QLatin1String("false") } });
}
return vars;
}
amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig)
{
ScriptVars vars;
@@ -391,12 +308,6 @@ amnezia::ScriptVars amnezia::genProtocolVarsForContainer(DockerContainer contain
case Proto::Socks5Proxy:
vars.append(genSocks5ProxyVars(containerConfig));
break;
case Proto::MtProxy:
vars.append(genMtProxyVars(containerConfig));
break;
case Proto::Telemt:
vars.append(genTelemtVars(containerConfig));
break;
default:
break;
}

View File

@@ -67,8 +67,6 @@ ScriptVars genWireGuardVars(const ContainerConfig &containerConfig);
ScriptVars genAwgVars(const ContainerConfig &containerConfig);
ScriptVars genSftpVars(const ContainerConfig &containerConfig);
ScriptVars genSocks5ProxyVars(const ContainerConfig &containerConfig);
ScriptVars genMtProxyVars(const ContainerConfig &containerConfig);
ScriptVars genTelemtVars(const ContainerConfig &containerConfig);
ScriptVars genProtocolVarsForContainer(DockerContainer container, const ContainerConfig &containerConfig);
}

View File

@@ -56,7 +56,7 @@ namespace libssh {
QEventLoop wait;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
watcher.setFuture(future);
wait.exec(QEventLoop::ExcludeUserInputEvents);
wait.exec();
int connectionResult = watcher.result();
@@ -189,7 +189,7 @@ namespace libssh {
QEventLoop wait;
QObject::connect(this, &Client::writeToChannelFinished, &wait, &QEventLoop::quit);
wait.exec(QEventLoop::ExcludeUserInputEvents);
wait.exec();
return watcher.result();
}
@@ -284,7 +284,7 @@ namespace libssh {
QEventLoop wait;
QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
wait.exec(QEventLoop::ExcludeUserInputEvents);
wait.exec();
closeScpSession();
return watcher.result();

View File

@@ -103,8 +103,8 @@ ErrorCode SshSession::runContainerScript(const ServerCredentials &credentials, D
if (e)
return e;
const bool useSh = container == DockerContainer::Socks5Proxy || container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
QString runner = QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, useSh ? "sh" : "bash");
QString runner =
QString("sudo docker exec -i $CONTAINER_NAME %2 %1 ").arg(fileName, (container == DockerContainer::Socks5Proxy ? "sh" : "bash"));
e = runScript(credentials, replaceVars(runner, amnezia::genBaseVars(credentials, container, QString(), QString())), cbReadStdOut, cbReadStdErr);
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);

View File

@@ -1,4 +1,4 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\

View File

@@ -1,9 +0,0 @@
FROM amneziavpn/mtproxy:latest
RUN mkdir -p /opt/amnezia /data
RUN printf '#!/bin/sh\ntail -f /dev/null\n' > /opt/amnezia/start.sh && \
chmod a+x /opt/amnezia/start.sh
VOLUME /data
ENTRYPOINT ["/bin/sh", "/opt/amnezia/start.sh"]
CMD [""]

View File

@@ -1,60 +0,0 @@
#!/bin/sh
# Download Telegram config files
curl -s https://core.telegram.org/getProxySecret -o /data/proxy-secret
curl -s https://core.telegram.org/getProxyConfig -o /data/proxy-multi.conf
# Determine secret: env var -> saved file -> generate new
if [ -n "$MTPROXY_SECRET" ]; then
SECRET="$MTPROXY_SECRET"
elif [ -f /data/secret ]; then
SECRET=$(cat /data/secret)
else
SECRET=$(openssl rand -hex 16)
fi
# Validate: must be exactly 32 hex chars
echo "$SECRET" | grep -qE '^[0-9a-fA-F]{32}$' || SECRET=$(openssl rand -hex 16)
# Persist secret for start.sh restarts
echo "$SECRET" > /data/secret
# Detect external IP
IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null)
[ -z "$IP" ] && IP=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null)
[ -z "$IP" ] && IP=$(curl -s --max-time 5 https://icanhazip.com 2>/dev/null)
# Use custom public host/domain if provided, otherwise fall back to detected IP
if [ -n "$MTPROXY_PUBLIC_HOST" ]; then
LINK_HOST="$MTPROXY_PUBLIC_HOST"
else
LINK_HOST="$IP"
fi
PORT=$MTPROXY_PORT
# Transport mode is substituted by replaceVars — plain variable, no curly braces
TRANSPORT_MODE=$MTPROXY_TRANSPORT_MODE
PADDED_SECRET="dd${SECRET}"
if [ "$TRANSPORT_MODE" = "faketls" ] && [ -n "$MTPROXY_TLS_DOMAIN" ]; then
DOMAIN_HEX=$(echo -n "$MTPROXY_TLS_DOMAIN" | od -A n -t x1 | tr -d ' \n')
FAKETLS_SECRET="ee${SECRET}${DOMAIN_HEX}"
else
FAKETLS_SECRET=""
fi
# Active link secret depends on transport mode
if [ "$TRANSPORT_MODE" = "faketls" ] && [ -n "$FAKETLS_SECRET" ]; then
LINK_SECRET="$FAKETLS_SECRET"
else
LINK_SECRET="$PADDED_SECRET"
fi
# Output stable markers — parsed by updateContainerConfigAfterInstallation()
echo "[*] MTProxy configuration"
echo "[*] Secret: ${SECRET}"
echo "[*] FakeTLS: ${FAKETLS_SECRET}"
echo "[*] tg:// link: tg://proxy?server=${LINK_HOST}&port=${PORT}&secret=${LINK_SECRET}"
echo "[*] t.me link: https://t.me/proxy?server=${LINK_HOST}&port=${PORT}&secret=${LINK_SECRET}"

View File

@@ -1,9 +0,0 @@
# Run container
sudo docker run -d \
--log-driver none \
--restart always \
-p $MTPROXY_PORT:$MTPROXY_PORT/tcp \
-v amnezia-mtproxy-data:/data \
--name $CONTAINER_NAME \
$CONTAINER_NAME

View File

@@ -1,71 +0,0 @@
#!/bin/sh
echo "Container startup"
# Read persisted secret
SECRET=""
if [ -f /data/secret ]; then
SECRET=$(cat /data/secret)
fi
if [ -z "$SECRET" ]; then
echo "ERROR: /data/secret not found — run configure_container first"
tail -f /dev/null
exit 1
fi
# Build tag argument
TAG_ARG=""
if [ -n "$MTPROXY_TAG" ]; then
TAG_ARG="-P $MTPROXY_TAG"
fi
# Build domain argument for FakeTLS mode
DOMAIN_ARG=""
if [ "$MTPROXY_TRANSPORT_MODE" = "faketls" ] && [ -n "$MTPROXY_TLS_DOMAIN" ]; then
DOMAIN_ARG="--domain $MTPROXY_TLS_DOMAIN"
fi
WORKERS=$MTPROXY_WORKERS
STATS_PORT=2398
LISTEN_PORT=$MTPROXY_PORT
NAT_FLAG=""
NAT_VALUE=""
if [ "$MTPROXY_NAT_ENABLED" = "1" ] && [ -n "$MTPROXY_NAT_INTERNAL_IP" ] && [ -n "$MTPROXY_NAT_EXTERNAL_IP" ]; then
NAT_FLAG="--nat-info"
NAT_VALUE="$MTPROXY_NAT_INTERNAL_IP:$MTPROXY_NAT_EXTERNAL_IP"
else
INTERNAL_IP=$(hostname -i 2>/dev/null | awk '{print $1}')
EXTERNAL_IP=$(curl -s --max-time 5 https://api.ipify.org 2>/dev/null)
[ -z "$EXTERNAL_IP" ] && EXTERNAL_IP=$(curl -s --max-time 5 https://ifconfig.me 2>/dev/null)
if [ -n "$INTERNAL_IP" ] && [ -n "$EXTERNAL_IP" ] && [ "$INTERNAL_IP" != "$EXTERNAL_IP" ]; then
NAT_FLAG="--nat-info"
NAT_VALUE="${INTERNAL_IP}:${EXTERNAL_IP}"
fi
fi
# Build additional secrets arguments
ADDITIONAL_SECRETS_ARG=""
if [ -n "$MTPROXY_ADDITIONAL_SECRETS" ]; then
for S in $(echo "$MTPROXY_ADDITIONAL_SECRETS" | tr ',' ' '); do
ADDITIONAL_SECRETS_ARG="$ADDITIONAL_SECRETS_ARG -S $S"
done
fi
# Start proxy (foreground)
exec mtproto-proxy \
-u root \
-p ${STATS_PORT} \
-H ${LISTEN_PORT} \
-S ${SECRET} \
${ADDITIONAL_SECRETS_ARG} \
--aes-pwd /data/proxy-secret \
-M ${WORKERS} \
-C 60000 \
--allow-skip-dh \
${NAT_FLAG:+${NAT_FLAG} ${NAT_VALUE}} \
${TAG_ARG} \
${DOMAIN_ARG} \
/data/proxy-multi.conf

View File

@@ -24,14 +24,6 @@
<file>ipsec/run_container.sh</file>
<file>ipsec/start.sh</file>
<file>ipsec/strongswan.profile</file>
<file>mtproxy/configure_container.sh</file>
<file>mtproxy/Dockerfile</file>
<file>mtproxy/run_container.sh</file>
<file>mtproxy/start.sh</file>
<file>telemt/configure_container.sh</file>
<file>telemt/Dockerfile</file>
<file>telemt/run_container.sh</file>
<file>telemt/start.sh</file>
<file>openvpn/configure_container.sh</file>
<file>openvpn/Dockerfile</file>
<file>openvpn/run_container.sh</file>
@@ -63,3 +55,4 @@
<file>xray/template.json</file>
</qresource>
</RCC>

View File

@@ -1,42 +0,0 @@
# syntax=docker/dockerfile:1
# Debian-based image with Telemt binary (shell + jq for Amnezia configure scripts).
# Binary from https://github.com/telemt/telemt releases (same pattern as upstream Dockerfile minimal stage).
FROM debian:12-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
binutils \
ca-certificates \
curl \
jq \
openssl \
tar \
&& rm -rf /var/lib/apt/lists/*
# Use machine arch (works with classic `docker build`; TARGETARCH is only set with BuildKit).
RUN set -eux; \
ARCH="$(uname -m)"; \
case "$ARCH" in \
x86_64) ASSET="telemt-x86_64-linux-musl.tar.gz" ;; \
aarch64|arm64) ASSET="telemt-aarch64-linux-musl.tar.gz" ;; \
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; \
esac; \
curl -fL --retry 5 --retry-delay 3 --connect-timeout 10 --max-time 120 \
-o "/tmp/${ASSET}" "https://github.com/telemt/telemt/releases/latest/download/${ASSET}"; \
curl -fL --retry 5 --retry-delay 3 --connect-timeout 10 --max-time 120 \
-o "/tmp/${ASSET}.sha256" "https://github.com/telemt/telemt/releases/latest/download/${ASSET}.sha256"; \
cd /tmp && sha256sum -c "${ASSET}.sha256"; \
tar -xzf "${ASSET}" -C /tmp; \
test -f /tmp/telemt; \
install -m 0755 /tmp/telemt /usr/local/bin/telemt; \
strip --strip-unneeded /usr/local/bin/telemt || true; \
rm -f "/tmp/${ASSET}" "/tmp/${ASSET}.sha256" /tmp/telemt
RUN mkdir -p /opt/amnezia /data
RUN printf '#!/bin/sh\ntail -f /dev/null\n' > /opt/amnezia/start.sh && \
chmod a+x /opt/amnezia/start.sh
VOLUME /data
ENTRYPOINT ["/bin/sh", "/opt/amnezia/start.sh"]
CMD [""]

View File

@@ -1,73 +0,0 @@
#!/bin/sh
# Do not use set -e: Telemt / curl / kill edge cases should not abort the whole configure step.
echo "[*] Amnezia Telemt: configure script start"
mkdir -p /data/tlsfront
# Secret: substituted $TELEMT_SECRET -> saved file -> openssl (same rules as MTProxy configure)
if [ -n "$TELEMT_SECRET" ]; then
SECRET="$TELEMT_SECRET"
elif [ -f /data/secret ]; then
SECRET=$(cat /data/secret)
else
SECRET=$(openssl rand -hex 16)
fi
# Must be exactly 32 hex chars
echo "$SECRET" | grep -qE '^[0-9a-fA-F]{32}$' || SECRET=$(openssl rand -hex 16)
# Build config.toml (other variables substituted on the host by Amnezia before upload)
rm -f /data/config.toml
{
echo "### Amnezia Telemt — generated"
echo "[general]"
echo "use_middle_proxy = $TELEMT_USE_MIDDLE_PROXY"
echo "log_level = \"normal\""
if [ -n "$TELEMT_TAG" ]; then
echo "ad_tag = \"$TELEMT_TAG\""
fi
echo ""
echo "[general.modes]"
echo "classic = false"
echo "secure = $TELEMT_TOML_SECURE"
echo "tls = $TELEMT_TOML_TLS"
echo ""
echo "[general.links]"
echo "show = \"*\""
if [ -n "$TELEMT_PUBLIC_HOST" ]; then
echo "public_host = \"$TELEMT_PUBLIC_HOST\""
fi
echo "public_port = $TELEMT_PORT"
echo ""
echo "[server]"
echo "port = $TELEMT_PORT"
echo ""
echo "[server.api]"
echo "enabled = true"
echo "listen = \"0.0.0.0:9091\""
# Match upstream Telemt default: localhost API only (curl in this script uses 127.0.0.1).
echo "whitelist = [\"127.0.0.0/8\"]"
echo ""
echo "[[server.listeners]]"
echo "ip = \"0.0.0.0\""
echo ""
echo "[censorship]"
echo "tls_domain = \"$TELEMT_TLS_DOMAIN\""
echo "mask = $TELEMT_MASK"
echo "tls_emulation = $TELEMT_TLS_EMULATION"
echo "tls_front_dir = \"/data/tlsfront\""
echo ""
echo "[access.users]"
echo "$TELEMT_USER_NAME = \"$SECRET\""
} > /data/config.toml
echo "$SECRET" > /data/secret
chmod 600 /data/secret 2>/dev/null || true
# Do not start telemt here: a long-lived process + curl loop inside `docker exec` can confuse SSH/Docker
# timing and is unnecessary — start.sh runs telemt after configure. Links can be empty until the service
# is up; the client still parses Secret below.
echo "[*] Telemt configuration"
echo "[*] Secret: $SECRET"
echo "[*] tg:// link: "
echo "[*] t.me link: "

View File

@@ -1,9 +0,0 @@
# Run container (ulimit per Telemt docs — avoids "Too many open files" under load)
sudo docker run -d \
--log-driver none \
--restart always \
--ulimit nofile=65536:65536 \
-p $TELEMT_PORT:$TELEMT_PORT/tcp \
-v amnezia-telemt-data:/data \
--name $CONTAINER_NAME \
$CONTAINER_NAME

View File

@@ -1,12 +0,0 @@
#!/bin/sh
echo "Container startup (Telemt)"
if [ ! -f /data/config.toml ]; then
echo "ERROR: /data/config.toml not found — run configure_container first"
tail -f /dev/null
exit 1
fi
mkdir -p /data/tlsfront
exec /usr/local/bin/telemt /data/config.toml

View File

@@ -4,8 +4,11 @@ project(AmneziaVPN_Tests)
find_package(Qt6 REQUIRED COMPONENTS Test)
include(CTest)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
qt6_add_resources(TEST_QRC
${CLIENT_ROOT_DIR}/server_scripts/serverScripts.qrc
@@ -77,6 +80,15 @@ target_link_libraries(test_server_edge_cases PRIVATE
test_common
)
add_executable(test_gateway_stacks
testGatewayStacks.cpp
)
target_link_libraries(test_gateway_stacks PRIVATE
Qt6::Test
test_common
)
add_executable(test_signal_order
testSignalOrder.cpp
)
@@ -131,15 +143,85 @@ target_link_libraries(test_self_hosted_server_setup PRIVATE
test_common
)
add_executable(test_exports
testMultipleExports.cpp
)
target_link_libraries(test_exports PRIVATE
Qt6::Test
test_common
)
add_executable(test_serialization
testSerialization.cpp
)
target_link_libraries(test_serialization PRIVATE
Qt6::Test
test_common
)
add_executable(test_ui_language_model_and_controller
testUiLanguageModelAndController.cpp
)
target_link_libraries(test_ui_language_model_and_controller PRIVATE
Qt6::Test
test_common
)
add_executable(test_ui_ip_model_and_controller
testUiIpModelAndController.cpp
)
target_link_libraries(test_ui_ip_model_and_controller PRIVATE
Qt6::Test
test_common
)
add_executable(test_ui_app_st_model_and_controller
testUiAppSTModelAndController.cpp
)
target_link_libraries(test_ui_app_st_model_and_controller PRIVATE
Qt6::Test
test_common
)
add_executable(test_ui_allowed_dns_model_and_controller
testUiAllowedDnsModelAndController.cpp
)
target_link_libraries(test_ui_allowed_dns_model_and_controller PRIVATE
Qt6::Test
test_common
)
add_executable(test_ui_api_services_model_and_controller
api/testUiApiServicesModelAndController.cpp
)
target_link_libraries(test_ui_api_services_model_and_controller PRIVATE
Qt6::Test
test_common
)
enable_testing()
add_test(NAME ImportExportTest COMMAND test_import_export)
add_test(NAME MultipleImportsTest COMMAND test_multiple_imports)
add_test(NAME ServerEditTest COMMAND test_server_edit)
add_test(NAME DefaultServerChangeTest COMMAND test_default_server_change)
add_test(NAME ServerEdgeCasesTest COMMAND test_server_edge_cases)
add_test(NAME GatewayStacksTest COMMAND test_gateway_stacks)
add_test(NAME SignalOrderTest COMMAND test_signal_order)
add_test(NAME ServersModelSyncTest COMMAND test_servers_model_sync)
add_test(NAME ComplexOperationsTest COMMAND test_complex_operations)
add_test(NAME SettingsSignalsTest COMMAND test_settings_signals)
add_test(NAME UiServersModelAndControllerTest COMMAND test_ui_servers_model_and_controller)
add_test(NAME SelfHostedServerSetupTest COMMAND test_self_hosted_server_setup)
add_test(NAME MultipleExportsTest COMMAND test_exports)
add_test(NAME SerializationTest COMMAND test_serialization)
add_test(NAME UiLanguageModelAndControllerTest COMMAND test_ui_language_model_and_controller)
add_test(NAME UiIpModelAndControllerTest COMMAND test_ui_ip_model_and_controller)
add_test(NAME UiAppSTModelAndControllerTest COMMAND test_ui_app_st_model_and_controller)
add_test(NAME UiAllowedDnsModelAndControllerTest COMMAND test_ui_allowed_dns_model_and_controller)

View File

@@ -0,0 +1,97 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcessEnvironment>
#include <QSignalSpy>
#include <QTest>
#include <QUuid>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestUiApiServicesModelAndController : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
// TODO: add env vars for api
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase()
{
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testRolesAndSignals()
{
QSignalSpy errorOccurredSpy(m_coreController->m_servicesCatalogUiController, &ServicesCatalogUiController::errorOccurred);
/* TODO:
m_coreController->m_servicesCatalogUiController->fillAvailableServices();
QVERIFY(errorOccurredSpy.count() == 0, "errorOccurred signal should not be emitted");
QModelIndex serviceModelIndex = m_coreController->m_apiServicesModel->index(0, 0);
QVERIFY2(serviceModelIndex.isValid(), "Service model index should be valid");
auto serviceName = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::NameRole);
QCOMPARE(serviceName, );
auto serviceCardDescription = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::CardDescriptionRole);
QCOMPARE(serviceCardDescription, );
auto isServiceAvailable = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::IsServiceAvailableRole);
QCOMPARE(isServiceAvailable, );
auto serviceSpeed = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::SpeedRole);
QCOMPARE(serviceSpeed, );
auto serviceTimeLimit = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::TimeLimitRole);
QCOMPARE(serviceTimeLimit, );
auto serviceRegion = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::RegionRole);
QCOMPARE(serviceRegion, );
auto serviceFeatures = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::FeaturesRole);
QCOMPARE(serviceFeatures, );
auto servicePrice = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::PriceRole);
QCOMPARE(servicePrice, );
auto serviceEndDate = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::EndDateRole);
QCOMPARE(serviceEndDate, );
auto serviceOrder = m_coreController->m_apiServicesModel->data(serviceModelIndex, ApiServicesModel::OrderRole);
QCOMPARE(serviceOrder, );
*/
}
};
QTEST_MAIN(TestUiApiServicesModelAndController)
#include "testUiApiServicesModelAndController.moc"

View File

@@ -0,0 +1,97 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcessEnvironment>
#include <QSignalSpy>
#include <QTest>
#include <QUuid>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestUiNewsModelAndController : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
// TODO: add env vars for api
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase()
{
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testRolesAndSignals()
{
QSignalSpy fetchNewsFinishedSpy(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished);
QSignalSpy errorOccurredSpy(m_coreController->m_apiNewsUiController, &ApiNewsUiController::errorOccurred);
QSignalSpy processedIndexChangedSpy(m_coreController->m_newsModel, &NewsModel::processedIndexChanged);
QSignalSpy hasUnreadChangedSpy(m_coreController->m_newsModel, &NewsModel::hasUnreadChanged);
/* TODO:
m_coreController->m_apiNewsUiController->fetchNews(false);
QVERIFY(errorOccurredSpy.count() == 0, "errorOccurred signal should not be emitted");
QVERIFY(fetchNewsFinishedSpy.count() == 1, "fetchNewsFinished signal should be emitted");
m_coreController->m_newsModel->updateModel();
QVERIFY(hasUnreadChangedSpy.count() == 1, "hasUnreadChanged signal should be emitted");
QModelIndex newsModelIndex = m_coreController->m_newsModel->index(0, 0);
QVERIFY2(newsModelIndex.isValid(), "News model index should be valid");
auto newsId = m_coreController->m_newsModel->data(newsModelIndex, NewsModel::IdRole);
QCOMPARE(newsId, );
auto newsTitle = m_coreController->m_newsModel->data(newsModelIndex, NewsModel::TitleRole);
QCOMPARE(newsTitle, );
auto newsContent = m_coreController->m_newsModel->data(newsModelIndex, NewsModel::ContentRole);
QCOMPARE(newsContent, );
auto newsTimestamp = m_coreController->m_newsModel->data(newsModelIndex, NewsModel::TimestampRole);
QCOMPARE(newsTimestamp, );
auto newsIsRead = m_coreController->m_newsModel->data(newsModelIndex, NewsModel::IsReadRole);
QCOMPARE(newsIsRead, false);
auto newsIsProcessed = m_coreController->m_newsModel->data(newsModelIndex, NewsModel::IsProcessedRole);
QCOMPARE(newsIsProcessed, );
m_coreController->m_newsModel->markAsRead(0);
? m_coreController->m_newsModel->updateModel(); ?
QVERIFY(hasUnreadChangedSpy.count() == 2, "hasUnreadChanged signal should be emitted");
QCOMPARE(newsIsRead, true);
*/
}
};
QTEST_MAIN(TestUiNewsModelAndController)
#include "testUiNewsModelAndController.moc"

View File

@@ -1,4 +1,3 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
@@ -6,6 +5,7 @@
#include <QDebug>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/utils/constants/configKeys.h"
@@ -20,6 +20,12 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
QJsonObject decodeVpnKey(const QString &vpnKey) {
QString key = vpnKey;
key.replace("vpn://", "");
@@ -97,7 +103,7 @@ private slots:
}
void testAdminSelfHostedExport() {
QString vpnKey = "vpn://AAABTXjarZIxT8MwEIX_Cro5jbDjQunKUhhYyoZQZZKjRGpsy3baQtT_zp2bJh3oACLLPfvz3bOe00FpTdS1QR9g_tKB3q1h3sFCwBzEdf9N5ElBBgtJqBiQOkcFoemAbs6RInQ7oNkZemAvrrKvRV9VX6fH-lhSVSwavU9GSdcmXZX0UqSbseJRMqlioDxuSsJZH1mKWTrhvI22tJvVljKoLU-TtB3aN4NxpavKYwhpSD7LRc4t0WsTeMwqNRNsKweHbAyTtnRj8KvWE0pUEut-hNah2TpDM0-Kwu8vKMSd-ttFLrntao_rVvuKWkc9OnIk4n8t915_Ulcqo5FSxa9tYsk2rxlU-K7bTby_lDWfCKWvXTy-5jOGeLVET-9L7MOG-KQbJEBx57jXjdtgXtqG_wUdws5yJhCpa1iefhopM2gD-n4An-ElHL4BvzD6nw";
QString vpnKey = getValueFromIni("configs/TEST_CONFIG_ANY");
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);

View File

@@ -1,8 +1,8 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
@@ -20,6 +20,12 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
@@ -43,9 +49,9 @@ private slots:
}
void testComplexOperationSequence() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QString xrayKey = getValueFromIni("configs/TEST_CONFIG_WG");
QString wgKey = getValueFromIni("configs/TEST_CONFIG_XRAY");
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);

View File

@@ -1,8 +1,8 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
@@ -21,6 +21,12 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
@@ -45,9 +51,9 @@ private slots:
}
void testSetDefaultServerIndex() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QString xrayKey = getValueFromIni("configs/TEST_CONFIG_WG");
QString wgKey = getValueFromIni("configs/TEST_CONFIG_XRAY");
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);
@@ -80,9 +86,9 @@ private slots:
}
void testDefaultServerChangeOnRemoveEdgeCases() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QString xrayKey = getValueFromIni("configs/TEST_CONFIG_WG");
QString wgKey = getValueFromIni("configs/TEST_CONFIG_XRAY");
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);

View File

@@ -0,0 +1,87 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "ui/controllers/serversUiController.h"
#include "ui/models/serversModel.h"
#include "vpnConnection.h"
#include "secureQSettings.h"
using namespace amnezia;
class TestGatewayStacks : public QObject
{
Q_OBJECT
private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase() {
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init() {
m_settings->clearSettings();
m_coreController->m_serversRepository->invalidateCache();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testGatewayStacksRecomputeOnServerOperations() {
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QSignalSpy serverAddedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverAdded);
QSignalSpy serverEditedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverEdited);
QSignalSpy serverRemovedSpy(m_coreController->m_serversRepository, &SecureServersRepository::serverRemoved);
QSignalSpy gatewayServersChangedSpy(m_coreController->m_serversUiController,
&ServersUiController::hasServersFromGatewayApiChanged);
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult.config);
QVERIFY2(serverAddedSpy.count() == 1, "serverAdded signal should be emitted");
QVERIFY2(!m_coreController->m_serversUiController->hasServersFromGatewayApi(),
"Self-hosted imports should not be treated as gateway API servers");
const QString serverId = m_coreController->m_serversController->getServerId(0);
QVERIFY2(m_coreController->m_serversController->renameServer(serverId, QStringLiteral("Edited Server")),
"Server rename should succeed");
QVERIFY2(serverEditedSpy.count() == 1, "serverEdited signal should be emitted");
m_coreController->m_serversController->removeServer(serverId);
QVERIFY2(serverRemovedSpy.count() == 1, "serverRemoved signal should be emitted");
QVERIFY2(!m_coreController->m_serversUiController->hasServersFromGatewayApi(),
"Gateway API server state should remain empty");
QVERIFY2(gatewayServersChangedSpy.count() == 0,
"Self-hosted server operations should not emit gateway API state changes");
}
};
QTEST_MAIN(TestGatewayStacks)
#include "testGatewayStacks.moc"

View File

@@ -0,0 +1,133 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSignalSpy>
#include <QUuid>
#include <QProcessEnvironment>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestMultipleExports : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
QString vpnKey = getValueFromIni("configs/TEST_SELF_HOSTED_CONFIG");
QJsonObject importedConfig = m_coreController->m_importCoreController->extractConfigFromData(vpnKey).config;
m_coreController->m_importCoreController->importConfig(importedConfig);
qDebug() << "SELF-HOSTED ADMIN SERVER IMPORTED\n";
}
void cleanupTestCase()
{
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
const QString serverId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
for (int containerIndex = 1; containerIndex < 7; ++containerIndex)
m_coreController->m_installUiController->clearCachedProfile(serverId, containerIndex);
m_coreController->m_serversController->removeServer(serverId);
qDebug() << "SERVER REMOVED\n";
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testMultipleExports()
{
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
const QString serverId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
QString clientName = "MultipleExports Test Client";
for (int containerIndex = 1; containerIndex < 7; ++containerIndex) {
QString containerName;
switch (containerIndex) {
case 1: containerName = "AwgLegacy"; break;
case 2: containerName = "Awg2"; break;
case 3: containerName = "WireGuard"; break;
case 4: containerName = "OpenVPN"; break;
case 5: continue; break; // skipping IPsec
case 6: containerName = "XRay"; break;
}
if (!m_coreController->m_containersModel->data(containerIndex, ContainersModel::Roles::IsInstalledRole).toBool()) {
qDebug() << QStringLiteral("%1: Not installed").arg(containerName).toUtf8().constData();
continue;
}
auto exportResult = m_coreController->m_exportController->generateConnectionConfig(serverId, containerIndex, clientName);
QVERIFY2(exportResult.errorCode == ErrorCode::NoError,
QStringLiteral("\n%1: Export should succeed").arg(containerName).toUtf8().constData());
QVERIFY2(!exportResult.config.isEmpty(),
QStringLiteral("%1: Exported config should not be empty\n").arg(containerName).toUtf8().constData());
}
}
void testMultipleExportsNative()
{
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
const QString serverId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
QString clientName = "MultipleExports Test Client";
auto exportResultAwg = m_coreController->m_exportController->generateAwgConfig(serverId, DockerContainer::Awg2, clientName);
auto exportResultWg = m_coreController->m_exportController->generateWireGuardConfig(serverId, clientName);
auto exportResultOvpn = m_coreController->m_exportController->generateOpenVpnConfig(serverId, clientName);
auto exportResultXray = m_coreController->m_exportController->generateXrayConfig(serverId, clientName);
QVERIFY2(exportResultAwg.errorCode == ErrorCode::NoError, "\nAwg (native): Export should succeed");
QVERIFY2(exportResultWg.errorCode == ErrorCode::NoError, "\nWg (native): Export should succeed");
QVERIFY2(exportResultOvpn.errorCode == ErrorCode::NoError, "\nOvpn (native): Export should succeed");
QVERIFY2(exportResultXray.errorCode == ErrorCode::NoError, "\nXray (native): Export should succeed");
QVERIFY2(!exportResultAwg.config.isEmpty(), "Awg (native): Exported config should not be empty\n");
QVERIFY2(!exportResultWg.config.isEmpty(), "Wg (native): Exported config should not be empty\n");
QVERIFY2(!exportResultOvpn.config.isEmpty(), "Ovpn (native): Exported config should not be empty\n");
QVERIFY2(!exportResultXray.config.isEmpty(), "Xray (native): Exported config should not be empty\n");
}
};
QTEST_MAIN(TestMultipleExports)
#include "testMultipleExports.moc"

View File

@@ -1,9 +1,9 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
@@ -21,6 +21,12 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
@@ -46,9 +52,9 @@ private slots:
}
void testMultipleImports() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QString xrayKey = getValueFromIni("configs/TEST_CONFIG_WG");
QString wgKey = getValueFromIni("configs/TEST_CONFIG_XRAY");
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);
@@ -126,8 +132,8 @@ private slots:
}
void testMultipleImportsRemoval() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QString xrayKey = getValueFromIni("configs/TEST_CONFIG_XRAY");
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
QSignalSpy defaultServerChangedSpy(m_coreController->m_serversRepository, &SecureServersRepository::defaultServerChanged);

View File

@@ -1,10 +1,10 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QProcessEnvironment>
#include <QDebug>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
@@ -31,13 +31,19 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
ServerCredentials getCredentialsFromEnv() {
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString hostName = env.value("TEST_SERVER_HOST");
QString userName = env.value("TEST_SERVER_USER");
QString password = env.value("TEST_SERVER_PASSWORD");
QString portStr = env.value("TEST_SERVER_PORT", "22");
QString hostName = getValueFromIni("secrets/TEST_SERVER_HOST");
QString userName = getValueFromIni("secrets/TEST_SERVER_USER");
QString password = getValueFromIni("secrets/TEST_SERVER_PASSWORD");
QString portStr = getValueFromIni("secrets/TEST_SERVER_PORT");
int port = portStr.toInt();
ServerCredentials credentials;

View File

@@ -0,0 +1,287 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcessEnvironment>
#include <QSignalSpy>
#include <QUuid>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "core/utils/serialization/serialization.h"
#include "core/utils/utilities.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestSerialization : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
QJsonObject extractXrayConfig(const QString &data, ConfigTypes configType, const QString &description = "") const
{
QJsonParseError parserErr;
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
QJsonObject xrayVpnConfig;
xrayVpnConfig[configKey::config] = jsonConf.toJson().constData();
QJsonObject lastConfig;
lastConfig[configKey::lastConfig] = jsonConf.toJson().constData();
lastConfig[configKey::isThirdPartyConfig] = true;
QJsonObject containers;
if (configType == ConfigTypes::ShadowSocks) {
containers.insert(configKey::ssxray, QJsonValue(lastConfig));
containers.insert(configKey::container, QJsonValue(configKey::amneziaSsxray));
} else {
containers.insert(configKey::container, QJsonValue(configKey::amneziaXray));
containers.insert(configKey::xray, QJsonValue(lastConfig));
}
QJsonArray arr;
arr.push_back(containers);
QString hostName;
const static QRegularExpression hostNameRegExp("\"address\":\\s*\"([^\"]+)");
QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data);
if (hostNameMatch.hasMatch()) {
hostName = hostNameMatch.captured(1);
}
QJsonObject config;
config[configKey::containers] = arr;
config[configKey::defaultContainer] =
(configType == ConfigTypes::ShadowSocks) ? configKey::amneziaSsxray : configKey::amneziaXray;
if (description.isEmpty()) {
config[configKey::description] = m_coreController->m_appSettingsRepository->nextAvailableServerName();
} else {
config[configKey::description] = description;
}
config[configKey::hostName] = hostName;
return config;
}
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
QString vpnKey = getValueFromIni("configs/TEST_SELF_HOSTED_CONFIG");
QJsonObject importedConfig = m_coreController->m_importCoreController->extractConfigFromData(vpnKey).config;
m_coreController->m_importCoreController->importConfig(importedConfig);
qDebug() << "SELF-HOSTED ADMIN SERVER IMPORTED\n";
}
void cleanupTestCase()
{
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
const QString serverId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
for (int containerIndex = 1; containerIndex < 7; ++containerIndex)
m_coreController->m_installUiController->clearCachedProfile(serverId, containerIndex);
m_coreController->m_serversController->removeServer(serverId);
qDebug() << "SERVER REMOVED\n";
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testVless()
{
int serverIndex = m_coreController->m_serversRepository->defaultServerIndex();
const QString serverId = m_coreController->m_serversRepository->serverIdAt(serverIndex);
QString clientName = "Test Client (vless (de)serialization)";
ExportController::ExportResult exportResult = m_coreController->m_exportController->generateXrayConfig(serverId, clientName);
ImportController::ImportResult importResult;
QString config = exportResult.config;
QString prefix;
QString errormsg;
ConfigTypes configType = ConfigTypes::Invalid;
if (config.startsWith("vless://")) {
configType = ConfigTypes::Xray;
importResult.config = extractXrayConfig(
Utils::JsonToString(serialization::vless::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
QVERIFY2(!importResult.config.empty(), "Config shouldn't be empty");
} else {
QSKIP("Config not starts with vless://");
}
QCOMPARE(importResult.config, config);
}
void testVmessNew()
{
QString clientName = "Test Client (vmess_new deserialization)";
ImportController::ImportResult importResult;
m_coreController->m_importController->extractConfigFromData(getValueFromIni("configs/TEST_CONFIG_VMESS_NEW"));
QString config = m_coreController->m_importController->getConfig();
QString prefix;
QString errormsg;
ConfigTypes configType = ConfigTypes::Invalid;
if (config.startsWith("vmess://") && config.contains("@")) {
configType = ConfigTypes::Xray;
importResult.config = extractXrayConfig(
Utils::JsonToString(serialization::vmess_new::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
QVERIFY2(!importResult.config.empty(), "Config shouldn't be empty");
} else {
QSKIP("Config not starts with vmess:// or not contain @");
}
QCOMPARE(importResult.config, config);
}
void testVmess()
{
QString clientName = "Test Client (vmess deserialization)";
ImportController::ImportResult importResult;
m_coreController->m_importController->extractConfigFromData(getValueFromIni("configs/TEST_CONFIG_VMESS"));
QString config = m_coreController->m_importController->getConfig();
QString prefix;
QString errormsg;
ConfigTypes configType = ConfigTypes::Invalid;
if (config.startsWith("vmess://")) {
configType = ConfigTypes::Xray;
importResult.config = extractXrayConfig(
Utils::JsonToString(serialization::vmess::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
QVERIFY2(!importResult.config.empty(), "Config shouldn't be empty");
} else {
QSKIP("Config not starts with vmess://");
}
QCOMPARE(importResult.config, config);
}
void testTrojan()
{
QString clientName = "Test Client (trojan deserialization)";
ImportController::ImportResult importResult;
m_coreController->m_importController->extractConfigFromData(getValueFromIni("configs/TEST_CONFIG_TROJAN"));
QString config = m_coreController->m_importController->getConfig();
QString prefix;
QString errormsg;
ConfigTypes configType = ConfigTypes::Invalid;
if (config.startsWith("trojan://")) {
configType = ConfigTypes::Xray;
importResult.config = extractXrayConfig(
Utils::JsonToString(serialization::trojan::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
QVERIFY2(!importResult.config.empty(), "Config shouldn't be empty");
} else {
QSKIP("Config not starts with trojan://");
}
QCOMPARE(importResult.config, config);
}
void testSS()
{
QString clientName = "Test Client (ss deserialization)";
ImportController::ImportResult importResult;
m_coreController->m_importController->extractConfigFromData(getValueFromIni("configs/TEST_CONFIG_SS"));
QString config = m_coreController->m_importController->getConfig();
QString prefix;
QString errormsg;
ConfigTypes configType = ConfigTypes::Invalid;
if (config.startsWith("ss://") && !config.contains("plugin=")) {
configType = ConfigTypes::ShadowSocks;
importResult.config = extractXrayConfig(
Utils::JsonToString(serialization::ss::Deserialize(config, &prefix, &errormsg), QJsonDocument::JsonFormat::Compact),
configType, prefix);
QVERIFY2(!importResult.config.empty(), "Config shouldn't be empty");
} else {
QSKIP("Config not starts with ss:// or contain plugin=");
}
QCOMPARE(importResult.config, config);
}
void testSSd()
{
QString clientName = "Test Client (ssd deserialization)";
ImportController::ImportResult importResult;
m_coreController->m_importController->extractConfigFromData(getValueFromIni("configs/TEST_CONFIG_SSD"));
QString config = m_coreController->m_importController->getConfig();
QString prefix;
QString errormsg;
ConfigTypes configType = ConfigTypes::Invalid;
if (config.startsWith("ssd://")) {
QStringList tmp;
QList<std::pair<QString, QJsonObject>> servers = serialization::ssd::Deserialize(config, &prefix, &tmp);
configType = ConfigTypes::ShadowSocks;
// Took only first config from list
if (!servers.isEmpty()) {
importResult.config = extractXrayConfig(servers.first().first, configType);
}
if (!importResult.config.empty()) {
importResult.configType = configType;
}
QVERIFY2(!importResult.config.empty(), "Config shouldn't be empty");
} else {
QSKIP("Config not starts with ssd://");
}
QCOMPARE(importResult.config, config);
}
};
QTEST_MAIN(TestSerialization)
#include "testSerialization.moc"

View File

@@ -1,7 +1,9 @@
#include <QJsonDocument>
#include <QTest>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/repositories/secureServersRepository.h"
@@ -21,6 +23,12 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
@@ -45,7 +53,7 @@ private slots:
}
void testInvalidIndexOperations() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult.config);

View File

@@ -1,8 +1,8 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
@@ -21,6 +21,12 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
@@ -45,7 +51,7 @@ private slots:
}
void testServerEditTriggersHandlers() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QSignalSpy importFinishedSpy(m_coreController->m_importCoreController, &ImportController::importFinished);
auto importResult = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
@@ -73,8 +79,8 @@ private slots:
}
void testServerEditPreservesDefault() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QString xrayKey = getValueFromIni("configs/TEST_CONFIG_XRAY");
auto importResult1 = m_coreController->m_importCoreController->extractConfigFromData(awgKey);
m_coreController->m_importCoreController->importConfig(importResult1.config);

View File

@@ -1,8 +1,8 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
@@ -21,6 +21,12 @@ private:
CoreController* m_coreController;
SecureQSettings* m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase() {
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
@@ -44,7 +50,7 @@ private slots:
}
void testServersModelSyncOnOperations() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
if (!m_coreController->m_serversModel) {
QSKIP("ServersModel not available");
@@ -71,9 +77,9 @@ private slots:
}
void testServersModelDefaultIndexSync() {
QString awgKey = "vpn://AAABFHjadZBBT4QwEIX_ipkzS2wBJdyMB1cPXvbgwRgyQnclgZa0RTYS_rszXRa52Mt77TfzOu0EldEeG62sg-J9AhxPUEywF1CAuF3WTl4dRLCXhJIVpVuUEMpWdLdFKaH7FeUb9Mx3scpFk0XTRbOLvlSkKZsOz-Gi4BsdRiV_EGEydhwlg0tWynEZmd5Yz1bkoaK3xpvKtOU3_UFjOE3SsRs-tfIl1rVVzoWQOI9FzC3eonYcU4ZmgkPdwxz9fSYdYafVT4M7-lEJ80cEtTri0PrH_2q4wlW26f1lioe3p5uDsjQWoS_j_Ct2ipvGU6zO2PWtiivT8RPQudHYmqBXzl-3Yn2slBEMTtklgYt4C_Mv3ROMwA";
QString xrayKey = "vpn://AAAAtXjadY7NCsJADIRfRXKui1YP0qt3L14EkRK7EQt2d0lS_0rf3awonjyFmW-YyQBNDIptIBao9sNPQgXYBXq2OL0zPqCA96kGSJHV6HK5MFP6YyCt0XsmsQqYz9zKzd3MmDIGyek6cdRoUJsE43gowNMJ-4uu_695kobbpG0MBndmTrbEV4sWcI6iG-zIQE47umOXLuSa2BlNKHKL7PMeiX5lmdH79bIsoBfiT0UOZQnjCw_AXRQ";
QString wgKey = "vpn://AAAAwXjahY89a8NADIb_StDsHLFDIHjt0C1LhgwlBNWnpgfx3SHp6hDj_15dacnYTS_Po68ZhhQVQyQW6N_mZ4QecIz0CLieAtO1IHto4Fn3M-TEat6u3XetMSnvkfSC3jOJjYN24_audRtjyhil-pfMSZPB4jMsy7kBTx9Ybvryz2ZPMnDIGlI042TktZLVkfjLmhr4TKIHHMnodHV0xzHfyA1pNJZRZEr1alAS_Yvbin6e6LoGihD_DqhSjbB8AyB_ZI8";
QString awgKey = getValueFromIni("configs/TEST_CONFIG_AWG");
QString xrayKey = getValueFromIni("configs/TEST_CONFIG_WG");
QString wgKey = getValueFromIni("configs/TEST_CONFIG_XRAY");
if (!m_coreController->m_serversModel) {
QSKIP("ServersModel not available");

View File

@@ -1,9 +1,9 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QLocale>
#include <QTest>
#include "core/controllers/coreController.h"
#include "ui/controllers/settingsUiController.h"

View File

@@ -1,8 +1,8 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUuid>
#include <QSignalSpy>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"

View File

@@ -0,0 +1,95 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcessEnvironment>
#include <QSignalSpy>
#include <QUuid>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestUiAllowedDnsModelAndController : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase()
{
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testRolesAndSignals()
{
QSignalSpy finishedSpy(m_coreController->m_allowedDnsUiController, &AllowedDnsUiController::finished);
QSignalSpy errorOccurredSpy(m_coreController->m_allowedDnsUiController, &AllowedDnsUiController::errorOccurred);
QString ip = "188.40.167.81";
m_coreController->m_allowedDnsUiController->addDns(ip);
m_coreController->m_allowedDnsUiController->updateModel();
QVERIFY2(errorOccurredSpy.count() == 0, "errorOccurred signal should not be emitted");
QVERIFY2(finishedSpy.count() == 1, "finished signal should be emitted");
QVERIFY2(m_coreController->m_allowedDnsModel->rowCount() == 1, "AllowedDnsModel should have 1 row");
QModelIndex allowedDnsModelIndex = m_coreController->m_allowedDnsModel->index(0, 0);
QVERIFY2(allowedDnsModelIndex.isValid(), "Site model index should be valid");
auto dnsIp = m_coreController->m_allowedDnsModel->data(allowedDnsModelIndex, AllowedDnsModel::IpRole);
QString msg = QString("dns ip should be %1, got %2").arg(ip, dnsIp.toString());
QVERIFY2(dnsIp == ip, msg.toLocal8Bit().constData());
m_coreController->m_allowedDnsUiController->importDns(getValueFromIni("paths/TEST_DNS_LIST_PATH"), true);
m_coreController->m_allowedDnsUiController->updateModel();
QVERIFY2(errorOccurredSpy.count() == 0, "errorOccurred signal should not be emitted");
QVERIFY2(finishedSpy.count() == 2, "finished signal should be emitted");
QVERIFY2(m_coreController->m_allowedDnsModel->rowCount() > 1, "AllowedDnsModel should have more than 1 row");
m_coreController->m_allowedDnsUiController->exportDns(getValueFromIni("paths/TEST_EXPORT_PATH") + "test_dns_export.json");
QVERIFY2(errorOccurredSpy.count() == 0, "errorOccurred signal should not be emitted");
QVERIFY2(finishedSpy.count() == 3, "finished signal should be emitted");
m_coreController->m_allowedDnsUiController->removeDns(0);
m_coreController->m_allowedDnsUiController->updateModel();
QVERIFY2(errorOccurredSpy.count() == 0, "errorOccurred signal should not be emitted");
QVERIFY2(finishedSpy.count() == 4, "finished signal should be emitted");
QVERIFY2(m_coreController->m_allowedDnsModel->rowCount() == 0, "AllowedDnsModel should have 0 rows");
}
};
QTEST_MAIN(TestUiAllowedDnsModelAndController)
#include "testUiAllowedDnsModelAndController.moc"

View File

@@ -0,0 +1,103 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcessEnvironment>
#include <QSignalSpy>
#include <QUuid>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestUiAppSTModelAndController : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase()
{
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testRolesAndSignals()
{
QSignalSpy finishedSpy(m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::finished);
QSignalSpy errorOccurredSpy(m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::errorOccurred);
QSignalSpy isSplitTunnelingChangedSpy(m_coreController->m_appSplitTunnelingUiController, &AppSplitTunnelingUiController::isSplitTunnelingEnabledChanged);
m_coreController->m_appSplitTunnelingUiController->toggleSplitTunneling(true);
QVERIFY2(isSplitTunnelingChangedSpy.count() == 1, "isSplitTunnelingChangedSpy signal should be emitted");
QVERIFY2(m_coreController->m_appSplitTunnelingUiController->isSplitTunnelingEnabled() == true, "AppSplitTunneling should be enabled");
m_coreController->m_appSplitTunnelingUiController->toggleSplitTunneling(false);
QVERIFY2(isSplitTunnelingChangedSpy.count() == 2, "isSplitTunnelingChangedSpy signal should be emitted 2nd time");
QVERIFY2(m_coreController->m_appSplitTunnelingUiController->isSplitTunnelingEnabled() == false, "AppSplitTunneling should be disabled");
QString app = getValueFromIni("paths/TEST_APP_PATH");
m_coreController->m_appSplitTunnelingUiController->addApp(app);
m_coreController->m_appSplitTunnelingUiController->updateModel();
QVERIFY2(finishedSpy.count() == 1, "finished signal should be emitted");
QVERIFY2(m_coreController->m_appSplitTunnelingModel->rowCount() == 1, "AppSplitTunnelingModel should have 1 row");
QModelIndex appSTModelIndex = m_coreController->m_appSplitTunnelingModel->index(0, 0);
QVERIFY2(appSTModelIndex.isValid(), "Site model index should be valid");
auto appPath = m_coreController->m_appSplitTunnelingModel->data(appSTModelIndex, AppSplitTunnelingModel::AppPathRole);
QString msg = QString("app path should be %1, got %2").arg(app, appPath.toString());
QVERIFY2(app.contains(appPath.toString()) == true, msg.toLocal8Bit().constData());
auto pkgAppName = m_coreController->m_appSplitTunnelingModel->data(appSTModelIndex, AppSplitTunnelingModel::PackageAppNameRole);
QVERIFY2(pkgAppName == true, "app name should be set");
auto pkgAppIcon = m_coreController->m_appSplitTunnelingModel->data(appSTModelIndex, AppSplitTunnelingModel::PackageAppIconRole);
QVERIFY2(pkgAppIcon == true, "app image should be set");
m_coreController->m_appSplitTunnelingUiController->addApp(app);
m_coreController->m_appSplitTunnelingUiController->updateModel();
QVERIFY2(errorOccurredSpy.count() == 1, "errorOccurred signal should be emitted");
QVERIFY2(m_coreController->m_appSplitTunnelingModel->rowCount() == 1, "AppSplitTunnelingModel should have 3 rows (same app should not be added)");
m_coreController->m_appSplitTunnelingUiController->removeApp(0);
m_coreController->m_appSplitTunnelingUiController->updateModel();
QVERIFY2(finishedSpy.count() == 2, "finished signal should be emitted");
QVERIFY2(m_coreController->m_appSplitTunnelingModel->rowCount() == 0, "AppSplitTunnelingModel should have 0 rows");
}
};
QTEST_MAIN(TestUiAppSTModelAndController)
#include "testUiAppSTModelAndController.moc"

View File

@@ -0,0 +1,119 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcessEnvironment>
#include <QSignalSpy>
#include <QUuid>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestUiIpModelAndController : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
QString getValueFromIni(const QString &key)
{
QSettings settings("test_vars.ini", QSettings::IniFormat);
return settings.value(key).toString();
}
QString normalizeHostname(const QString &hostname) const
{
QString normalized = hostname;
normalized.replace("https://", "");
normalized.replace("http://", "");
normalized.replace("ftp://", "");
normalized = normalized.split("/", Qt::SkipEmptyParts).first();
return normalized;
}
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase()
{
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testRolesAndSignals()
{
QSignalSpy finishedSpy(m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::finished);
QSignalSpy errorOccurredSpy(m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::errorOccurred);
QSignalSpy isSplitTunnelingChangedSpy(m_coreController->m_ipSplitTunnelingUiController, &IpSplitTunnelingUiController::isSplitTunnelingEnabledChanged);
m_coreController->m_ipSplitTunnelingUiController->toggleSplitTunneling(true);
QVERIFY2(isSplitTunnelingChangedSpy.count() == 1, "isSplitTunnelingChangedSpy signal should be emitted");
QVERIFY2(m_coreController->m_ipSplitTunnelingUiController->isSplitTunnelingEnabled() == true, "ipSplitTunneling should be enabled");
m_coreController->m_ipSplitTunnelingUiController->toggleSplitTunneling(false);
QVERIFY2(isSplitTunnelingChangedSpy.count() == 2, "isSplitTunnelingChangedSpy signal should be emitted 2nd time");
QVERIFY2(m_coreController->m_ipSplitTunnelingUiController->isSplitTunnelingEnabled() == false, "ipSplitTunneling should be disabled");
QString site = "2ip.io";
m_coreController->m_ipSplitTunnelingUiController->addSite(site);
m_coreController->m_ipSplitTunnelingUiController->addSite("whatismyipaddress.com");
m_coreController->m_ipSplitTunnelingUiController->updateModel();
QVERIFY2(finishedSpy.count() == 2, "finished signal should be emitted 2 times");
QVERIFY2(m_coreController->m_ipSplitTunnelingModel->rowCount() == 2, "IpSplitTunnelingModel should have 2 rows");
QModelIndex siteModelIndex = m_coreController->m_ipSplitTunnelingModel->index(0, 0);
QVERIFY2(siteModelIndex.isValid(), "Ip model index should be valid");
auto siteUrl = m_coreController->m_ipSplitTunnelingModel->data(siteModelIndex, IpSplitTunnelingModel::UrlRole);
QCOMPARE(siteUrl, normalizeHostname(site));
auto siteIp = m_coreController->m_ipSplitTunnelingModel->data(siteModelIndex, IpSplitTunnelingModel::IpRole);
QVERIFY2(siteIp.isNull() == false, "Ip should not be empty");
m_coreController->m_ipSplitTunnelingUiController->removeSite(0);
m_coreController->m_ipSplitTunnelingUiController->updateModel();
QVERIFY2(finishedSpy.count() == 3, "finished signal should be emitted");
QVERIFY2(m_coreController->m_ipSplitTunnelingModel->rowCount() == 1, "IpSplitTunnelingModel should have 1 row");
m_coreController->m_ipSplitTunnelingUiController->importSites(getValueFromIni("paths/TEST_SITES_LIST_PATH"), true);
m_coreController->m_ipSplitTunnelingUiController->updateModel();
QVERIFY2(errorOccurredSpy.count() == 0, "errorOccurred signal should not be emitted");
QVERIFY2(finishedSpy.count() == 4, "finished signal should be emitted");
QVERIFY2(m_coreController->m_ipSplitTunnelingModel->rowCount() > 1, "IpSplitTunnelingModel should have more than 1 row");
m_coreController->m_ipSplitTunnelingUiController->exportSites(getValueFromIni("paths/TEST_EXPORT_PATH") + "test_ips_export.json");
QVERIFY2(finishedSpy.count() == 5, "finished signal should be emitted");
m_coreController->m_ipSplitTunnelingUiController->removeSites();
m_coreController->m_ipSplitTunnelingUiController->updateModel();
QVERIFY2(finishedSpy.count() == 6, "finished signal should be emitted");
QVERIFY2(m_coreController->m_ipSplitTunnelingModel->rowCount() == 0, "IpSplitTunnelingModel should have 0 rows");
}
};
QTEST_MAIN(TestUiIpModelAndController)
#include "testUiIpModelAndController.moc"

View File

@@ -0,0 +1,91 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcessEnvironment>
#include <QSignalSpy>
#include <QUuid>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
using namespace amnezia;
class TestUiLanguageModelAndController : public QObject
{
Q_OBJECT
private:
CoreController *m_coreController;
SecureQSettings *m_settings;
private slots:
void initTestCase()
{
QString testOrg = "AmneziaVPN-Test-" + QUuid::createUuid().toString();
m_settings = new SecureQSettings(testOrg, "amnezia-client", nullptr, false);
auto vpnConnection = QSharedPointer<VpnConnection>::create(nullptr, nullptr);
m_coreController = new CoreController(vpnConnection, m_settings, nullptr, this);
}
void cleanupTestCase()
{
m_settings->clearSettings();
delete m_coreController;
delete m_settings;
}
void init()
{
m_settings->clearSettings();
if (m_coreController->m_serversModel) {
m_coreController->m_serversModel->updateModel(QVector<ServerDescription>(), -1);
}
}
void testChangeLanguage()
{
QVERIFY2(m_coreController->m_languageModel->rowCount() > 0, "Language model should not be empty");
QSignalSpy updateTranslationsSpy(m_coreController->m_languageUiController, &LanguageUiController::updateTranslations);
QSignalSpy translationsUpdatedSpy(m_coreController->m_languageUiController, &LanguageUiController::translationsUpdated);
m_coreController->m_languageUiController->changeLanguage(LanguageSettings::AvailableLanguageEnum::China_cn);
QVERIFY2(updateTranslationsSpy.count() == 1, "updateTranslations signal should be emitted");
QVERIFY2(translationsUpdatedSpy.count() == 1, "translationsUpdated signal should be emitted");
m_coreController->m_languageUiController->changeLanguage(LanguageSettings::AvailableLanguageEnum::English);
QVERIFY2(updateTranslationsSpy.count() == 2, "updateTranslations signal should be emitted");
QVERIFY2(translationsUpdatedSpy.count() == 2, "translationsUpdated signal should be emitted");
}
void testUrl()
{
m_coreController->m_languageUiController->changeLanguage(LanguageSettings::AvailableLanguageEnum::Russian);
QString siteRU = m_coreController->m_languageUiController->getCurrentSiteUrl("test_path");
QString docsRU = m_coreController->m_languageUiController->getCurrentDocsUrl("test_path");
m_coreController->m_languageUiController->changeLanguage(LanguageSettings::AvailableLanguageEnum::English);
QString siteEN = m_coreController->m_languageUiController->getCurrentSiteUrl("test_path");
QString docsEN = m_coreController->m_languageUiController->getCurrentDocsUrl("test_path");
QVERIFY2(siteRU != siteEN, "site url's should not be same");
QVERIFY2(docsRU != docsEN, "docs url's should not be same");
}
void testLineHeight()
{
m_coreController->m_languageUiController->changeLanguage(LanguageSettings::AvailableLanguageEnum::Burmese);
QVERIFY2(m_coreController->m_languageUiController->getLineHeightAppend() == 10, "line height should be 10");
m_coreController->m_languageUiController->changeLanguage(LanguageSettings::AvailableLanguageEnum::English);
QVERIFY2(m_coreController->m_languageUiController->getLineHeightAppend() == 0, "line height should be 0");
}
};
QTEST_MAIN(TestUiLanguageModelAndController)
#include "testUiLanguageModelAndController.moc"

View File

@@ -1,4 +1,3 @@
#include <QTest>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
@@ -6,6 +5,7 @@
#include <QUuid>
#include <QSignalSpy>
#include <QModelIndex>
#include <QTest>
#include "core/controllers/coreController.h"
#include "core/models/serverDescription.h"

View File

@@ -0,0 +1,23 @@
[configs]
TEST_SELF_HOSTED_CONFIG = self-hosted_config_string
TEST_CONFIG_ANY = any_config_string
TEST_CONFIG_AWG = awg_config_string
TEST_CONFIG_WG = wg_config_string
TEST_CONFIG_XRAY = xray_config_string
TEST_CONFIG_VMESS_NEW = vmess_new_config_string
TEST_CONFIG_VMESS = vmess_config_string
TEST_CONFIG_TROJAN = trojan_config_string
TEST_CONFIG_SS = ss_config_string
TEST_CONFIG_SSD = ssd_config_string
[paths]
TEST_APP_PATH = path/to/app
TEST_SITES_LIST_PATH = path/to/file/with/sites_list
TEST_DNS_LIST_PATH = path/to/file/with/dns_list
TEST_EXPORT_PATH = path/to/export_directory
[secrets]
TEST_SERVER_HOST = server_host
TEST_SERVER_USER = server_user
TEST_SERVER_PASSWORD = server_password
TEST_SERVER_PORT = 22

View File

@@ -1,46 +0,0 @@
#include "networkReachabilityController.h"
#include <QNetworkInformation>
namespace {
bool reachabilityAllowsRemoteOperations(QNetworkInformation::Reachability r) {
using R = QNetworkInformation::Reachability;
// Unknown: no backend or not yet determined — do not block UI.
return r == R::Online || r == R::Unknown;
}
} // namespace
NetworkReachabilityController::NetworkReachabilityController(QObject *parent) : QObject(parent) {
attachToNetworkInformation();
}
bool NetworkReachabilityController::hasInternetAccess() const {
return m_hasInternetAccess;
}
void NetworkReachabilityController::attachToNetworkInformation() {
if (!QNetworkInformation::loadDefaultBackend()) {
return;
}
QNetworkInformation *ni = QNetworkInformation::instance();
if (!ni) {
return;
}
const bool initial = reachabilityAllowsRemoteOperations(ni->reachability());
const bool previous = m_hasInternetAccess;
m_hasInternetAccess = initial;
if (previous != m_hasInternetAccess) {
emit hasInternetAccessChanged();
}
connect(ni, &QNetworkInformation::reachabilityChanged, this,
[this](QNetworkInformation::Reachability r) {
const bool ok = reachabilityAllowsRemoteOperations(r);
if (ok == m_hasInternetAccess) {
return;
}
m_hasInternetAccess = ok;
emit hasInternetAccessChanged();
});
}

View File

@@ -1,30 +0,0 @@
#ifndef NETWORKREACHABILITYCONTROLLER_H
#define NETWORKREACHABILITYCONTROLLER_H
#include <QObject>
// Exposes QNetworkInformation to QML for UI that must not run remote operations offline.
// Note: mozilla/networkwatcher.h has NetworkWatcher::getReachability() using the same API,
// but networkwatcher.cpp is not linked into the desktop client (only the service process).
class NetworkReachabilityController final : public QObject {
Q_OBJECT
Q_PROPERTY(bool hasInternetAccess READ hasInternetAccess NOTIFY hasInternetAccessChanged)
public:
explicit NetworkReachabilityController(QObject *parent = nullptr);
bool hasInternetAccess() const;
signals:
void hasInternetAccessChanged();
private:
void attachToNetworkInformation();
bool m_hasInternetAccess = true;
};
#endif // NETWORKREACHABILITYCONTROLLER_H

View File

@@ -50,8 +50,6 @@ namespace PageLoader
PageServiceTorWebsiteSettings,
PageServiceDnsSettings,
PageServiceSocksProxySettings,
PageServiceMtProxySettings,
PageServiceTelemtSettings,
PageSetupWizardStart,
PageSetupWizardCredentials,

View File

@@ -3,7 +3,6 @@
#include <QDebug>
#include "../systemController.h"
#include "core/utils/qrCodeUtils.h"
ExportUiController::ExportUiController(ExportController* exportController, QObject *parent)
: QObject(parent),
@@ -54,14 +53,6 @@ void ExportUiController::generateXrayConfig(const QString &serverId, const QStri
applyExportResult(result);
}
void ExportUiController::generateQrFromString(const QString &text)
{
clearPreviousConfig();
m_config = text;
m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(text.toUtf8());
emit exportConfigChanged();
}
QString ExportUiController::getConfig()
{
return m_config;

View File

@@ -25,7 +25,6 @@ public slots:
void generateWireGuardConfig(const QString &serverId, const QString &clientName);
void generateAwgConfig(const QString &serverId, int containerIndex, const QString &clientName);
void generateXrayConfig(const QString &serverId, const QString &clientName);
void generateQrFromString(const QString &text);
QString getConfig();
QString getNativeConfigString();

View File

@@ -5,13 +5,11 @@
#include <QEventLoop>
#include <QJsonObject>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QStandardPaths>
#include <QFutureWatcher>
#include <QtConcurrent>
#include "core/utils/api/apiUtils.h"
#include "core/controllers/selfhosted/installController.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
@@ -49,8 +47,6 @@ InstallUiController::InstallUiController(InstallController *installController,
#endif
SftpConfigModel *sftpConfigModel,
Socks5ProxyConfigModel *socks5ConfigModel,
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel *telemtConfigModel,
QObject *parent)
: QObject(parent),
m_installController(installController),
@@ -67,9 +63,7 @@ InstallUiController::InstallUiController(InstallController *installController,
m_ikev2ConfigModel(ikev2ConfigModel),
#endif
m_sftpConfigModel(sftpConfigModel),
m_socks5ConfigModel(socks5ConfigModel),
m_mtProxyConfigModel(mtConfigModel),
m_telemtConfigModel(telemtConfigModel)
m_socks5ConfigModel(socks5ConfigModel)
{
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
@@ -205,7 +199,7 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
emit installationErrorOccurred(errorCode);
}
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
@@ -244,14 +238,6 @@ void InstallUiController::updateContainer(const QString &serverId, int container
containerConfig.protocolConfig = m_socks5ConfigModel->getProtocolConfig();
break;
}
case Proto::MtProxy: {
containerConfig.protocolConfig = m_mtProxyConfigModel->getProtocolConfig();
break;
}
case Proto::Telemt: {
containerConfig.protocolConfig = m_telemtConfigModel->getProtocolConfig();
break;
}
#ifdef Q_OS_WINDOWS
case Proto::Ikev2: {
containerConfig.protocolConfig = m_ikev2ConfigModel->getProtocolConfig();
@@ -263,128 +249,19 @@ void InstallUiController::updateContainer(const QString &serverId, int container
}
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
emit serverIsBusy(true);
auto *watcher = new QFutureWatcher<ErrorCode>(this);
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
[this, watcher, serverId, container, closePage]() {
const ErrorCode errorCode = watcher->result();
watcher->deleteLater();
emit serverIsBusy(false);
if (errorCode == ErrorCode::NoError) {
const ContainerConfig updatedConfig =
m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(updatedConfig);
const auto defaultContainer =
m_serversController->getDefaultContainer(serverId);
if ((serverId == m_serversController->getDefaultServerId())
&& (container == defaultContainer)) {
emit currentContainerUpdated();
} else {
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
}
} else {
emit installationErrorOccurred(errorCode);
}
});
ContainerConfig newConfigCopy = containerConfig;
ContainerConfig oldConfigCopy = oldContainerConfig;
InstallController *installController = m_installController;
QFuture<ErrorCode> future =
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
newConfigCopy]() mutable -> ErrorCode {
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
});
watcher->setFuture(future);
return;
}
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
if (errorCode == ErrorCode::NoError) {
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(updatedConfig);
const auto defaultContainer = m_serversController->getDefaultContainer(serverId);
if ((serverId == m_serversController->getDefaultServerId()) && (container == defaultContainer)) {
emit currentContainerUpdated();
} else {
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
}
emit updateContainerFinished(tr("Settings updated successfully"));
return;
}
emit installationErrorOccurred(errorCode);
}
void InstallUiController::setContainerEnabled(const QString &serverId, int containerIndex, bool enabled)
{
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return;
}
emit serverIsBusy(true);
const ErrorCode errorCode = m_installController->setDockerContainerEnabledState(serverId, container, enabled);
emit serverIsBusy(false);
if (errorCode == ErrorCode::NoError) {
const ContainerConfig currentConfig = m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(currentConfig);
emit setContainerEnabledFinished(enabled);
return;
}
emit installationErrorOccurred(errorCode);
}
void InstallUiController::refreshContainerStatus(const QString &serverId, int containerIndex)
{
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return;
}
int status = 3;
const ErrorCode errorCode = m_installController->queryDockerContainerStatus(serverId, container, status);
if (errorCode != ErrorCode::NoError) {
emit containerStatusRefreshed(3);
return;
}
emit containerStatusRefreshed(status);
}
void InstallUiController::refreshContainerDiagnostics(const QString &serverId, int containerIndex, int port)
{
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return;
}
MtProxyContainerDiagnostics diag;
const ErrorCode errorCode = m_installController->queryMtProxyDiagnostics(serverId, container, port, diag);
if (errorCode != ErrorCode::NoError) {
emit containerDiagnosticsRefreshed(false, false, -1, QString(), QString());
return;
}
emit containerDiagnosticsRefreshed(diag.portReachable, diag.upstreamReachable, diag.clientsConnected,
diag.lastConfigRefresh, diag.statsEndpoint);
}
void InstallUiController::fetchContainerSecret(const QString &serverId, int containerIndex)
{
const DockerContainer container = static_cast<DockerContainer>(containerIndex);
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
return;
}
const QString secret = m_installController->fetchDockerContainerSecret(serverId, container);
emit containerSecretFetched(secret);
}
void InstallUiController::rebootServer(const QString &serverId)
{
const QString serverName = m_serversController->notificationDisplayName(serverId);
@@ -596,8 +473,6 @@ void InstallUiController::updateProtocolConfigModel(const QString &serverId, int
case Proto::TorWebSite: updateIfPresent(m_torConfigModel, containerConfig.getTorProtocolConfig()); break;
case Proto::Sftp: updateIfPresent(m_sftpConfigModel, containerConfig.getSftpProtocolConfig()); break;
case Proto::Socks5Proxy: updateIfPresent(m_socks5ConfigModel, containerConfig.getSocks5ProxyProtocolConfig()); break;
case Proto::MtProxy: updateIfPresent(m_mtProxyConfigModel, containerConfig.getMtProxyProtocolConfig()); break;
case Proto::Telemt: updateIfPresent(m_telemtConfigModel, containerConfig.getTelemtProtocolConfig()); break;
#ifdef Q_OS_WINDOWS
case Proto::Ikev2: updateIfPresent(m_ikev2ConfigModel, containerConfig.getIkev2ProtocolConfig()); break;
#endif

View File

@@ -28,8 +28,6 @@
#include "ui/models/services/torConfigModel.h"
#include "core/models/protocols/sftpProtocolConfig.h"
#include "core/models/protocols/socks5ProxyProtocolConfig.h"
#include "ui/models/services/mtProxyConfigModel.h"
#include "ui/models/services/telemtConfigModel.h"
class InstallUiController : public QObject
{
@@ -50,8 +48,6 @@ public:
#endif
SftpConfigModel* sftpConfigModel,
Socks5ProxyConfigModel* socks5ConfigModel,
MtProxyConfigModel* mtConfigModel,
TelemtConfigModel* telemtConfigModel,
QObject *parent = nullptr);
~InstallUiController();
@@ -62,16 +58,12 @@ public slots:
void scanServerForInstalledContainers(const QString &serverId);
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex);
void removeServer(const QString &serverId);
void rebootServer(const QString &serverId);
void removeAllContainers(const QString &serverId);
void removeContainer(const QString &serverId, int containerIndex);
void setContainerEnabled(const QString &serverId, int containerIndex, bool enabled);
void refreshContainerStatus(const QString &serverId, int containerIndex);
void refreshContainerDiagnostics(const QString &serverId, int containerIndex, int port);
void fetchContainerSecret(const QString &serverId, int containerIndex);
void clearCachedProfile(const QString &serverId, int containerIndex);
@@ -102,7 +94,7 @@ signals:
void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
void installServerFinished(const QString &finishMessage);
void updateContainerFinished(const QString &message, bool closePage);
void updateContainerFinished(const QString &message);
void scanServerFinished(bool isInstalledContainerFound);
@@ -110,11 +102,6 @@ signals:
void removeServerFinished(const QString &finishedMessage);
void removeAllContainersFinished(const QString &finishedMessage);
void removeContainerFinished(const QString &finishedMessage);
void setContainerEnabledFinished(bool enabled);
void containerStatusRefreshed(int status);
void containerDiagnosticsRefreshed(bool portReachable, bool upstreamReachable, int clientsConnected,
const QString &lastConfigRefresh, const QString &statsEndpoint);
void containerSecretFetched(const QString &secret);
void installationErrorOccurred(ErrorCode errorCode);
void wrongInstallationUser(const QString &message);
@@ -127,8 +114,6 @@ signals:
void serverIsBusy(const bool isBusy);
void cancelInstallation();
void currentContainerUpdated();
void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message);
@@ -153,8 +138,6 @@ private:
#endif
SftpConfigModel* m_sftpConfigModel;
Socks5ProxyConfigModel* m_socks5ConfigModel;
MtProxyConfigModel* m_mtProxyConfigModel;
TelemtConfigModel* m_telemtConfigModel;
ServerCredentials m_processedServerCredentials;

View File

@@ -510,10 +510,6 @@ QStringList ServersUiController::getAllInstalledServicesName(int serverIndex) co
servicesName.append("TOR");
} else if (container == DockerContainer::Socks5Proxy) {
servicesName.append("SOCKS5");
} else if (container == DockerContainer::MtProxy) {
servicesName.append("MTProxy");
} else if (container == DockerContainer::Telemt) {
servicesName.append("Telemt");
}
}
}

View File

@@ -74,8 +74,6 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
case IsSftpRole: return container == DockerContainer::Sftp;
case IsTorWebsiteRole: return container == DockerContainer::TorWebSite;
case IsSocks5ProxyRole: return container == DockerContainer::Socks5Proxy;
case IsMtProxyRole: return container == DockerContainer::MtProxy;
case IsTelemtRole: return container == DockerContainer::Telemt;
case InstallPageOrderRole: return ContainerUtils::installPageOrder(container);
}
@@ -186,7 +184,5 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
roles[IsSftpRole] = "isSftp";
roles[IsTorWebsiteRole] = "isTorWebsite";
roles[IsSocks5ProxyRole] = "isSocks5Proxy";
roles[IsMtProxyRole] = "isMtProxy";
roles[IsTelemtRole] = "isTelemt";
return roles;
}

View File

@@ -48,9 +48,7 @@ public:
IsDnsRole,
IsSftpRole,
IsTorWebsiteRole,
IsSocks5ProxyRole,
IsMtProxyRole,
IsTelemtRole,
IsSocks5ProxyRole
};
Q_INVOKABLE void openContainerSettings(int containerIndex);

View File

@@ -42,8 +42,6 @@ QHash<int, QByteArray> ProtocolsModel::roleNames() const
roles[IsSftpRole] = "isSftp";
roles[IsIpsecRole] = "isIpsec";
roles[IsSocks5ProxyRole] = "isSocks5Proxy";
roles[IsMtProxyRole] = "isMtProxy";
roles[IsTelemtRole] = "isTelemt";
return roles;
}
@@ -73,8 +71,6 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const
case IsSftpRole: return proto == Proto::Sftp;
case IsIpsecRole: return proto == Proto::Ikev2;
case IsSocks5ProxyRole: return proto == Proto::Socks5Proxy;
case IsMtProxyRole: return proto == Proto::MtProxy;
case IsTelemtRole: return proto == Proto::Telemt;
case RawConfigRole:
return getRawConfig();
case IsClientProtocolExistsRole:
@@ -128,8 +124,6 @@ PageLoader::PageEnum ProtocolsModel::serverProtocolPage(Proto protocol) const
case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings;
case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings;
case Proto::Socks5Proxy: return PageLoader::PageEnum::PageServiceSocksProxySettings;
case Proto::MtProxy: return PageLoader::PageEnum::PageServiceMtProxySettings;
case Proto::Telemt: return PageLoader::PageEnum::PageServiceTelemtSettings;
default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
}
}

View File

@@ -25,9 +25,7 @@ public:
IsXrayRole,
IsSftpRole,
IsIpsecRole,
IsSocks5ProxyRole,
IsMtProxyRole,
IsTelemtRole,
IsSocks5ProxyRole
};
explicit ProtocolsModel(QObject *parent = nullptr);

View File

@@ -1,714 +0,0 @@
#include "mtProxyConfigModel.h"
#include "ui/models/utils/mtproxy_public_host_input.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/qrCodeUtils.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/constants/configKeys.h"
#include "qrcodegen.hpp"
#include <QClipboard>
#include <QGuiApplication>
#include <QHostAddress>
#include <QRegExp>
#include <QRegularExpression>
#include <QtGlobal>
#include <qqml.h>
using namespace amnezia;
MtProxyConfigModel::MtProxyConfigModel(QObject *parent) : QAbstractListModel(parent) {
qmlRegisterType<PublicHostInputValidator>("MtProxyConfig", 1, 0, "PublicHostInputValidator");
}
int MtProxyConfigModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return 1;
}
bool MtProxyConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid() || index.row() != 0) {
return false;
}
switch (role) {
case Roles::PortRole: {
m_protocolConfig.port = value.toString();
break;
}
case Roles::SecretRole: {
m_protocolConfig.secret = value.toString();
break;
}
case Roles::TagRole: {
const QString tag = sanitizeMtProxyTagFieldText(value.toString());
if (!isValidMtProxyTag(tag)) {
return false;
}
m_protocolConfig.tag = tag;
break;
}
case Roles::IsEnabledRole: {
m_protocolConfig.isEnabled = value.toBool();
break;
}
case Roles::PublicHostRole: {
const QString h = value.toString().trimmed();
if (!isValidPublicHost(h)) {
return false;
}
m_protocolConfig.publicHost = h;
break;
}
case Roles::TransportModeRole: {
m_protocolConfig.transportMode = value.toString();
break;
}
case Roles::TlsDomainRole: {
const QString d = value.toString().trimmed();
if (!isValidFakeTlsDomain(d)) {
return false;
}
m_protocolConfig.tlsDomain = d;
break;
}
case Roles::AdditionalSecretsRole: {
m_protocolConfig.additionalSecrets = value.toStringList();
break;
}
case Roles::WorkersModeRole: {
m_protocolConfig.workersMode = value.toString();
break;
}
case Roles::WorkersRole: {
m_protocolConfig.workers = value.toString();
break;
}
case Roles::NatEnabledRole: {
m_protocolConfig.natEnabled = value.toBool();
break;
}
case Roles::NatInternalIpRole: {
const QString ip = value.toString().trimmed();
if (!isValidOptionalIpv4(ip)) {
return false;
}
m_protocolConfig.natInternalIp = ip;
break;
}
case Roles::NatExternalIpRole: {
const QString ip = value.toString().trimmed();
if (!isValidOptionalIpv4(ip)) {
return false;
}
m_protocolConfig.natExternalIp = ip;
break;
}
default: {
return false;
}
}
emit dataChanged(index, index, QList{role});
return true;
}
QVariant MtProxyConfigModel::data(const QModelIndex &index, int role) const {
if (!index.isValid() || index.row() != 0) {
return QVariant();
}
switch (role) {
case Roles::PortRole: {
return m_protocolConfig.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : m_protocolConfig.port;
}
case Roles::SecretRole: {
return m_protocolConfig.secret;
}
case Roles::TagRole: {
return m_protocolConfig.tag;
}
case Roles::TgLinkRole: {
return m_protocolConfig.tgLink;
}
case Roles::TmeLinkRole: {
return m_protocolConfig.tmeLink;
}
case Roles::IsEnabledRole: {
return m_protocolConfig.isEnabled;
}
case Roles::PublicHostRole: {
return m_protocolConfig.publicHost.isEmpty()
? m_fullConfig.value(configKey::hostName).toString()
: m_protocolConfig.publicHost;
}
case Roles::TransportModeRole: {
return m_protocolConfig.transportMode.isEmpty()
? QString(protocols::mtProxy::transportModeStandard)
: m_protocolConfig.transportMode;
}
case Roles::TlsDomainRole: {
return m_protocolConfig.tlsDomain;
}
case Roles::AdditionalSecretsRole: {
return m_protocolConfig.additionalSecrets;
}
case Roles::WorkersModeRole: {
return m_protocolConfig.workersMode.isEmpty()
? QString(protocols::mtProxy::workersModeAuto)
: m_protocolConfig.workersMode;
}
case Roles::WorkersRole: {
return m_protocolConfig.workers.isEmpty() ? QString(protocols::mtProxy::defaultWorkers)
: m_protocolConfig.workers;
}
case Roles::NatEnabledRole: {
return m_protocolConfig.natEnabled;
}
case Roles::NatInternalIpRole: {
return m_protocolConfig.natInternalIp;
}
case Roles::NatExternalIpRole: {
return m_protocolConfig.natExternalIp;
}
}
return QVariant();
}
void MtProxyConfigModel::updateModel(amnezia::DockerContainer container,
const amnezia::MtProxyProtocolConfig &protocolConfig) {
beginResetModel();
m_container = container;
m_protocolConfig = protocolConfig;
endResetModel();
}
void MtProxyConfigModel::updateModel(const QJsonObject &config) {
beginResetModel();
m_fullConfig = config;
m_protocolConfig = MtProxyProtocolConfig::fromJson(config.value(configKey::mtproxy).toObject());
if (m_protocolConfig.port.isEmpty()) m_protocolConfig.port = protocols::mtProxy::defaultPort;
if (m_protocolConfig.transportMode.isEmpty()) m_protocolConfig.transportMode = protocols::mtProxy::transportModeStandard;
if (m_protocolConfig.workersMode.isEmpty()) m_protocolConfig.workersMode = protocols::mtProxy::workersModeAuto;
if (m_protocolConfig.workers.isEmpty()) m_protocolConfig.workers = protocols::mtProxy::defaultWorkers;
{
QString tagIn = sanitizeMtProxyTagFieldText(m_protocolConfig.tag);
if (!isValidMtProxyTag(tagIn)) {
tagIn.clear();
}
m_protocolConfig.tag = tagIn;
}
endResetModel();
}
QJsonObject MtProxyConfigModel::getConfig() {
m_fullConfig.insert(configKey::mtproxy, m_protocolConfig.toJson());
return m_fullConfig;
}
void MtProxyConfigModel::generateSecret() {
// Generate 16 random bytes = 32 hex chars
QString secret;
for (int i = 0; i < 16; ++i) {
quint32 byte = QRandomGenerator::global()->bounded(256);
secret += QString("%1").arg(byte, 2, 16, QChar('0'));
}
m_protocolConfig.secret = secret;
emit dataChanged(index(0), index(0), QList<int>{SecretRole});
}
void MtProxyConfigModel::setSecret(const QString &secret) {
if (secret.isEmpty()) {
return;
}
setData(index(0), secret, SecretRole);
}
bool MtProxyConfigModel::validateAndSetSecret(const QString &rawSecret) {
if (!QRegularExpression("^[0-9a-fA-F]{32}$").match(rawSecret).hasMatch()) {
return false;
}
setData(index(0), rawSecret, SecretRole);
return true;
}
void MtProxyConfigModel::setPort(const QString &port) {
setData(index(0), port, PortRole);
}
void MtProxyConfigModel::setTag(const QString &tag) {
setData(index(0), tag, TagRole);
}
void MtProxyConfigModel::setPublicHost(const QString &host) {
const QString t = host.trimmed();
if (!isValidPublicHost(t)) {
return;
}
setData(index(0), t, PublicHostRole);
}
void MtProxyConfigModel::setTransportMode(const QString &mode) {
setData(index(0), mode, TransportModeRole);
}
QString MtProxyConfigModel::getTransportMode() const {
return m_protocolConfig.transportMode.isEmpty()
? QString(protocols::mtProxy::transportModeStandard)
: m_protocolConfig.transportMode;
}
QString MtProxyConfigModel::getTlsDomain() const {
return m_protocolConfig.tlsDomain.isEmpty()
? QString(protocols::mtProxy::defaultTlsDomain)
: m_protocolConfig.tlsDomain;
}
QString MtProxyConfigModel::getPublicHost() const {
return m_protocolConfig.publicHost;
}
void MtProxyConfigModel::setTlsDomain(const QString &domain) {
const QString t = domain.trimmed();
if (!isValidFakeTlsDomain(t)) {
return;
}
setData(index(0), t, TlsDomainRole);
}
void MtProxyConfigModel::setWorkersMode(const QString &mode) {
setData(index(0), mode, WorkersModeRole);
}
void MtProxyConfigModel::setWorkers(const QString &workers) {
setData(index(0), workers, WorkersRole);
}
void MtProxyConfigModel::setNatEnabled(bool enabled) {
setData(index(0), enabled, NatEnabledRole);
}
void MtProxyConfigModel::setNatInternalIp(const QString &ip) {
const QString t = ip.trimmed();
if (!isValidOptionalIpv4(t)) {
return;
}
setData(index(0), t, NatInternalIpRole);
}
void MtProxyConfigModel::setNatExternalIp(const QString &ip) {
const QString t = ip.trimmed();
if (!isValidOptionalIpv4(t)) {
return;
}
setData(index(0), t, NatExternalIpRole);
}
void MtProxyConfigModel::addAdditionalSecret() {
QString newSecret;
for (int i = 0; i < 16; ++i) {
quint32 byte = QRandomGenerator::global()->bounded(256);
newSecret += QString("%1").arg(byte, 2, 16, QChar('0'));
}
m_protocolConfig.additionalSecrets.append(newSecret);
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
}
void MtProxyConfigModel::removeAdditionalSecret(int idx) {
if (idx < 0 || idx >= m_protocolConfig.additionalSecrets.size()) {
return;
}
m_protocolConfig.additionalSecrets.removeAt(idx);
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
}
QVariantList MtProxyConfigModel::additionalSecretsList() const {
QVariantList out;
out.reserve(m_protocolConfig.additionalSecrets.size());
for (const auto &s: m_protocolConfig.additionalSecrets) {
if (!s.isEmpty()) {
out.append(s);
}
}
return out;
}
void MtProxyConfigModel::setEnabled(bool enabled) {
m_protocolConfig.isEnabled = enabled;
emit dataChanged(index(0), index(0), QList<int>{IsEnabledRole});
}
QString MtProxyConfigModel::generateQrCode(const QString &text) {
if (text.isEmpty()) {
return "";
}
auto qr = qrCodeUtils::generateQrCode(text.toUtf8());
return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
}
QString MtProxyConfigModel::defaultTlsDomain() const {
return protocols::mtProxy::defaultTlsDomain;
}
QString MtProxyConfigModel::defaultPort() const {
return protocols::mtProxy::defaultPort;
}
QString MtProxyConfigModel::defaultWorkers() const {
return protocols::mtProxy::defaultWorkers;
}
int MtProxyConfigModel::maxWorkers() const {
return protocols::mtProxy::maxWorkers;
}
QString MtProxyConfigModel::transportModeStandard() const {
return protocols::mtProxy::transportModeStandard;
}
QString MtProxyConfigModel::transportModeFakeTLS() const {
return protocols::mtProxy::transportModeFakeTLS;
}
QString MtProxyConfigModel::workersModeAuto() const {
return protocols::mtProxy::workersModeAuto;
}
QString MtProxyConfigModel::workersModeManual() const {
return protocols::mtProxy::workersModeManual;
}
bool MtProxyConfigModel::isValidPublicHost(const QString &host) const {
const QString t = host.trimmed();
if (t.isEmpty()) {
return true;
}
if (t.length() > 253) {
return false;
}
QHostAddress a(t);
if (a.protocol() == QHostAddress::IPv4Protocol) {
return NetworkUtilities::checkIPv4Format(t);
}
if (a.protocol() == QHostAddress::IPv6Protocol) {
return true;
}
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
if (onlyAsciiDigits.match(t).hasMatch()) {
return false;
}
return NetworkUtilities::domainRegExp().exactMatch(t);
}
bool MtProxyConfigModel::isPublicHostInputAllowed(const QString &text) const {
return mtproxyPublicHostInputAllowed(text);
}
bool MtProxyConfigModel::isPublicHostTypingIncomplete(const QString &text) const {
const QString t = text.trimmed();
if (isValidPublicHost(t)) {
return false;
}
static const QRegularExpression onlyDigitDot(QStringLiteral(R"(^[0-9.]+$)"));
if (onlyDigitDot.match(t).hasMatch()) {
if (t.endsWith(QLatin1Char('.'))) {
return true;
}
const QStringList parts = t.split(QLatin1Char('.'), Qt::KeepEmptyParts);
if (parts.size() < 4) {
return true;
}
for (const QString &part: parts) {
if (part.isEmpty()) {
return true;
}
}
return false;
}
if (t.contains(QLatin1Char(':'))) {
if (t.contains(QLatin1String(":::"))) {
return false;
}
if (t.endsWith(QLatin1Char(':'))) {
return true;
}
QHostAddress a(t);
if (a.protocol() == QHostAddress::IPv6Protocol) {
return false;
}
if (!t.contains(QLatin1String("::")) && t.count(QLatin1Char(':')) < 7 && !t.contains(QLatin1Char('.'))) {
return true;
}
return false;
}
if (!t.contains(QLatin1Char('.'))) {
return true;
}
return false;
}
bool MtProxyConfigModel::isValidMtProxyTag(const QString &tag) const {
if (tag.isEmpty()) {
return true;
}
static const QRegularExpression re(
QStringLiteral("^([0-9a-fA-F]{%1})$").arg(protocols::mtProxy::botTagHexLength));
return re.match(tag).hasMatch();
}
bool MtProxyConfigModel::isMtProxyTagTypingIncomplete(const QString &text) const {
const QString t = text.trimmed();
if (t.isEmpty()) {
return true;
}
static const QRegularExpression hexOnly(QStringLiteral(R"(^[0-9a-fA-F]*$)"));
if (!hexOnly.match(t).hasMatch()) {
return false;
}
return t.size() < protocols::mtProxy::botTagHexLength;
}
int MtProxyConfigModel::mtProxyBotTagHexLength() const {
return protocols::mtProxy::botTagHexLength;
}
bool MtProxyConfigModel::isValidFakeTlsDomain(const QString &domain) const {
const QString t = domain.trimmed();
if (t.isEmpty()) {
return true;
}
if (t.length() > 253) {
return false;
}
QHostAddress addr;
if (addr.setAddress(t)) {
return false;
}
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
if (onlyAsciiDigits.match(t).hasMatch()) {
return false;
}
QRegExp re(NetworkUtilities::domainRegExp());
re.setCaseSensitivity(Qt::CaseInsensitive);
if (!re.exactMatch(t)) {
return false;
}
// ee + 32 hex (base secret) + hex(UTF-8 domain); keep headroom under typical client limits.
if (t.toUtf8().size() > 111) {
return false;
}
return true;
}
QString MtProxyConfigModel::clipboardText() const {
if (QClipboard *c = QGuiApplication::clipboard()) {
return c->text();
}
return QString();
}
QString MtProxyConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
const QString t = normalizeFakeTlsDomainInput(input);
QString out;
out.reserve(t.size());
for (const QChar &c: t) {
const ushort u = c.unicode();
const bool letter = (u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z');
const bool digit = (u >= '0' && u <= '9');
if (letter || digit || u == '.' || u == '-') {
out.append(c);
}
}
if (out.size() > 253) {
out.truncate(253);
}
return out;
}
bool MtProxyConfigModel::isFakeTlsDomainInputAllowed(const QString &text) const {
if (text.length() > 253) {
return false;
}
static const QRegularExpression re(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
return re.match(text).hasMatch();
}
QString MtProxyConfigModel::sanitizePublicHostFieldText(const QString &input) const {
QString out;
const int cap = qMin(input.size(), 253);
out.reserve(cap);
for (const QChar &c: input) {
if (out.size() >= 253) {
break;
}
const ushort u = c.unicode();
if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '.' || u == ':' ||
u == '-') {
out.append(c);
}
}
return out;
}
QString MtProxyConfigModel::sanitizePortFieldText(const QString &input) const {
QString out;
out.reserve(qMin(input.size(), 5));
for (const QChar &c: input) {
const ushort u = c.unicode();
if (u >= '0' && u <= '9' && out.size() < 5) {
out.append(c);
}
}
return out;
}
QString MtProxyConfigModel::sanitizeMtProxyTagFieldText(const QString &input) const {
QString trimmed = input.trimmed();
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
trimmed = trimmed.mid(2).trimmed();
}
// Prefer a contiguous 32-hex run (paste from bot message with extra text).
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
const QRegularExpressionMatch m = runHex.match(trimmed);
if (m.hasMatch()) {
return m.captured(1);
}
const int cap = protocols::mtProxy::botTagHexLength;
QString out;
out.reserve(qMin(trimmed.size(), cap));
for (const QChar &c: trimmed) {
if (out.size() >= cap) {
break;
}
const ushort u = c.unicode();
if ((u >= '0' && u <= '9') || (u >= 'a' && u <= 'f') || (u >= 'A' && u <= 'F')) {
out.append(c);
}
}
return out;
}
QString MtProxyConfigModel::sanitizeWorkersFieldText(const QString &input) const {
QString out;
out.reserve(qMin(input.size(), 3));
for (const QChar &c: input) {
const ushort u = c.unicode();
if (u >= '0' && u <= '9' && out.size() < 3) {
out.append(c);
}
}
return out;
}
QString MtProxyConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
QString out;
out.reserve(qMin(input.size(), 15));
for (const QChar &c: input) {
if (out.size() >= 15) {
break;
}
const ushort u = c.unicode();
if ((u >= '0' && u <= '9') || u == '.') {
out.append(c);
}
}
return out;
}
QString MtProxyConfigModel::normalizeFakeTlsDomainInput(const QString &input) const {
QString t = input.trimmed();
if (t.startsWith(QLatin1String("https://"), Qt::CaseInsensitive)) {
t = t.mid(8);
} else if (t.startsWith(QLatin1String("http://"), Qt::CaseInsensitive)) {
t = t.mid(7);
}
if (const int slash = t.indexOf(QLatin1Char('/')); slash >= 0) {
t = t.left(slash);
}
if (const int at = t.indexOf(QLatin1Char('@')); at >= 0) {
t = t.mid(at + 1);
}
if (const int colon = t.indexOf(QLatin1Char(':')); colon >= 0) {
t = t.left(colon);
}
if (t.startsWith(QLatin1String("www."), Qt::CaseInsensitive)) {
const QString rest = t.mid(4);
if (rest.contains(QLatin1Char('.'))) {
t = rest;
}
}
return t.trimmed();
}
bool MtProxyConfigModel::isFakeTlsDomainTypingIncomplete(const QString &text) const {
const QString t = text.trimmed();
if (t.isEmpty()) {
return true;
}
if (isValidFakeTlsDomain(t)) {
return false;
}
if (t.contains(QLatin1Char('/')) || t.contains(QLatin1Char(':')) || t.contains(QLatin1Char('@'))
|| t.contains(QLatin1Char(' '))) {
return false;
}
if (t.contains(QLatin1String(".."))) {
return false;
}
if (!t.contains(QLatin1Char('.'))) {
return true;
}
if (t.endsWith(QLatin1Char('.'))) {
return true;
}
static const QRegularExpression legalPartial(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
if (!legalPartial.match(t).hasMatch()) {
return false;
}
return true;
}
bool MtProxyConfigModel::isValidOptionalIpv4(const QString &ip) const {
const QString t = ip.trimmed();
if (t.isEmpty()) {
return true;
}
return NetworkUtilities::checkIPv4Format(t);
}
QHash<int, QByteArray> MtProxyConfigModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[PortRole] = "port";
roles[SecretRole] = "secret";
roles[TagRole] = "tag";
roles[TgLinkRole] = "tgLink";
roles[TmeLinkRole] = "tmeLink";
roles[IsEnabledRole] = "isEnabled";
roles[PublicHostRole] = "publicHost";
roles[TransportModeRole] = "transportMode";
roles[TlsDomainRole] = "tlsDomain";
roles[AdditionalSecretsRole] = "additionalSecrets";
roles[WorkersModeRole] = "workersMode";
roles[WorkersRole] = "workers";
roles[NatEnabledRole] = "natEnabled";
roles[NatInternalIpRole] = "natInternalIp";
roles[NatExternalIpRole] = "natExternalIp";
return roles;
}
amnezia::MtProxyProtocolConfig MtProxyConfigModel::getProtocolConfig() {
return m_protocolConfig;
}

View File

@@ -1,156 +0,0 @@
#ifndef MTPROXYCONFIGMODEL_H
#define MTPROXYCONFIGMODEL_H
#include <QAbstractListModel>
#include <QJsonArray>
#include <QJsonObject>
#include <QRandomGenerator>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/models/protocols/mtProxyProtocolConfig.h"
class MtProxyConfigModel : public QAbstractListModel {
Q_OBJECT
public:
enum Roles {
PortRole = Qt::UserRole + 1,
SecretRole,
TagRole,
TgLinkRole,
TmeLinkRole,
IsEnabledRole,
PublicHostRole,
TransportModeRole,
TlsDomainRole,
AdditionalSecretsRole,
WorkersModeRole,
WorkersRole,
NatEnabledRole,
NatInternalIpRole,
NatExternalIpRole
};
explicit MtProxyConfigModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
public slots:
void updateModel(amnezia::DockerContainer container, const amnezia::MtProxyProtocolConfig &protocolConfig);
void updateModel(const QJsonObject &config);
QJsonObject getConfig();
amnezia::MtProxyProtocolConfig getProtocolConfig();
Q_INVOKABLE void generateSecret();
Q_INVOKABLE void setSecret(const QString &secret);
Q_INVOKABLE bool validateAndSetSecret(const QString &rawSecret);
Q_INVOKABLE void setPort(const QString &port);
Q_INVOKABLE void setTag(const QString &tag);
Q_INVOKABLE void setPublicHost(const QString &host);
Q_INVOKABLE void setTransportMode(const QString &mode);
Q_INVOKABLE QString getTransportMode() const;
Q_INVOKABLE QString getTlsDomain() const;
Q_INVOKABLE QString getPublicHost() const;
Q_INVOKABLE void setTlsDomain(const QString &domain);
Q_INVOKABLE void setWorkersMode(const QString &mode);
Q_INVOKABLE void setWorkers(const QString &workers);
Q_INVOKABLE void setNatEnabled(bool enabled);
Q_INVOKABLE void setNatInternalIp(const QString &ip);
Q_INVOKABLE void setNatExternalIp(const QString &ip);
Q_INVOKABLE void addAdditionalSecret();
Q_INVOKABLE void removeAdditionalSecret(int idx);
/// Current `mtproxy_additional_secrets` list from in-memory config (for QML snapshot vs. unsaved adds).
Q_INVOKABLE QVariantList additionalSecretsList() const;
Q_INVOKABLE QString generateQrCode(const QString &text);
Q_INVOKABLE void setEnabled(bool enabled);
Q_INVOKABLE QString defaultTlsDomain() const;
Q_INVOKABLE QString defaultPort() const;
Q_INVOKABLE QString defaultWorkers() const;
Q_INVOKABLE int maxWorkers() const;
Q_INVOKABLE QString transportModeStandard() const;
Q_INVOKABLE QString transportModeFakeTLS() const;
Q_INVOKABLE QString workersModeAuto() const;
Q_INVOKABLE QString workersModeManual() const;
Q_INVOKABLE bool isValidPublicHost(const QString &host) const;
Q_INVOKABLE bool isPublicHostInputAllowed(const QString &text) const;
Q_INVOKABLE bool isPublicHostTypingIncomplete(const QString &text) const;
Q_INVOKABLE bool isValidMtProxyTag(const QString &tag) const;
Q_INVOKABLE bool isMtProxyTagTypingIncomplete(const QString &text) const;
Q_INVOKABLE int mtProxyBotTagHexLength() const;
Q_INVOKABLE bool isValidFakeTlsDomain(const QString &domain) const;
Q_INVOKABLE QString normalizeFakeTlsDomainInput(const QString &input) const;
Q_INVOKABLE QString sanitizeFakeTlsDomainFieldText(const QString &input) const;
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
Q_INVOKABLE QString clipboardText() const;
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizeWorkersFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
Q_INVOKABLE bool isValidOptionalIpv4(const QString &ip) const;
protected:
QHash<int, QByteArray> roleNames() const override;
private:
amnezia::DockerContainer m_container;
QJsonObject m_fullConfig;
amnezia::MtProxyProtocolConfig m_protocolConfig;
};
#endif // MTPROXYCONFIGMODEL_H

View File

@@ -1,406 +0,0 @@
#include "telemtConfigModel.h"
#include <QRegularExpression>
#include "core/utils/qrCodeUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "qrcodegen.hpp"
using namespace amnezia;
TelemtConfigModel::TelemtConfigModel(QObject *parent) : QAbstractListModel(parent) {}
void TelemtConfigModel::applyDefaults(TelemtProtocolConfig &c) {
if (c.port.isEmpty()) {
c.port = QString::fromUtf8(protocols::telemt::defaultPort);
}
if (c.transportMode.isEmpty()) {
c.transportMode = QString::fromUtf8(protocols::telemt::transportModeStandard);
}
if (c.workersMode.isEmpty()) {
c.workersMode = QString::fromUtf8(protocols::telemt::workersModeAuto);
}
if (c.workers.isEmpty()) {
c.workers = QString::fromUtf8(protocols::telemt::defaultWorkers);
}
if (c.userName.isEmpty()) {
c.userName = QString::fromUtf8(protocols::telemt::defaultUserName);
}
}
int TelemtConfigModel::rowCount(const QModelIndex &parent) const {
Q_UNUSED(parent);
return 1;
}
bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) {
if (!index.isValid() || index.row() != 0) {
return false;
}
switch (role) {
case Roles::PortRole: {
m_protocolConfig.port = value.toString();
break;
}
case Roles::SecretRole: {
m_protocolConfig.secret = value.toString();
break;
}
case Roles::TagRole: {
m_protocolConfig.tag = value.toString();
break;
}
case Roles::IsEnabledRole: {
m_protocolConfig.isEnabled = value.toBool();
break;
}
case Roles::PublicHostRole: {
m_protocolConfig.publicHost = value.toString();
break;
}
case Roles::TransportModeRole: {
m_protocolConfig.transportMode = value.toString();
break;
}
case Roles::TlsDomainRole: {
m_protocolConfig.tlsDomain = value.toString();
break;
}
case Roles::AdditionalSecretsRole: {
m_protocolConfig.additionalSecrets = value.toStringList();
break;
}
case Roles::WorkersModeRole: {
m_protocolConfig.workersMode = value.toString();
break;
}
case Roles::WorkersRole: {
m_protocolConfig.workers = value.toString();
break;
}
case Roles::NatEnabledRole: {
m_protocolConfig.natEnabled = value.toBool();
break;
}
case Roles::NatInternalIpRole: {
m_protocolConfig.natInternalIp = value.toString();
break;
}
case Roles::NatExternalIpRole: {
m_protocolConfig.natExternalIp = value.toString();
break;
}
case Roles::MaskEnabledRole: {
m_protocolConfig.maskEnabled = value.toBool();
break;
}
case Roles::UseMiddleProxyRole: {
m_protocolConfig.useMiddleProxy = value.toBool();
break;
}
case Roles::TlsEmulationRole: {
m_protocolConfig.tlsEmulation = value.toBool();
break;
}
case Roles::UserNameRole: {
m_protocolConfig.userName = value.toString();
break;
}
default: {
return false;
}
}
emit dataChanged(index, index, QList{role});
return true;
}
QVariant TelemtConfigModel::data(const QModelIndex &index, int role) const {
if (!index.isValid() || index.row() != 0) {
return QVariant();
}
switch (role) {
case Roles::PortRole: {
return m_protocolConfig.port.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultPort)
: m_protocolConfig.port;
}
case Roles::SecretRole: {
return m_protocolConfig.secret;
}
case Roles::TagRole: {
return m_protocolConfig.tag;
}
case Roles::TgLinkRole: {
return m_protocolConfig.tgLink;
}
case Roles::TmeLinkRole: {
return m_protocolConfig.tmeLink;
}
case Roles::IsEnabledRole: {
return m_protocolConfig.isEnabled;
}
case Roles::PublicHostRole: {
return m_protocolConfig.publicHost.isEmpty() ? m_fullConfig.value(QString(configKey::hostName)).toString()
: m_protocolConfig.publicHost;
}
case Roles::TransportModeRole: {
return m_protocolConfig.transportMode.isEmpty() ? QString::fromUtf8(
protocols::telemt::transportModeStandard)
: m_protocolConfig.transportMode;
}
case Roles::TlsDomainRole: {
return m_protocolConfig.tlsDomain;
}
case Roles::AdditionalSecretsRole: {
return m_protocolConfig.additionalSecrets;
}
case Roles::WorkersModeRole: {
return m_protocolConfig.workersMode.isEmpty() ? QString::fromUtf8(protocols::telemt::workersModeAuto)
: m_protocolConfig.workersMode;
}
case Roles::WorkersRole: {
return m_protocolConfig.workers.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultWorkers)
: m_protocolConfig.workers;
}
case Roles::NatEnabledRole: {
return m_protocolConfig.natEnabled;
}
case Roles::NatInternalIpRole: {
return m_protocolConfig.natInternalIp;
}
case Roles::NatExternalIpRole: {
return m_protocolConfig.natExternalIp;
}
case Roles::MaskEnabledRole: {
return m_protocolConfig.maskEnabled;
}
case Roles::UseMiddleProxyRole: {
return m_protocolConfig.useMiddleProxy;
}
case Roles::TlsEmulationRole: {
return m_protocolConfig.tlsEmulation;
}
case Roles::UserNameRole: {
return m_protocolConfig.userName.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultUserName)
: m_protocolConfig.userName;
}
}
return QVariant();
}
void TelemtConfigModel::updateModel(DockerContainer container, const TelemtProtocolConfig &protocolConfig) {
beginResetModel();
m_container = container;
m_protocolConfig = protocolConfig;
applyDefaults(m_protocolConfig);
endResetModel();
}
void TelemtConfigModel::updateModel(const QJsonObject &config) {
beginResetModel();
m_fullConfig = config;
m_protocolConfig = TelemtProtocolConfig::fromJson(config.value(QString(configKey::telemt)).toObject());
applyDefaults(m_protocolConfig);
endResetModel();
}
QJsonObject TelemtConfigModel::getConfig() {
m_fullConfig.insert(QString(configKey::telemt), m_protocolConfig.toJson());
return m_fullConfig;
}
TelemtProtocolConfig TelemtConfigModel::getProtocolConfig() {
return m_protocolConfig;
}
void TelemtConfigModel::generateSecret() {
QString secret;
for (int i = 0; i < 16; ++i) {
quint32 byte = QRandomGenerator::global()->bounded(256);
secret += QString("%1").arg(byte, 2, 16, QChar('0'));
}
m_protocolConfig.secret = secret;
emit dataChanged(index(0), index(0), QList<int>{SecretRole});
}
void TelemtConfigModel::setSecret(const QString &secret) {
if (secret.isEmpty()) {
return;
}
setData(index(0), secret, SecretRole);
}
bool TelemtConfigModel::validateAndSetSecret(const QString &rawSecret) {
if (!QRegularExpression(QStringLiteral("^[0-9a-fA-F]{32}$")).match(rawSecret).hasMatch()) {
return false;
}
setData(index(0), rawSecret, SecretRole);
return true;
}
void TelemtConfigModel::setPort(const QString &port) {
setData(index(0), port, PortRole);
}
void TelemtConfigModel::setTag(const QString &tag) {
setData(index(0), tag, TagRole);
}
void TelemtConfigModel::setPublicHost(const QString &host) {
setData(index(0), host, PublicHostRole);
}
void TelemtConfigModel::setTransportMode(const QString &mode) {
setData(index(0), mode, TransportModeRole);
}
QString TelemtConfigModel::getTransportMode() const {
return m_protocolConfig.transportMode.isEmpty() ? QString::fromUtf8(protocols::telemt::transportModeStandard)
: m_protocolConfig.transportMode;
}
QString TelemtConfigModel::getTlsDomain() const {
return m_protocolConfig.tlsDomain.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultTlsDomain)
: m_protocolConfig.tlsDomain;
}
QString TelemtConfigModel::getPublicHost() const {
return m_protocolConfig.publicHost;
}
void TelemtConfigModel::setTlsDomain(const QString &domain) {
setData(index(0), domain, TlsDomainRole);
}
void TelemtConfigModel::setWorkersMode(const QString &mode) {
setData(index(0), mode, WorkersModeRole);
}
void TelemtConfigModel::setWorkers(const QString &workers) {
setData(index(0), workers, WorkersRole);
}
void TelemtConfigModel::setNatEnabled(bool enabled) {
setData(index(0), enabled, NatEnabledRole);
}
void TelemtConfigModel::setNatInternalIp(const QString &ip) {
setData(index(0), ip, NatInternalIpRole);
}
void TelemtConfigModel::setNatExternalIp(const QString &ip) {
setData(index(0), ip, NatExternalIpRole);
}
void TelemtConfigModel::setMaskEnabled(bool enabled) {
setData(index(0), enabled, MaskEnabledRole);
}
void TelemtConfigModel::setUseMiddleProxy(bool enabled) {
setData(index(0), enabled, UseMiddleProxyRole);
}
void TelemtConfigModel::setTlsEmulation(bool enabled) {
setData(index(0), enabled, TlsEmulationRole);
}
void TelemtConfigModel::setUserName(const QString &name) {
setData(index(0), name, UserNameRole);
}
void TelemtConfigModel::addAdditionalSecret() {
QString newSecret;
for (int i = 0; i < 16; ++i) {
quint32 byte = QRandomGenerator::global()->bounded(256);
newSecret += QString("%1").arg(byte, 2, 16, QChar('0'));
}
m_protocolConfig.additionalSecrets.append(newSecret);
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
}
void TelemtConfigModel::removeAdditionalSecret(int idx) {
if (idx < 0 || idx >= m_protocolConfig.additionalSecrets.size()) {
return;
}
m_protocolConfig.additionalSecrets.removeAt(idx);
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
}
void TelemtConfigModel::setEnabled(bool enabled) {
m_protocolConfig.isEnabled = enabled;
emit dataChanged(index(0), index(0), QList<int>{IsEnabledRole});
}
QString TelemtConfigModel::generateQrCode(const QString &text) {
if (text.isEmpty()) {
return "";
}
auto qr = qrCodeUtils::generateQrCode(text.toUtf8());
return qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
}
QString TelemtConfigModel::defaultTlsDomain() const {
return QString::fromUtf8(protocols::telemt::defaultTlsDomain);
}
QString TelemtConfigModel::defaultPort() const {
return QString::fromUtf8(protocols::telemt::defaultPort);
}
QString TelemtConfigModel::defaultWorkers() const {
return QString::fromUtf8(protocols::telemt::defaultWorkers);
}
int TelemtConfigModel::maxWorkers() const {
return protocols::telemt::maxWorkers;
}
QString TelemtConfigModel::transportModeStandard() const {
return QString::fromUtf8(protocols::telemt::transportModeStandard);
}
QString TelemtConfigModel::transportModeFakeTLS() const {
return QString::fromUtf8(protocols::telemt::transportModeFakeTLS);
}
QString TelemtConfigModel::workersModeAuto() const {
return QString::fromUtf8(protocols::telemt::workersModeAuto);
}
QString TelemtConfigModel::workersModeManual() const {
return QString::fromUtf8(protocols::telemt::workersModeManual);
}
QHash<int, QByteArray> TelemtConfigModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[PortRole] = "port";
roles[SecretRole] = "secret";
roles[TagRole] = "tag";
roles[TgLinkRole] = "tgLink";
roles[TmeLinkRole] = "tmeLink";
roles[IsEnabledRole] = "isEnabled";
roles[PublicHostRole] = "publicHost";
roles[TransportModeRole] = "transportMode";
roles[TlsDomainRole] = "tlsDomain";
roles[AdditionalSecretsRole] = "additionalSecrets";
roles[WorkersModeRole] = "workersMode";
roles[WorkersRole] = "workers";
roles[NatEnabledRole] = "natEnabled";
roles[NatInternalIpRole] = "natInternalIp";
roles[NatExternalIpRole] = "natExternalIp";
roles[MaskEnabledRole] = "maskEnabled";
roles[UseMiddleProxyRole] = "useMiddleProxy";
roles[TlsEmulationRole] = "tlsEmulation";
roles[UserNameRole] = "userName";
return roles;
}

View File

@@ -1,130 +0,0 @@
#ifndef TELEMTCONFIGMODEL_H
#define TELEMTCONFIGMODEL_H
#include <QAbstractListModel>
#include <QJsonObject>
#include <QRandomGenerator>
#include "core/models/protocols/telemtProtocolConfig.h"
#include "core/utils/containerEnum.h"
class TelemtConfigModel : public QAbstractListModel {
Q_OBJECT
public:
enum Roles {
PortRole = Qt::UserRole + 1,
SecretRole,
TagRole,
TgLinkRole,
TmeLinkRole,
IsEnabledRole,
PublicHostRole,
TransportModeRole,
TlsDomainRole,
AdditionalSecretsRole,
WorkersModeRole,
WorkersRole,
NatEnabledRole,
NatInternalIpRole,
NatExternalIpRole,
MaskEnabledRole,
UseMiddleProxyRole,
TlsEmulationRole,
UserNameRole
};
explicit TelemtConfigModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
public slots:
void updateModel(amnezia::DockerContainer container, const amnezia::TelemtProtocolConfig &protocolConfig);
void updateModel(const QJsonObject &config);
QJsonObject getConfig();
amnezia::TelemtProtocolConfig getProtocolConfig();
Q_INVOKABLE void generateSecret();
Q_INVOKABLE void setSecret(const QString &secret);
Q_INVOKABLE bool validateAndSetSecret(const QString &rawSecret);
Q_INVOKABLE void setPort(const QString &port);
Q_INVOKABLE void setTag(const QString &tag);
Q_INVOKABLE void setPublicHost(const QString &host);
Q_INVOKABLE void setTransportMode(const QString &mode);
Q_INVOKABLE QString getTransportMode() const;
Q_INVOKABLE QString getTlsDomain() const;
Q_INVOKABLE QString getPublicHost() const;
Q_INVOKABLE void setTlsDomain(const QString &domain);
Q_INVOKABLE void setWorkersMode(const QString &mode);
Q_INVOKABLE void setWorkers(const QString &workers);
Q_INVOKABLE void setNatEnabled(bool enabled);
Q_INVOKABLE void setNatInternalIp(const QString &ip);
Q_INVOKABLE void setNatExternalIp(const QString &ip);
Q_INVOKABLE void addAdditionalSecret();
Q_INVOKABLE void removeAdditionalSecret(int idx);
Q_INVOKABLE QString generateQrCode(const QString &text);
Q_INVOKABLE void setEnabled(bool enabled);
Q_INVOKABLE void setMaskEnabled(bool enabled);
Q_INVOKABLE void setUseMiddleProxy(bool enabled);
Q_INVOKABLE void setTlsEmulation(bool enabled);
Q_INVOKABLE void setUserName(const QString &name);
Q_INVOKABLE QString defaultTlsDomain() const;
Q_INVOKABLE QString defaultPort() const;
Q_INVOKABLE QString defaultWorkers() const;
Q_INVOKABLE int maxWorkers() const;
Q_INVOKABLE QString transportModeStandard() const;
Q_INVOKABLE QString transportModeFakeTLS() const;
Q_INVOKABLE QString workersModeAuto() const;
Q_INVOKABLE QString workersModeManual() const;
protected:
QHash<int, QByteArray> roleNames() const override;
private:
static void applyDefaults(amnezia::TelemtProtocolConfig &c);
amnezia::DockerContainer m_container = amnezia::DockerContainer::None;
QJsonObject m_fullConfig;
amnezia::TelemtProtocolConfig m_protocolConfig;
};
#endif // TELEMTCONFIGMODEL_H

View File

@@ -1,127 +0,0 @@
#include "mtproxy_public_host_input.h"
#include <QRegularExpression>
namespace {
bool ipv4OctetTokenOk(const QString &s) {
static const QRegularExpression re(QStringLiteral(R"(^\d{1,3}$)"));
if (!re.match(s).hasMatch()) {
return false;
}
bool ok = false;
const int n = s.toInt(&ok);
return ok && n >= 0 && n <= 255;
}
// Reject labels like "312edweqwe" (digits >255 then letters).
bool labelHasInvalidOctetLikePrefixBeforeLetters(const QString &label) {
static const QRegularExpression re(QStringLiteral(R"(^(\d+)([a-zA-Z].*)$)"));
const QRegularExpressionMatch m = re.match(label);
if (!m.hasMatch()) {
return false;
}
const QString digits = m.captured(1);
if (digits.length() > 3) {
return true;
}
bool ok = false;
const int n = digits.toInt(&ok);
if (!ok) {
return true;
}
if (n > 255) {
return true;
}
// Do not restrict n≤255 + letters here (e.g. "123mlkjh.example.com"); four-segment IPv4+junk is handled below.
return false;
}
// "123.123wqqweqweqweqwe" — first label is a real octet, second looks like an octet glued to letters (not "123.45").
bool looksLikeTwoSegmentOctetThenDigitLetterGlue(const QString &text) {
const QStringList parts = text.split(QLatin1Char('.'), Qt::KeepEmptyParts);
if (parts.size() != 2) {
return false;
}
if (!ipv4OctetTokenOk(parts.at(0))) {
return false;
}
const QString &p1 = parts.at(1);
static const QRegularExpression digitThenLetter(QStringLiteral(R"(^\d+[a-zA-Z])"));
if (!digitThenLetter.match(p1).hasMatch()) {
return false;
}
return !ipv4OctetTokenOk(p1);
}
// "a.b.c.djunk" where first three parts are pure octets and last part has digits then letters (e.g. "123wdqweqweqwe").
bool looksLikeFourOctetIpv4WithGarbageInLastSegment(const QString &text) {
const QStringList parts = text.split(QLatin1Char('.'), Qt::KeepEmptyParts);
if (parts.size() != 4) {
return false;
}
for (int i = 0; i < 3; ++i) {
if (!ipv4OctetTokenOk(parts.at(i))) {
return false;
}
}
static const QRegularExpression digitThenLetter(QStringLiteral(R"(^\d+[a-zA-Z])"));
return digitThenLetter.match(parts.at(3)).hasMatch();
}
bool hostLabelsRejectBrokenDigitLetterMix(const QString &text) {
if (looksLikeTwoSegmentOctetThenDigitLetterGlue(text)) {
return false;
}
if (looksLikeFourOctetIpv4WithGarbageInLastSegment(text)) {
return false;
}
const QStringList parts = text.split(QLatin1Char('.'), Qt::KeepEmptyParts);
for (const QString &part: parts) {
if (labelHasInvalidOctetLikePrefixBeforeLetters(part)) {
return false;
}
}
return true;
}
} // namespace
bool mtproxyPublicHostInputAllowed(const QString &text) {
if (text.length() > 253) {
return false;
}
static const QRegularExpression allowed(QStringLiteral(R"(^[a-zA-Z0-9.:\-]*$)"));
if (!allowed.match(text).hasMatch()) {
return false;
}
static const QRegularExpression onlyDigits(QStringLiteral(R"(^\d+$)"));
if (onlyDigits.match(text).hasMatch() && text.length() > 3) {
return false;
}
static const QRegularExpression onlyDigitDot(QStringLiteral(R"(^[0-9.]+$)"));
if (!text.isEmpty() && onlyDigitDot.match(text).hasMatch()) {
static const QRegularExpression ipv4Partial(QStringLiteral(R"(^(\d{1,3}\.){0,3}\d{0,3}$)"));
return ipv4Partial.match(text).hasMatch();
}
if (text.contains(QLatin1Char(':'))) {
static const QRegularExpression ipv6Chars(QStringLiteral(R"(^[0-9a-fA-F:.]*$)"));
if (!ipv6Chars.match(text).hasMatch()) {
return false;
}
if (text.size() > 45) {
return false;
}
}
if (!hostLabelsRejectBrokenDigitLetterMix(text)) {
return false;
}
return true;
}
PublicHostInputValidator::PublicHostInputValidator(QObject *parent) : QValidator(parent) {}
QValidator::State PublicHostInputValidator::validate(QString &input, int &pos) const {
Q_UNUSED(pos)
return mtproxyPublicHostInputAllowed(input) ? Acceptable : Invalid;
}

View File

@@ -1,20 +0,0 @@
#ifndef MTPROXY_PUBLIC_HOST_INPUT_H
#define MTPROXY_PUBLIC_HOST_INPUT_H
#include <QString>
#include <QValidator>
/// Shared rules for public host field (IPv4 dotted partial, IPv6 hex, FQDN ASCII).
bool mtproxyPublicHostInputAllowed(const QString &text);
class PublicHostInputValidator : public QValidator {
Q_OBJECT
public:
explicit PublicHostInputValidator(QObject *parent = nullptr);
QValidator::State validate(QString &input, int &pos) const override;
};
#endif

View File

@@ -45,12 +45,6 @@ ListViewType {
PageController.goToPage(PageEnum.PageProtocolRaw)
} else if (isDns) {
PageController.goToPage(PageEnum.PageServiceDnsSettings)
} else if (isMtProxy) {
MtProxyConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceMtProxySettings)
} else if (isTelemt) {
TelemtConfigModel.updateModel(config)
PageController.goToPage(PageEnum.PageServiceTelemtSettings)
} else {
InstallController.updateProtocols(ServersUiController.getServerId(ServersUiController.processedServerIndex), containerIndex)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)

View File

@@ -31,9 +31,6 @@ ListViewType {
function triggerCurrentItem() {
var item = root.itemAtIndex(selectedIndex)
if (!item) {
return
}
item.selectable.clicked()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -132,11 +132,9 @@ PageType {
onInstallationErrorOccurred(message)
}
function onUpdateContainerFinished(message, closePage) {
function onUpdateContainerFinished(message) {
PageController.showNotificationMessage(message)
if (closePage) {
PageController.closePage()
}
PageController.closePage()
}
function onCachedProfileCleared(message) {

View File

@@ -78,8 +78,6 @@
<file>Pages2/PageProtocolWireGuardSettings.qml</file>
<file>Pages2/PageProtocolXraySettings.qml</file>
<file>Pages2/PageServiceDnsSettings.qml</file>
<file>Pages2/PageServiceMtProxySettings.qml</file>
<file>Pages2/PageServiceTelemtSettings.qml</file>
<file>Pages2/PageServiceSftpSettings.qml</file>
<file>Pages2/PageServiceSocksProxySettings.qml</file>
<file>Pages2/PageServiceTorWebsiteSettings.qml</file>