mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 05c7c57ad2 | |||
| ef712b7054 | |||
| c22f9ff08a | |||
| 04fb1825d5 | |||
| 4f8f873682 | |||
| 9fe75c6120 | |||
| bb7e8f46cb | |||
| 5db0c281ee | |||
| aac9bfcea6 | |||
| e6ee9085a2 | |||
| d62ade58a5 | |||
| d8020878d5 | |||
| b027fff103 | |||
| a0c06048cd | |||
| 53746f2f66 | |||
| 2649dba4ad | |||
| 6a1e3c07b1 | |||
| a365eff76f | |||
| 8f9acd9367 | |||
| 53fdf5f70d | |||
| 9be13ea465 | |||
| 871aced1d1 | |||
| 2254bfc128 | |||
| b71dcb8dd0 | |||
| 33d1518fd2 | |||
| ee5344a4ea | |||
| 9faabe9e7d |
+1
-1
Submodule client/3rd-prebuilt updated: c969f28b84...eb43e90f38
@@ -0,0 +1,2 @@
|
|||||||
|
*.user
|
||||||
|
build/
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
project(QJsonStruct LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
option(BUILD_TESTING ON)
|
||||||
|
|
||||||
|
include(QJsonStruct.cmake)
|
||||||
|
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
include(CTest)
|
||||||
|
add_subdirectory(test)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Qv2ray Workgroup
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QList>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
enum class QJsonIOPathType
|
||||||
|
{
|
||||||
|
JSONIO_MODE_ARRAY,
|
||||||
|
JSONIO_MODE_OBJECT
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef QPair<QString, QJsonIOPathType> QJsonIONodeType;
|
||||||
|
|
||||||
|
struct QJsonIOPath : QList<QJsonIONodeType>
|
||||||
|
{
|
||||||
|
template<typename type1, typename type2, typename... types>
|
||||||
|
QJsonIOPath(const type1 t1, const type2 t2, const types... ts)
|
||||||
|
{
|
||||||
|
AppendPath(t1);
|
||||||
|
AppendPath(t2);
|
||||||
|
(AppendPath(ts), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendPath(size_t index)
|
||||||
|
{
|
||||||
|
append({ QString::number(index), QJsonIOPathType::JSONIO_MODE_ARRAY });
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendPath(const QString &key)
|
||||||
|
{
|
||||||
|
append({ key, QJsonIOPathType::JSONIO_MODE_OBJECT });
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath &operator<<(const t &str)
|
||||||
|
{
|
||||||
|
AppendPath(str);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath &operator+=(const t &val)
|
||||||
|
{
|
||||||
|
AppendPath(val);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonIOPath &operator<<(const QJsonIOPath &other)
|
||||||
|
{
|
||||||
|
for (const auto &x : other)
|
||||||
|
this->append(x);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath &operator<<(const t &val) const
|
||||||
|
{
|
||||||
|
auto _new = *this;
|
||||||
|
return _new << val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath operator+(const t &val) const
|
||||||
|
{
|
||||||
|
auto _new = *this;
|
||||||
|
return _new << val;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonIOPath operator+(const QJsonIOPath &other) const
|
||||||
|
{
|
||||||
|
auto _new = *this;
|
||||||
|
for (const auto &x : other)
|
||||||
|
_new.append(x);
|
||||||
|
return _new;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QJsonIO
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const static inline QJsonValue Null = QJsonValue::Null;
|
||||||
|
const static inline QJsonValue Undefined = QJsonValue::Undefined;
|
||||||
|
|
||||||
|
template<typename current_key_type, typename... t_other_types>
|
||||||
|
static QJsonValue GetValue(const QJsonValue &parent, const current_key_type ¤t, const t_other_types &...other)
|
||||||
|
{
|
||||||
|
if constexpr (sizeof...(t_other_types) == 0)
|
||||||
|
if constexpr (std::is_integral_v<current_key_type>)
|
||||||
|
return parent.toArray()[current];
|
||||||
|
else
|
||||||
|
return parent.toObject()[current];
|
||||||
|
else if constexpr (std::is_integral_v<current_key_type>)
|
||||||
|
return GetValue(parent.toArray()[current], other...);
|
||||||
|
else
|
||||||
|
return GetValue(parent.toObject()[current], other...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... key_types_t>
|
||||||
|
static QJsonValue GetValue(QJsonValue value, const std::tuple<key_types_t...> &keys, const QJsonValue &defaultValue = Undefined)
|
||||||
|
{
|
||||||
|
std::apply([&](auto &&...args) { ((value = value[args]), ...); }, keys);
|
||||||
|
return value.isUndefined() ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename parent_type, typename t_value_type, typename current_key_type, typename... t_other_key_types>
|
||||||
|
static void SetValue(parent_type &parent, const t_value_type &val, const current_key_type ¤t, const t_other_key_types &...other)
|
||||||
|
{
|
||||||
|
// If current parent is an array, increase its size to fit the "key"
|
||||||
|
if constexpr (std::is_integral_v<current_key_type>)
|
||||||
|
for (auto i = parent.size(); i <= current; i++)
|
||||||
|
parent.insert(i, {});
|
||||||
|
|
||||||
|
// If the t_other_key_types has nothing....
|
||||||
|
// Means we have reached the end of recursion.
|
||||||
|
if constexpr (sizeof...(t_other_key_types) == 0)
|
||||||
|
parent[current] = val;
|
||||||
|
else if constexpr (std::is_integral_v<typename std::tuple_element_t<0, std::tuple<t_other_key_types...>>>)
|
||||||
|
{
|
||||||
|
// Means we still have many keys
|
||||||
|
// So this element is an array.
|
||||||
|
auto _array = parent[current].toArray();
|
||||||
|
SetValue(_array, val, other...);
|
||||||
|
parent[current] = _array;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto _object = parent[current].toObject();
|
||||||
|
SetValue(_object, val, other...);
|
||||||
|
parent[current] = _object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static QJsonValue GetValue(const QJsonValue &parent, const QJsonIOPath &path, const QJsonValue &defaultValue = QJsonIO::Undefined)
|
||||||
|
{
|
||||||
|
QJsonValue val = parent;
|
||||||
|
for (const auto &[k, t] : path)
|
||||||
|
{
|
||||||
|
if (t == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||||
|
val = val.toArray()[k.toInt()];
|
||||||
|
else
|
||||||
|
val = val.toObject()[k];
|
||||||
|
}
|
||||||
|
return val.isUndefined() ? defaultValue : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename parent_type, typename value_type>
|
||||||
|
static void SetValue(parent_type &parent, const value_type &t, const QJsonIOPath &path)
|
||||||
|
{
|
||||||
|
QList<std::tuple<QString, QJsonIOPathType, QJsonValue>> _stack;
|
||||||
|
QJsonValue lastNode = parent;
|
||||||
|
for (const auto &[key, type] : path)
|
||||||
|
{
|
||||||
|
_stack.prepend({ key, type, lastNode });
|
||||||
|
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||||
|
lastNode = lastNode.toArray().at(key.toInt());
|
||||||
|
else
|
||||||
|
lastNode = lastNode.toObject()[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
lastNode = t;
|
||||||
|
|
||||||
|
for (const auto &[key, type, node] : _stack)
|
||||||
|
{
|
||||||
|
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||||
|
{
|
||||||
|
const auto index = key.toInt();
|
||||||
|
auto nodeArray = node.toArray();
|
||||||
|
for (auto i = nodeArray.size(); i <= index; i++)
|
||||||
|
nodeArray.insert(i, {});
|
||||||
|
nodeArray[index] = lastNode;
|
||||||
|
lastNode = nodeArray;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto nodeObject = node.toObject();
|
||||||
|
nodeObject[key] = lastNode;
|
||||||
|
lastNode = nodeObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<parent_type, QJsonObject>)
|
||||||
|
parent = lastNode.toObject();
|
||||||
|
else if constexpr (std::is_same_v<parent_type, QJsonArray>)
|
||||||
|
parent = lastNode.toArray();
|
||||||
|
else
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|
||||||
|
set(QJSONSTRUCT_SOURCES
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/QJsonStruct.hpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/QJsonIO.hpp)
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "macroexpansion.hpp"
|
||||||
|
|
||||||
|
#ifndef _X
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// macro to define an operator==
|
||||||
|
#define ___JSONSTRUCT_DEFAULT_COMPARE_IMPL(x) (this->x == ___another___instance__.x) &&
|
||||||
|
#define JSONSTRUCT_COMPARE(CLASS, ...) \
|
||||||
|
bool operator==(const CLASS &___another___instance__) const \
|
||||||
|
{ \
|
||||||
|
return FOR_EACH(___JSONSTRUCT_DEFAULT_COMPARE_IMPL, __VA_ARGS__) true; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// Load JSON IMPL
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL(name) name::loadJson(___json_object_);
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC(name) ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name) \
|
||||||
|
if (___json_object_.toObject().contains(#name)) \
|
||||||
|
{ \
|
||||||
|
JsonStructHelper::Deserialize(this->name, ___json_object_.toObject()[#name]); \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
this->name = ___qjsonstruct_default_check.name; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// To JSON IMPL
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_F_FUNC(name) \
|
||||||
|
if (!(___qjsonstruct_default_check.name == this->name)) \
|
||||||
|
{ \
|
||||||
|
___json_object_.insert(#name, JsonStructHelper::Serialize(name)); \
|
||||||
|
}
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_A_FUNC(name) ___json_object_.insert(#name, JsonStructHelper::Serialize(name));
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL(name) JsonStructHelper::MergeJson(___json_object_, name::toJson());
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// Load JSON Wrapper
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_EXTRACT_B_F(name_option) ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_##name_option
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// To JSON Wrapper
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||||
|
#define ___SERIALIZE_TO_JSON_EXTRACT_B_F(name_option) ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_##name_option
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
#define JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, ...) \
|
||||||
|
void loadJson(const QJsonValue &___json_object_) \
|
||||||
|
{ \
|
||||||
|
___class_type_ ___qjsonstruct_default_check; \
|
||||||
|
FOREACH_CALL_FUNC(___DESERIALIZE_FROM_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] const QJsonObject toJson() const \
|
||||||
|
{ \
|
||||||
|
___class_type_ ___qjsonstruct_default_check; \
|
||||||
|
QJsonObject ___json_object_; \
|
||||||
|
FOREACH_CALL_FUNC(___SERIALIZE_TO_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||||
|
return ___json_object_; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define JSONSTRUCT_REGISTER(___class_type_, ...) \
|
||||||
|
JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, __VA_ARGS__); \
|
||||||
|
[[nodiscard]] static auto fromJson(const QJsonValue &___json_object_) \
|
||||||
|
{ \
|
||||||
|
___class_type_ _t; \
|
||||||
|
_t.loadJson(___json_object_); \
|
||||||
|
return _t; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(type, convert_func) \
|
||||||
|
static void Deserialize(type &t, const QJsonValue &d) \
|
||||||
|
{ \
|
||||||
|
t = d.convert_func; \
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonStructHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void MergeJson(QJsonObject &mergeTo, const QJsonObject &mergeIn)
|
||||||
|
{
|
||||||
|
for (const auto &key : mergeIn.keys())
|
||||||
|
mergeTo[key] = mergeIn.value(key);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
template<typename T>
|
||||||
|
static void Deserialize(T &t, const QJsonValue &d)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_enum_v<T>)
|
||||||
|
t = (T) d.toInt();
|
||||||
|
else if constexpr (std::is_same_v<T, QJsonObject>)
|
||||||
|
t = d.toObject();
|
||||||
|
else if constexpr (std::is_same_v<T, QJsonArray>)
|
||||||
|
t = d.toArray();
|
||||||
|
else
|
||||||
|
t.loadJson(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QString, toString());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QChar, toVariant().toChar());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::string, toString().toStdString());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::wstring, toString().toStdWString());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(bool, toBool());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(double, toDouble());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(float, toVariant().toFloat());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(int, toInt());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long, toVariant().toLongLong());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long long, toVariant().toLongLong());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned int, toVariant().toUInt());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long, toVariant().toULongLong());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long long, toVariant().toULongLong());
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static void Deserialize(QList<T> &t, const QJsonValue &d)
|
||||||
|
{
|
||||||
|
t.clear();
|
||||||
|
for (const auto &val : d.toArray())
|
||||||
|
{
|
||||||
|
T data;
|
||||||
|
Deserialize(data, val);
|
||||||
|
t.push_back(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TKey, typename TValue>
|
||||||
|
static void Deserialize(QMap<TKey, TValue> &t, const QJsonValue &d)
|
||||||
|
{
|
||||||
|
t.clear();
|
||||||
|
const auto &jsonObject = d.toObject();
|
||||||
|
TKey keyVal;
|
||||||
|
TValue valueVal;
|
||||||
|
for (const auto &key : jsonObject.keys())
|
||||||
|
{
|
||||||
|
Deserialize(keyVal, key);
|
||||||
|
Deserialize(valueVal, jsonObject.value(key));
|
||||||
|
t.insert(keyVal, valueVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================== Store Json Data ===========================
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static QJsonValue Serialize(const T &t)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_enum_v<T>)
|
||||||
|
return (int) t;
|
||||||
|
else if constexpr (std::is_same_v<T, QJsonObject> || std::is_same_v<T, QJsonArray>)
|
||||||
|
return t;
|
||||||
|
else
|
||||||
|
return t.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define pure_func(x) (x)
|
||||||
|
#define ___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(type) \
|
||||||
|
static QJsonValue Serialize(const type &t) \
|
||||||
|
{ \
|
||||||
|
return QJsonValue(t); \
|
||||||
|
}
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(int);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(bool);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonArray);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonObject);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QString);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(long long);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(float);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(double);
|
||||||
|
|
||||||
|
#define ___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(type, func) \
|
||||||
|
static QJsonValue Serialize(const type &t) \
|
||||||
|
{ \
|
||||||
|
return QJsonValue::fromVariant(func); \
|
||||||
|
}
|
||||||
|
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::string, QString::fromStdString(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::wstring, QString::fromStdWString(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(long, QVariant::fromValue<long>(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned int, QVariant::fromValue<unsigned int>(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long, QVariant::fromValue<unsigned long>(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long long, QVariant::fromValue<unsigned long long>(t))
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
static QJsonValue Serialize(const QMap<QString, TValue> &t)
|
||||||
|
{
|
||||||
|
QJsonObject mapObject;
|
||||||
|
for (const auto &key : t.keys())
|
||||||
|
{
|
||||||
|
auto valueVal = Serialize(t.value(key));
|
||||||
|
mapObject.insert(key, valueVal);
|
||||||
|
}
|
||||||
|
return mapObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static QJsonValue Serialize(const QList<T> &t)
|
||||||
|
{
|
||||||
|
QJsonArray listObject;
|
||||||
|
for (const auto &item : t)
|
||||||
|
{
|
||||||
|
listObject.push_back(Serialize(item));
|
||||||
|
}
|
||||||
|
return listObject;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
|
||||||
|
#define CONCATENATE2(arg1, arg2) arg1##arg2
|
||||||
|
#define CONCATENATE(x, y) x##y
|
||||||
|
|
||||||
|
#define EXPAND(...) __VA_ARGS__
|
||||||
|
#define FOR_EACH_1(what, x, ...) what(x)
|
||||||
|
#define FOR_EACH_2(what, x, ...) what(x) EXPAND(FOR_EACH_1(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_3(what, x, ...) what(x) EXPAND(FOR_EACH_2(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_4(what, x, ...) what(x) EXPAND(FOR_EACH_3(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_5(what, x, ...) what(x) EXPAND(FOR_EACH_4(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_6(what, x, ...) what(x) EXPAND(FOR_EACH_5(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_7(what, x, ...) what(x) EXPAND(FOR_EACH_6(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_8(what, x, ...) what(x) EXPAND(FOR_EACH_7(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_9(what, x, ...) what(x) EXPAND(FOR_EACH_8(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_10(what, x, ...) what(x) EXPAND(FOR_EACH_9(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_11(what, x, ...) what(x) EXPAND(FOR_EACH_10(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_12(what, x, ...) what(x) EXPAND(FOR_EACH_11(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_13(what, x, ...) what(x) EXPAND(FOR_EACH_12(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_14(what, x, ...) what(x) EXPAND(FOR_EACH_13(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_15(what, x, ...) what(x) EXPAND(FOR_EACH_14(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_16(what, x, ...) what(x) EXPAND(FOR_EACH_15(what, __VA_ARGS__))
|
||||||
|
|
||||||
|
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
|
||||||
|
#define FOR_EACH_NARG_(...) EXPAND(FOR_EACH_ARG_N(__VA_ARGS__))
|
||||||
|
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
|
||||||
|
#define FOR_EACH_RSEQ_N() 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||||
|
|
||||||
|
#define FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||||
|
#define FOREACH_CALL_FUNC(func, ...) FOR_EACH(func, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Bad hack ==========================================================================================================================
|
||||||
|
#define _2X_FOR_EACH_1(what, x, ...) what(x)
|
||||||
|
#define _2X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_1(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_2(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_3(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_4(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_5(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_6(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_7(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_8(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_9(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_10(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_11(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_12(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_13(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_14(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_15(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_2X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH(what, ...) _2X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||||
|
#define FOREACH_CALL_FUNC_2(func, ...) _2X_FOR_EACH(func, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Bad hack ==========================================================================================================================
|
||||||
|
#define _3X_FOR_EACH_1(what, x, ...) what(x)
|
||||||
|
#define _3X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_1(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_2(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_3(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_4(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_5(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_6(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_7(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_8(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_9(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_10(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_11(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_12(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_13(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_14(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_15(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_3X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH(what, ...) _3X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||||
|
#define FOREACH_CALL_FUNC_3(func, ...) _3X_FOR_EACH(func, __VA_ARGS__)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
function(QJSONSTRUCT_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||||
|
add_executable(${TEST_NAME} ${TEST_SOURCE} catch.hpp ${QJSONSTRUCT_SOURCES})
|
||||||
|
target_include_directories(${TEST_NAME}
|
||||||
|
PRIVATE
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||||
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
${TEST_NAME}
|
||||||
|
PRIVATE
|
||||||
|
Qt::Core
|
||||||
|
)
|
||||||
|
add_test(NAME QJSONSTRUCT_TEST_${TEST_NAME} COMMAND $<TARGET_FILE:${TEST_NAME}> -s)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
QJSONSTRUCT_ADD_TEST(serialization serialize/main.cpp)
|
||||||
|
#QJSONSTRUCT_ADD_TEST(serialize_strings serialize/strings.cpp)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
#ifndef _X
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct BaseStruct
|
||||||
|
{
|
||||||
|
QString baseStr;
|
||||||
|
int o;
|
||||||
|
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr, o))
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BaseStruct2
|
||||||
|
{
|
||||||
|
QString baseStr2;
|
||||||
|
int o2;
|
||||||
|
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr2, o2))
|
||||||
|
};
|
||||||
|
struct TestInnerStruct
|
||||||
|
: BaseStruct
|
||||||
|
, BaseStruct2
|
||||||
|
{
|
||||||
|
QJsonObject jobj;
|
||||||
|
QJsonArray jarray;
|
||||||
|
QString str;
|
||||||
|
JSONSTRUCT_REGISTER(TestInnerStruct, B(BaseStruct, BaseStruct2), F(str, jobj, jarray))
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JsonIOTest
|
||||||
|
{
|
||||||
|
QString str;
|
||||||
|
QList<int> listOfNumber;
|
||||||
|
QList<bool> listOfBool;
|
||||||
|
QList<QString> listOfString;
|
||||||
|
QList<QList<QString>> listOfListOfString;
|
||||||
|
|
||||||
|
QMap<QString, QString> map;
|
||||||
|
TestInnerStruct inner;
|
||||||
|
|
||||||
|
JSONSTRUCT_REGISTER(JsonIOTest, F(str, listOfNumber, listOfBool, listOfString, listOfListOfString, map, inner));
|
||||||
|
JsonIOTest(){};
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
|
||||||
|
struct SubData
|
||||||
|
{
|
||||||
|
QString subString;
|
||||||
|
JSONSTRUCT_REGISTER_TOJSON(subString)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ToJsonOnlyData
|
||||||
|
{
|
||||||
|
QString x;
|
||||||
|
int y;
|
||||||
|
int z;
|
||||||
|
QList<int> ints;
|
||||||
|
SubData sub;
|
||||||
|
QMap<QString, SubData> subs;
|
||||||
|
JSONSTRUCT_REGISTER_TOJSON(x, y, z, sub, ints, subs)
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
|||||||
|
#include "QJsonIO.hpp"
|
||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
#include "TestIO.hpp"
|
||||||
|
#include "TestOut.hpp"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
Q_UNUSED(argc)
|
||||||
|
Q_UNUSED(argv)
|
||||||
|
|
||||||
|
{
|
||||||
|
ToJsonOnlyData data;
|
||||||
|
data.x = "1string";
|
||||||
|
data.y = 2;
|
||||||
|
data.ints << 0;
|
||||||
|
data.ints << 100;
|
||||||
|
data.ints << 900;
|
||||||
|
data.sub.subString = "subs";
|
||||||
|
data.subs["subs-1"] = { "subs1-data" };
|
||||||
|
data.subs["subs-2"] = { "subs2-data" };
|
||||||
|
data.subs["subs-3"] = { "subs3-data" };
|
||||||
|
data.z = 3;
|
||||||
|
auto x = data.toJson();
|
||||||
|
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
{
|
||||||
|
auto f = JsonIOTest::fromJson( //
|
||||||
|
QJsonObject{
|
||||||
|
{ "inner", QJsonObject{ { "str", "innerString" }, //
|
||||||
|
{ "jobj", QJsonObject{ { "key", "value" } } }, //
|
||||||
|
{ "jarray", QJsonArray{ "array0", "array1", "array2" } }, //
|
||||||
|
{ "baseStr", "baseInnerString" } } }, //
|
||||||
|
{ "str", "data1" }, //
|
||||||
|
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||||
|
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||||
|
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||||
|
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||||
|
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||||
|
QJsonArray{ "1", "2" }, //
|
||||||
|
QJsonArray{ "1", "2", "3" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||||
|
});
|
||||||
|
auto x = f.toJson();
|
||||||
|
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QJsonObject obj{
|
||||||
|
{ "inner", QJsonObject{ { "str", "innerString" }, { "baseStr", "baseInnerString" } } }, //
|
||||||
|
{ "str", "data1" }, //
|
||||||
|
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||||
|
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||||
|
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||||
|
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||||
|
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||||
|
QJsonArray{ "1", "2" }, //
|
||||||
|
QJsonArray{ "1", "2", "3" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||||
|
};
|
||||||
|
auto y = QJsonIO::GetValue(obj, std::tuple{ "listOfListOfString", 2 });
|
||||||
|
y.toObject();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
const static inline auto INT_TEST_MAX = std::numeric_limits<int>::max() - 1;
|
||||||
|
const static inline auto INT_TEST_MIN = -(std::numeric_limits<int>::min() + 1);
|
||||||
|
|
||||||
|
#define SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance) \
|
||||||
|
class CLASS \
|
||||||
|
{ \
|
||||||
|
public: \
|
||||||
|
TYPE field = defaultvalue; \
|
||||||
|
JSONSTRUCT_REGISTER(CLASS, existance(field)); \
|
||||||
|
};
|
||||||
|
|
||||||
|
// SINGLE_ELEMENT_REQUIRE( CLASS_NAME , TYPE , FIELD , DEFAULT_VALUE , SET VALUE , CHECK VALUE )
|
||||||
|
#define SINGLE_ELEMENT_REQUIRE(CLASS, TYPE, field, defaultvalue, value, checkvalue, existance) \
|
||||||
|
SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance); \
|
||||||
|
CLASS CLASS##_class; \
|
||||||
|
CLASS##_class.field = value; \
|
||||||
|
REQUIRE(CLASS##_class.toJson()[#field] == checkvalue);
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
SCENARIO("Test Serialization", "[Serialize]")
|
||||||
|
{
|
||||||
|
GIVEN("Single Element")
|
||||||
|
{
|
||||||
|
const static QList<QString> defaultList{ "entry 1", "entry 2" };
|
||||||
|
const static QMap<QString, QString> defaultMap{ { "key1", "value1" }, { "key2", "value2" } };
|
||||||
|
typedef QMap<QString, QString> QStringQStringMap;
|
||||||
|
|
||||||
|
WHEN("Serialize a single element")
|
||||||
|
{
|
||||||
|
const static QStringQStringMap setValueMap{ { "newkey1", "newvalue1" } };
|
||||||
|
const static QJsonObject setValueJson{ { "newkey1", QJsonValue{ "newvalue1" } } };
|
||||||
|
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_Empty, QString, a, "empty", "", "", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest, QString, a, "empty", "Some QString", "Some QString", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_WithQoutes, QString, a, "empty", "\"", "\"", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_zint, int, a, -10, 0, 0, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_nint, int, a, -10, 1, 1, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_pint, int, a, -10, -1, -1, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_pmint, int, a, -1, INT_TEST_MAX, INT_TEST_MAX, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_zmint, int, a, -1, INT_TEST_MIN, INT_TEST_MIN, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_zuint, uint, a, -10, 0, 0, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_puint, uint, a, -10, 1, 1, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(BoolTest_True, bool, a, false, true, true, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(BoolTest_False, bool, a, true, false, false, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(StdStringTest, string, a, "def", "std::string _test", "std::string _test", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QListTest, QList<QString>, a, defaultList, { "newEntry" }, QJsonArray{ "newEntry" }, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QMapTest, QStringQStringMap, a, defaultMap, {}, QJsonObject{}, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QMapValueTest, QStringQStringMap, a, defaultMap, setValueMap, setValueJson, F);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Serialize a default value")
|
||||||
|
{
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", QJsonValue::Undefined, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, QJsonValue::Undefined, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, QJsonValue::Undefined, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, QJsonValue::Undefined, F);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Serialize a force existance default value")
|
||||||
|
{
|
||||||
|
const static QJsonArray defaultListJson{ "entry 1", "entry 2" };
|
||||||
|
const static QJsonObject defaultMapJson{ { "key1", "value1" }, { "key2", "value2" } };
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", "defaultvalue", A);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, 12345, A);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, defaultListJson, A);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, defaultMapJson, A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN("Multiple Simple Elements")
|
||||||
|
{
|
||||||
|
WHEN("Can Omit Default Value")
|
||||||
|
{
|
||||||
|
class MultipleNonDefaultElementTestClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString astring;
|
||||||
|
int integer = 0;
|
||||||
|
double adouble = 0.0;
|
||||||
|
QList<QString> myList;
|
||||||
|
JSONSTRUCT_REGISTER(MultipleNonDefaultElementTestClass, F(astring, integer, adouble, myList))
|
||||||
|
};
|
||||||
|
MultipleNonDefaultElementTestClass instance;
|
||||||
|
const auto json = instance.toJson();
|
||||||
|
REQUIRE(json["astring"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["integer"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["adouble"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["myList"] == QJsonValue::Undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Forcing Existance")
|
||||||
|
{
|
||||||
|
class MultipleNonDefaultExistanceElementTestClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString astring;
|
||||||
|
int integer = 0;
|
||||||
|
double adouble = 0.0;
|
||||||
|
QList<QString> myList;
|
||||||
|
JSONSTRUCT_REGISTER(MultipleNonDefaultExistanceElementTestClass, A(astring, integer, adouble, myList))
|
||||||
|
};
|
||||||
|
MultipleNonDefaultExistanceElementTestClass instance;
|
||||||
|
const auto json = instance.toJson();
|
||||||
|
REQUIRE(json["astring"] == "");
|
||||||
|
REQUIRE(json["integer"] == 0);
|
||||||
|
REQUIRE(json["adouble"] == 0.0);
|
||||||
|
REQUIRE(json["myList"] == QJsonArray{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN("Nested Elements")
|
||||||
|
{
|
||||||
|
WHEN("Can Omit Default Value")
|
||||||
|
{
|
||||||
|
class Parent
|
||||||
|
{
|
||||||
|
class NestedChild
|
||||||
|
{
|
||||||
|
class NestedChild2
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int childChildInt = 13579;
|
||||||
|
JSONSTRUCT_COMPARE(NestedChild2, childChildInt)
|
||||||
|
JSONSTRUCT_REGISTER(NestedChild2, F(childChildInt))
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
int childInt = 54321;
|
||||||
|
QString childQString = "A QString";
|
||||||
|
NestedChild2 anotherChild;
|
||||||
|
JSONSTRUCT_COMPARE(NestedChild, childInt, childQString, anotherChild)
|
||||||
|
JSONSTRUCT_REGISTER(NestedChild, F(childInt, childQString, anotherChild))
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
int parentInt = 12345;
|
||||||
|
NestedChild child;
|
||||||
|
JSONSTRUCT_REGISTER(Parent, F(parentInt, child))
|
||||||
|
};
|
||||||
|
|
||||||
|
WHEN("Omitted whole child element")
|
||||||
|
{
|
||||||
|
Parent parent;
|
||||||
|
const auto json = parent.toJson();
|
||||||
|
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"] == QJsonValue::Undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Omitted one element in the child")
|
||||||
|
{
|
||||||
|
const auto childJson = QJsonObject{ { "childInt", 1314 } };
|
||||||
|
Parent parent;
|
||||||
|
parent.child.childInt = 1314;
|
||||||
|
const auto json = parent.toJson();
|
||||||
|
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"] == childJson);
|
||||||
|
REQUIRE(json["child"]["childInt"] == 1314);
|
||||||
|
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"]["child"]["anotherChild"] == QJsonValue::Undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Omitted one element in the child child")
|
||||||
|
{
|
||||||
|
Parent parent;
|
||||||
|
parent.child.childInt = 1314;
|
||||||
|
parent.child.anotherChild.childChildInt = 97531;
|
||||||
|
const auto json = parent.toJson();
|
||||||
|
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"]["childInt"] == 1314);
|
||||||
|
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||||
|
const QJsonObject childChild{ { "childChildInt", 97531 } };
|
||||||
|
REQUIRE(json["child"]["anotherChild"] == childChild);
|
||||||
|
REQUIRE(json["child"]["anotherChild"]["childChildInt"] == 97531);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -134,6 +134,8 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
@@ -151,7 +153,6 @@ include_directories(mozilla/models)
|
|||||||
|
|
||||||
if(NOT IOS)
|
if(NOT IOS)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
@@ -177,6 +178,14 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla sources
|
# Mozilla sources
|
||||||
@@ -193,7 +202,6 @@ endif()
|
|||||||
|
|
||||||
if(NOT IOS)
|
if(NOT IOS)
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_settings = std::shared_ptr<Settings>(new Settings);
|
m_settings = std::shared_ptr<Settings>(new Settings);
|
||||||
|
m_nam = new QNetworkAccessManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
AmneziaApplication::~AmneziaApplication()
|
AmneziaApplication::~AmneziaApplication()
|
||||||
@@ -150,7 +151,7 @@ void AmneziaApplication::init()
|
|||||||
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::openConnection);
|
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::closeConnection);
|
&ConnectionController::closeConnection);
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
@@ -350,6 +351,9 @@ void AmneziaApplication::initModels()
|
|||||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||||
|
|
||||||
|
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||||
|
|
||||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||||
@@ -362,10 +366,16 @@ void AmneziaApplication::initControllers()
|
|||||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||||
|
|
||||||
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](const QString &errorMessage) {
|
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this, [this](const QString &errorMessage) {
|
||||||
emit m_pageController->showErrorMessage(errorMessage);
|
emit m_pageController->showErrorMessage(errorMessage);
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this, [this](ErrorCode errorCode) {
|
||||||
|
emit m_pageController->showErrorMessage(errorCode);
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
|
||||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define AMNEZIA_APPLICATION_H
|
#define AMNEZIA_APPLICATION_H
|
||||||
|
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
#include "ui/models/protocols_model.h"
|
#include "ui/models/protocols_model.h"
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
#include "ui/models/services/sftpConfigModel.h"
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
|
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
@@ -75,6 +77,7 @@ public:
|
|||||||
bool parseCommands();
|
bool parseCommands();
|
||||||
|
|
||||||
QQmlApplicationEngine *qmlEngine() const;
|
QQmlApplicationEngine *qmlEngine() const;
|
||||||
|
QNetworkAccessManager *manager() { return m_nam; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void translationsUpdated();
|
void translationsUpdated();
|
||||||
@@ -112,6 +115,7 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||||
|
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||||
|
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QThread m_vpnConnectionThread;
|
QThread m_vpnConnectionThread;
|
||||||
@@ -128,6 +132,8 @@ private:
|
|||||||
QScopedPointer<SitesController> m_sitesController;
|
QScopedPointer<SitesController> m_sitesController;
|
||||||
QScopedPointer<SystemController> m_systemController;
|
QScopedPointer<SystemController> m_systemController;
|
||||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
|
|
||||||
|
QNetworkAccessManager *m_nam;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMNEZIA_APPLICATION_H
|
#endif // AMNEZIA_APPLICATION_H
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.mm
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,10 +63,14 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
|||||||
|
|
||||||
case DockerContainer::Xray: return { Proto::Xray };
|
case DockerContainer::Xray: return { Proto::Xray };
|
||||||
|
|
||||||
|
case DockerContainer::SSXray: return { Proto::SSXray };
|
||||||
|
|
||||||
case DockerContainer::Dns: return { Proto::Dns };
|
case DockerContainer::Dns: return { Proto::Dns };
|
||||||
|
|
||||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||||
|
|
||||||
|
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
||||||
|
|
||||||
default: return { defaultProtocol(container) };
|
default: return { defaultProtocol(container) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,10 +96,12 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
|||||||
{ DockerContainer::Awg, "AmneziaWG" },
|
{ DockerContainer::Awg, "AmneziaWG" },
|
||||||
{ DockerContainer::Xray, "XRay" },
|
{ DockerContainer::Xray, "XRay" },
|
||||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||||
|
{ DockerContainer::SSXray, "ShadowSocks"},
|
||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
||||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } };
|
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service") },
|
||||||
|
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
||||||
@@ -128,7 +134,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||||||
{ DockerContainer::Dns,
|
{ DockerContainer::Dns,
|
||||||
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
|
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
|
||||||
{ DockerContainer::Sftp,
|
{ DockerContainer::Sftp,
|
||||||
QObject::tr("Create a file vault on your server to securely store and transfer files.") } };
|
QObject::tr("Create a file vault on your server to securely store and transfer files.") },
|
||||||
|
{ DockerContainer::Socks5Proxy,
|
||||||
|
QObject::tr("") } };
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
||||||
@@ -237,7 +245,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
|
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
|
||||||
"You will be able to access it using\n FileZilla or other SFTP clients, "
|
"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"
|
"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.\" ") }
|
"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") }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,10 +266,12 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
|||||||
case DockerContainer::Awg: return Proto::Awg;
|
case DockerContainer::Awg: return Proto::Awg;
|
||||||
case DockerContainer::Xray: return Proto::Xray;
|
case DockerContainer::Xray: return Proto::Xray;
|
||||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||||
|
case DockerContainer::SSXray: return Proto::SSXray;
|
||||||
|
|
||||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||||
case DockerContainer::Dns: return Proto::Dns;
|
case DockerContainer::Dns: return Proto::Dns;
|
||||||
case DockerContainer::Sftp: return Proto::Sftp;
|
case DockerContainer::Sftp: return Proto::Sftp;
|
||||||
|
case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy;
|
||||||
default: return Proto::Any;
|
default: return Proto::Any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,6 +374,7 @@ bool ContainerProps::isShareable(DockerContainer container)
|
|||||||
case DockerContainer::TorWebSite: return false;
|
case DockerContainer::TorWebSite: return false;
|
||||||
case DockerContainer::Dns: return false;
|
case DockerContainer::Dns: return false;
|
||||||
case DockerContainer::Sftp: return false;
|
case DockerContainer::Sftp: return false;
|
||||||
|
case DockerContainer::Socks5Proxy: return false;
|
||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ namespace amnezia
|
|||||||
ShadowSocks,
|
ShadowSocks,
|
||||||
Ipsec,
|
Ipsec,
|
||||||
Xray,
|
Xray,
|
||||||
|
SSXray,
|
||||||
|
|
||||||
// non-vpn
|
// non-vpn
|
||||||
TorWebSite,
|
TorWebSite,
|
||||||
Dns,
|
Dns,
|
||||||
Sftp
|
Sftp,
|
||||||
|
Socks5Proxy
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(DockerContainer)
|
Q_ENUM_NS(DockerContainer)
|
||||||
} // namespace ContainerEnumNS
|
} // namespace ContainerEnumNS
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include "amnezia_application.h"
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -19,7 +21,10 @@ namespace
|
|||||||
constexpr char certificate[] = "certificate";
|
constexpr char certificate[] = "certificate";
|
||||||
constexpr char publicKey[] = "public_key";
|
constexpr char publicKey[] = "public_key";
|
||||||
constexpr char protocol[] = "protocol";
|
constexpr char protocol[] = "protocol";
|
||||||
|
|
||||||
constexpr char uuid[] = "installation_uuid";
|
constexpr char uuid[] = "installation_uuid";
|
||||||
|
constexpr char osVersion[] = "os_version";
|
||||||
|
constexpr char appVersion[] = "app_version";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,8 +32,7 @@ ApiController::ApiController(QObject *parent) : QObject(parent)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
|
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
|
||||||
QString &config)
|
|
||||||
{
|
{
|
||||||
if (protocol == configKey::cloak) {
|
if (protocol == configKey::cloak) {
|
||||||
config.replace("<key>", "<key>\n");
|
config.replace("<key>", "<key>\n");
|
||||||
@@ -36,6 +40,28 @@ void ApiController::processApiConfig(const QString &protocol, const ApiControlle
|
|||||||
return;
|
return;
|
||||||
} else if (protocol == configKey::awg) {
|
} else if (protocol == configKey::awg) {
|
||||||
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||||
|
auto serverConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||||
|
auto containers = serverConfig.value(config_key::containers).toArray();
|
||||||
|
if (containers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto container = containers.at(0).toObject();
|
||||||
|
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
||||||
|
auto containerConfig = container.value(containerName).toObject();
|
||||||
|
auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||||
|
containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount);
|
||||||
|
containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize);
|
||||||
|
containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize);
|
||||||
|
containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize);
|
||||||
|
containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize);
|
||||||
|
containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader);
|
||||||
|
containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader);
|
||||||
|
containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader);
|
||||||
|
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
|
||||||
|
container[containerName] = containerConfig;
|
||||||
|
containers.replace(0, container);
|
||||||
|
serverConfig[config_key::containers] = containers;
|
||||||
|
config = QString(QJsonDocument(serverConfig).toJson());
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -61,53 +87,52 @@ QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiCont
|
|||||||
} else if (protocol == configKey::awg) {
|
} else if (protocol == configKey::awg) {
|
||||||
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
|
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obj[configKey::osVersion] = QSysInfo::productType();
|
||||||
|
obj[configKey::appVersion] = QString(APP_VERSION);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ApiController::updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig)
|
void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
|
||||||
{
|
{
|
||||||
QFutureWatcher<ErrorCode> watcher;
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
QFuture<ErrorCode> future = QtConcurrent::run([this, &serverConfig, &installationUuid]() {
|
|
||||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||||
QNetworkAccessManager manager;
|
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(7000);
|
request.setTransferTimeout(7000);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setRawHeader("Authorization",
|
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||||
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
|
||||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||||
request.setUrl(endpoint);
|
request.setUrl(endpoint);
|
||||||
|
|
||||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||||
|
|
||||||
auto apiPayloadData = generateApiPayloadData(protocol);
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
auto apiPayload = fillApiPayload(protocol, apiPayloadData);
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
apiPayload[configKey::uuid] = installationUuid;
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|
||||||
QScopedPointer<QNetworkReply> reply;
|
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
|
||||||
reply.reset(manager.post(request, requestBody));
|
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QObject::connect(reply.get(), &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
QString contents = QString::fromUtf8(reply->readAll());
|
QString contents = QString::fromUtf8(reply->readAll());
|
||||||
auto data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
QString data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
||||||
|
|
||||||
data.replace("vpn://", "");
|
data.replace("vpn://", "");
|
||||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(),
|
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
|
||||||
|
|
||||||
if (ba.isEmpty()) {
|
if (ba.isEmpty()) {
|
||||||
return ErrorCode::ApiConfigDownloadError;
|
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ba_uncompressed = qUncompress(ba);
|
QByteArray ba_uncompressed = qUncompress(ba);
|
||||||
@@ -119,30 +144,37 @@ ErrorCode ApiController::updateServerConfigFromApi(const QString &installationUu
|
|||||||
processApiConfig(protocol, apiPayloadData, configStr);
|
processApiConfig(protocol, apiPayloadData, configStr);
|
||||||
|
|
||||||
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||||
|
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
||||||
serverConfig.insert(config_key::dns1, apiConfig.value(config_key::dns1));
|
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
||||||
serverConfig.insert(config_key::dns2, apiConfig.value(config_key::dns2));
|
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
||||||
serverConfig.insert(config_key::containers, apiConfig.value(config_key::containers));
|
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
||||||
serverConfig.insert(config_key::hostName, apiConfig.value(config_key::hostName));
|
|
||||||
|
|
||||||
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||||
serverConfig.insert(config_key::defaultContainer, defaultContainer);
|
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||||
|
|
||||||
|
emit configUpdated(true, serverConfig, serverIndex);
|
||||||
|
} else {
|
||||||
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
|
||||||
} else {
|
} else {
|
||||||
QString err = reply->errorString();
|
QString err = reply->errorString();
|
||||||
qDebug() << QString::fromUtf8(reply->readAll());
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
qDebug() << reply->error();
|
qDebug() << reply->error();
|
||||||
qDebug() << err;
|
qDebug() << err;
|
||||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||||
return ErrorCode::ApiConfigDownloadError;
|
emit errorOccurred(ErrorCode::ApiConfigDownloadError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrorCode::NoError;
|
|
||||||
|
reply->deleteLater();
|
||||||
});
|
});
|
||||||
|
|
||||||
QEventLoop wait;
|
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
||||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
||||||
watcher.setFuture(future);
|
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
|
||||||
wait.exec();
|
qDebug().noquote() << errors;
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
||||||
return watcher.result();
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@
|
|||||||
|
|
||||||
#include "configurators/openvpn_configurator.h"
|
#include "configurators/openvpn_configurator.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class ApiController : public QObject
|
class ApiController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -13,10 +17,15 @@ public:
|
|||||||
explicit ApiController(QObject *parent = nullptr);
|
explicit ApiController(QObject *parent = nullptr);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
ErrorCode updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig);
|
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void errorOccurred(ErrorCode errorCode);
|
||||||
|
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ApiPayloadData {
|
struct ApiPayloadData
|
||||||
|
{
|
||||||
OpenVpnConfigurator::ConnectionData certRequest;
|
OpenVpnConfigurator::ConnectionData certRequest;
|
||||||
|
|
||||||
QString wireGuardClientPrivKey;
|
QString wireGuardClientPrivKey;
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
|||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName);
|
QString runner = QString("sudo docker exec -i $CONTAINER_NAME sh %1 ").arg(fileName);
|
||||||
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName);
|
||||||
@@ -376,6 +376,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (container == DockerContainer::Socks5Proxy) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,11 +420,16 @@ ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentia
|
|||||||
|
|
||||||
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
{
|
{
|
||||||
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
|
QString dockerFilePath = amnezia::server::getDockerfileFolder(container) + "/Dockerfile";
|
||||||
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
QString scriptString = QString("sudo rm %1").arg(dockerFilePath);
|
||||||
|
ErrorCode errorCode = runScript(credentials, replaceVars(scriptString, genVarsForScript(credentials, container)));
|
||||||
|
if (errorCode)
|
||||||
|
return errorCode;
|
||||||
|
|
||||||
if (e)
|
errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),dockerFilePath);
|
||||||
return e;
|
|
||||||
|
if (errorCode)
|
||||||
|
return errorCode;
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -428,13 +437,13 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
e = runScript(credentials,
|
errorCode = runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||||
cbReadStdOut);
|
cbReadStdOut);
|
||||||
if (e)
|
if (errorCode)
|
||||||
return e;
|
return errorCode;
|
||||||
|
|
||||||
return e;
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||||
@@ -495,6 +504,19 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
|
|||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
|
if (container == DockerContainer::OpenVpn)
|
||||||
|
{
|
||||||
|
QFile file(":/server_scripts/openvpn/password_auth.sh");
|
||||||
|
file.open(QIODevice::ReadOnly);
|
||||||
|
QString scriptContent = QString(file.readAll());
|
||||||
|
const QString serverScriptPath = "/opt/amnezia/password_auth.sh";
|
||||||
|
|
||||||
|
uploadTextFileToContainer(container, credentials, scriptContent, serverScriptPath);
|
||||||
|
runScript(credentials,
|
||||||
|
replaceVars(QStringLiteral("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod +rx %1\"").arg(serverScriptPath),
|
||||||
|
genVarsForScript(credentials, container, config)));
|
||||||
|
}
|
||||||
|
|
||||||
return runScript(credentials,
|
return runScript(credentials,
|
||||||
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && "
|
replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && "
|
||||||
"/opt/amnezia/start.sh\"",
|
"/opt/amnezia/start.sh\"",
|
||||||
@@ -511,6 +533,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
||||||
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
|
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
|
||||||
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
||||||
|
const QJsonObject &socks5ProxyConfig = config.value(ProtocolProps::protoToString(Proto::Socks5Proxy)).toObject();
|
||||||
|
|
||||||
Vars vars;
|
Vars vars;
|
||||||
|
|
||||||
@@ -608,6 +631,14 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||||
|
|
||||||
|
// Socks5 proxy vars
|
||||||
|
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
||||||
|
auto username = socks5ProxyConfig.value(config_key:: userName).toString();
|
||||||
|
auto password = socks5ProxyConfig.value(config_key::password).toString();
|
||||||
|
QString socks5user = (!username.isEmpty() && !password.isEmpty()) ? QString("users %1:CL:%2").arg(username, password) : "";
|
||||||
|
vars.append({ { "$SOCKS5_USER", socks5user } });
|
||||||
|
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
||||||
|
|
||||||
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||||
@@ -686,6 +717,30 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
for (auto &port : fixedPorts) {
|
for (auto &port : fixedPorts) {
|
||||||
script = script.append("|:%1").arg(port);
|
script = script.append("|:%1").arg(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transportProto == "tcpandudp") {
|
||||||
|
QString tcpProtoScript = script;
|
||||||
|
QString udpProtoScript = script;
|
||||||
|
tcpProtoScript.append("' | grep -i tcp");
|
||||||
|
udpProtoScript.append("' | grep -i udp");
|
||||||
|
tcpProtoScript.append(" | grep LISTEN");
|
||||||
|
|
||||||
|
ErrorCode errorCode = runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCode = runScript(credentials, replaceVars(udpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stdOut.isEmpty()) {
|
||||||
|
return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||||
|
}
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
script = script.append("' | grep -i %1").arg(transportProto);
|
script = script.append("' | grep -i %1").arg(transportProto);
|
||||||
|
|
||||||
if (transportProto == "tcp") {
|
if (transportProto == "tcp") {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ QScopedPointer<ConfiguratorBase> VpnConfigurationsController::createConfigurator
|
|||||||
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(m_settings, m_serverController));
|
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(m_settings, m_serverController));
|
||||||
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(m_settings, m_serverController));
|
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(m_settings, m_serverController));
|
||||||
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
|
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::SSXray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
|
||||||
default: return QScopedPointer<ConfiguratorBase>();
|
default: return QScopedPointer<ConfiguratorBase>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ namespace amnezia
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace error_code_ns
|
||||||
|
{
|
||||||
|
Q_NAMESPACE
|
||||||
|
// TODO: change to enum class
|
||||||
enum ErrorCode {
|
enum ErrorCode {
|
||||||
// General error codes
|
// General error codes
|
||||||
NoError = 0,
|
NoError = 0,
|
||||||
@@ -99,6 +103,9 @@ namespace amnezia
|
|||||||
// Api errors
|
// Api errors
|
||||||
ApiConfigDownloadError = 1100,
|
ApiConfigDownloadError = 1100,
|
||||||
ApiConfigAlreadyAdded = 1101,
|
ApiConfigAlreadyAdded = 1101,
|
||||||
|
ApiConfigEmptyError = 1102,
|
||||||
|
ApiConfigTimeoutError = 1103,
|
||||||
|
ApiConfigSslError = 1104,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
@@ -108,6 +115,10 @@ namespace amnezia
|
|||||||
FatalError = 1204,
|
FatalError = 1204,
|
||||||
AbortError = 1205
|
AbortError = 1205
|
||||||
};
|
};
|
||||||
|
Q_ENUM_NS(ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
using ErrorCode = error_code_ns::ErrorCode;
|
||||||
|
|
||||||
} // namespace amnezia
|
} // namespace amnezia
|
||||||
|
|
||||||
|
|||||||
@@ -8,65 +8,68 @@ QString errorString(ErrorCode code) {
|
|||||||
switch (code) {
|
switch (code) {
|
||||||
|
|
||||||
// General error codes
|
// General error codes
|
||||||
case(NoError): errorMessage = QObject::tr("No error"); break;
|
case(ErrorCode::NoError): errorMessage = QObject::tr("No error"); break;
|
||||||
case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
||||||
case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
||||||
case(AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
||||||
|
|
||||||
// Server errors
|
// Server errors
|
||||||
case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
|
case(ErrorCode::ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
|
||||||
case(ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break;
|
case(ErrorCode::ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break;
|
||||||
case(ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
||||||
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||||
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
||||||
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
||||||
case(ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
||||||
case(SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
case(ErrorCode::SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
||||||
case(SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
case(ErrorCode::SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
||||||
case(SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
case(ErrorCode::SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
||||||
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
case(ErrorCode::SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
||||||
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
case(ErrorCode::SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||||
|
|
||||||
// Ssh scp errors
|
// Ssh scp errors
|
||||||
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
case(ErrorCode::SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
||||||
|
|
||||||
// Local errors
|
// Local errors
|
||||||
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
case (ErrorCode::OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||||
case (OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
|
case (ErrorCode::OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
|
||||||
|
|
||||||
// Distro errors
|
// Distro errors
|
||||||
case (OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
||||||
case (ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
||||||
case (CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
case (ErrorCode::CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
||||||
case (AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
||||||
case (OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
||||||
|
|
||||||
// VPN errors
|
// VPN errors
|
||||||
case (OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
|
case (ErrorCode::OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
|
||||||
case (OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
|
case (ErrorCode::OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
|
||||||
case (AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
||||||
|
|
||||||
case (ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||||
|
|
||||||
// Android errors
|
// Android errors
|
||||||
case (AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
||||||
|
|
||||||
// Api errors
|
// Api errors
|
||||||
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
case (ErrorCode::ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||||
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
|
case (ErrorCode::ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
|
||||||
|
case (ErrorCode::ApiConfigEmptyError): errorMessage = QObject::tr("In the response from the server, an empty config was received"); break;
|
||||||
|
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
||||||
|
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
case(ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
|
case(ErrorCode::ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
|
||||||
case(PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
|
case(ErrorCode::PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
|
||||||
case(UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
|
case(ErrorCode::UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
|
||||||
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
case(ErrorCode::FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
||||||
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
case(ErrorCode::AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
||||||
|
|
||||||
case(InternalError):
|
case(ErrorCode::InternalError):
|
||||||
default:
|
default:
|
||||||
errorMessage = QObject::tr("Internal error"); break;
|
errorMessage = QObject::tr("Internal error"); break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
|||||||
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
||||||
case DockerContainer::Dns: return QLatin1String("dns");
|
case DockerContainer::Dns: return QLatin1String("dns");
|
||||||
case DockerContainer::Sftp: return QLatin1String("sftp");
|
case DockerContainer::Sftp: return QLatin1String("sftp");
|
||||||
|
case DockerContainer::Socks5Proxy: return QLatin1String("socks5_proxy");
|
||||||
default: return QString();
|
default: return QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,6 +47,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
|
|||||||
case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh");
|
case ProtocolScriptType::configure_container: return QLatin1String("configure_container.sh");
|
||||||
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
|
case ProtocolScriptType::container_startup: return QLatin1String("start.sh");
|
||||||
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
||||||
|
case ProtocolScriptType::openvpn_password_auth: return QLatin1String("password_auth.sh");
|
||||||
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
||||||
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
||||||
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
|
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ enum ProtocolScriptType {
|
|||||||
configure_container,
|
configure_container,
|
||||||
container_startup,
|
container_startup,
|
||||||
openvpn_template,
|
openvpn_template,
|
||||||
|
openvpn_password_auth,
|
||||||
wireguard_template,
|
wireguard_template,
|
||||||
awg_template,
|
awg_template,
|
||||||
xray_template
|
xray_template
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::inbounds
|
||||||
|
{
|
||||||
|
|
||||||
|
//"inbounds": [
|
||||||
|
// {
|
||||||
|
// "listen": "127.0.0.1",
|
||||||
|
// "port": 10808,
|
||||||
|
// "protocol": "socks",
|
||||||
|
// "settings": {
|
||||||
|
// "udp": true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//],
|
||||||
|
|
||||||
|
const static QString listen = "127.0.0.1";
|
||||||
|
const static int port = 10808;
|
||||||
|
const static QString protocol = "socks";
|
||||||
|
|
||||||
|
QJsonObject GenerateInboundEntry()
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonIO::SetValue(root, listen, "listen");
|
||||||
|
QJsonIO::SetValue(root, port, "port");
|
||||||
|
QJsonIO::SetValue(root, protocol, "protocol");
|
||||||
|
QJsonIO::SetValue(root, true, "settings", "udp");
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::inbounds
|
||||||
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::outbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(domainStrategy, redirect)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateBlackHoleOUT(bool useHTTP)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonObject resp;
|
||||||
|
resp.insert("type", useHTTP ? "http" : "none");
|
||||||
|
root.insert("response", resp);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(address, port, method, password)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &_servers)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray x;
|
||||||
|
|
||||||
|
for (const auto &server : _servers)
|
||||||
|
{
|
||||||
|
x.append(GenerateShadowSocksServerOUT(server.address, server.port, server.method, server.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.insert("servers", x);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateHTTPSOCKSOut(const QString &addr, int port, bool useAuth, const QString &username, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonIO::SetValue(root, addr, "servers", 0, "address");
|
||||||
|
QJsonIO::SetValue(root, port, "servers", 0, "port");
|
||||||
|
if (useAuth)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(root, username, "servers", 0, "users", 0, "user");
|
||||||
|
QJsonIO::SetValue(root, password, "servers", 0, "users", 0, "pass");
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateOutboundEntry(const QString &tag, const QString &protocol, const QJsonObject &settings, const QJsonObject &streamSettings,
|
||||||
|
const QJsonObject &mux, const QString &sendThrough)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(sendThrough, protocol, settings, tag, streamSettings, mux)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &_servers)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray x;
|
||||||
|
|
||||||
|
for (const auto &server : _servers)
|
||||||
|
{
|
||||||
|
x.append(GenerateTrojanServerOUT(server.address, server.port, server.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.insert("servers", x);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(address, port, password)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::outbounds
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef SERIALIZATION_H
|
||||||
|
#define SERIALIZATION_H
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include "transfer.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization
|
||||||
|
{
|
||||||
|
namespace vmess
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||||
|
} // namespace vmess
|
||||||
|
|
||||||
|
namespace vmess_new
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||||
|
} // namespace vmess_new
|
||||||
|
|
||||||
|
namespace vless
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
||||||
|
} // namespace vless
|
||||||
|
|
||||||
|
namespace ss
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &ss, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool isSip002);
|
||||||
|
} // namespace ss
|
||||||
|
|
||||||
|
namespace ssd
|
||||||
|
{
|
||||||
|
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList);
|
||||||
|
} // namespace ssd
|
||||||
|
|
||||||
|
namespace trojan
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &trojan, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const TrojanObject &server, const QString &alias);
|
||||||
|
} // namespace trojan
|
||||||
|
|
||||||
|
namespace outbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect);
|
||||||
|
QJsonObject GenerateBlackHoleOUT(bool useHTTP);
|
||||||
|
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &servers);
|
||||||
|
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password);
|
||||||
|
QJsonObject GenerateHTTPSOCKSOut(const QString &address, int port, bool useAuth, const QString &username, const QString &password);
|
||||||
|
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &servers);
|
||||||
|
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password);
|
||||||
|
QJsonObject GenerateOutboundEntry(const QString &tag, //
|
||||||
|
const QString &protocol, //
|
||||||
|
const QJsonObject &settings, //
|
||||||
|
const QJsonObject &streamSettings, //
|
||||||
|
const QJsonObject &mux = {}, //
|
||||||
|
const QString &sendThrough = "0.0.0.0");
|
||||||
|
} // namespace outbounds
|
||||||
|
|
||||||
|
namespace inbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateInboundEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SERIALIZATION_H
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||||
|
|
||||||
|
namespace amnezia::serialization::ss
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &ssUri, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
ShadowSocksServerObject server;
|
||||||
|
QString d_name;
|
||||||
|
|
||||||
|
// auto ssUri = _ssUri.toStdString();
|
||||||
|
if (ssUri.length() < 5)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("SS URI is too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto uri = ssUri.mid(5);
|
||||||
|
auto hashPos = uri.lastIndexOf("#");
|
||||||
|
|
||||||
|
if (hashPos >= 0)
|
||||||
|
{
|
||||||
|
// Get the name/remark
|
||||||
|
d_name = uri.mid(uri.lastIndexOf("#") + 1);
|
||||||
|
uri.truncate(hashPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto atPos = uri.indexOf('@');
|
||||||
|
|
||||||
|
if (atPos < 0)
|
||||||
|
{
|
||||||
|
// Old URI scheme
|
||||||
|
QString decoded = QByteArray::fromBase64(uri.toUtf8(), QByteArray::Base64Option::OmitTrailingEquals);
|
||||||
|
auto colonPos = decoded.indexOf(':');
|
||||||
|
|
||||||
|
if (colonPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.method = decoded.left(colonPos);
|
||||||
|
decoded.remove(0, colonPos + 1);
|
||||||
|
atPos = decoded.lastIndexOf('@');
|
||||||
|
|
||||||
|
if (atPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the at separator between password and hostname");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.password = decoded.mid(0, atPos);
|
||||||
|
decoded.remove(0, atPos + 1);
|
||||||
|
colonPos = decoded.lastIndexOf(':');
|
||||||
|
|
||||||
|
if (colonPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between hostname and port");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.address = decoded.mid(0, colonPos);
|
||||||
|
server.port = decoded.mid(colonPos + 1).toInt();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// SIP002 URI scheme
|
||||||
|
auto x = QUrl::fromUserInput(uri);
|
||||||
|
server.address = x.host();
|
||||||
|
server.port = x.port();
|
||||||
|
const auto userInfo = Utils::SafeBase64Decode(x.userName());
|
||||||
|
const auto userInfoSp = userInfo.indexOf(':');
|
||||||
|
|
||||||
|
if (userInfoSp < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||||
|
return QJsonObject{};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto method = userInfo.mid(0, userInfoSp);
|
||||||
|
server.method = method;
|
||||||
|
server.password = userInfo.mid(userInfoSp + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
d_name = QUrl::fromPercentEncoding(d_name.toUtf8());
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ server }), {}));
|
||||||
|
JADD(outbounds)
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
*alias = alias->isEmpty() ? d_name : *alias + "_" + d_name;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool)
|
||||||
|
{
|
||||||
|
QUrl url;
|
||||||
|
const auto plainUserInfo = server.method + ":" + server.password;
|
||||||
|
const auto userinfo = plainUserInfo.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
url.setUserInfo(userinfo);
|
||||||
|
url.setScheme("ss");
|
||||||
|
url.setHost(server.address);
|
||||||
|
url.setPort(server.port);
|
||||||
|
url.setFragment(alias);
|
||||||
|
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::ss
|
||||||
|
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Naive SSD Decoder for Qv2ray
|
||||||
|
*
|
||||||
|
* @author DuckSoft <realducksoft@gmail.com>
|
||||||
|
* @copyright Licensed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
const inline QString QV2RAY_SSD_DEFAULT_NAME_PATTERN = "%1 - %2 (rate %3)";
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::ssd
|
||||||
|
{
|
||||||
|
// These below are super strict checking schemes, but necessary.
|
||||||
|
#define MUST_EXIST(fieldName) \
|
||||||
|
if (!obj.contains((fieldName)) || obj[(fieldName)].isUndefined() || obj[(fieldName)].isNull()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must exist").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_PORT(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (int value = obj[(fieldName)].toInt(-1); value < 0 || value > 65535) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be valid port number"); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_STRING(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (!obj[(fieldName)].isString()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be of type 'string'").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_ARRAY(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (!obj[(fieldName)].isArray()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be an array").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SERVER_SHOULD_BE_OBJECT(server) \
|
||||||
|
if (!server.isObject()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: server must be an object"); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
#define SHOULD_EXIST(fieldName) \
|
||||||
|
if (serverObject[(fieldName)].isUndefined()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: missing required field %1").arg(fieldName); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
#define SHOULD_STRING(fieldName) \
|
||||||
|
SHOULD_EXIST(fieldName); \
|
||||||
|
if (!serverObject[(fieldName)].isString()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: field %1 should be of type 'string'").arg(fieldName); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList)
|
||||||
|
{
|
||||||
|
// ssd links should begin with "ssd://"
|
||||||
|
if (!uri.startsWith("ssd://"))
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: should begin with ssd://");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode base64
|
||||||
|
const auto ssdURIBody = uri.mid(6, uri.length() - 6); //(&uri, 6, uri.length() - 6);
|
||||||
|
const auto decodedJSON = Utils::SafeBase64Decode(ssdURIBody).toUtf8();
|
||||||
|
|
||||||
|
if (decodedJSON.length() == 0)
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: base64 parse failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto decodeError = Utils::VerifyJsonString(decodedJSON);
|
||||||
|
if (!decodeError.isEmpty())
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json parse failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// casting to object
|
||||||
|
const auto obj = Utils::JsonFromString(decodedJSON);
|
||||||
|
|
||||||
|
// obj.airport
|
||||||
|
MUST_STRING("airport");
|
||||||
|
*groupName = obj["airport"].toString();
|
||||||
|
|
||||||
|
// obj.port
|
||||||
|
MUST_PORT("port");
|
||||||
|
const int port = obj["port"].toInt();
|
||||||
|
|
||||||
|
// obj.encryption
|
||||||
|
MUST_STRING("encryption");
|
||||||
|
const auto encryption = obj["encryption"].toString();
|
||||||
|
|
||||||
|
// check: rc4-md5 is not supported by v2ray-core
|
||||||
|
// TODO: more checks, including all algorithms
|
||||||
|
if (encryption.toLower() == "rc4-md5")
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: rc4-md5 encryption is not supported by v2ray-core");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// obj.password
|
||||||
|
MUST_STRING("password");
|
||||||
|
const auto password = obj["password"].toString();
|
||||||
|
// obj.servers
|
||||||
|
MUST_ARRAY("servers");
|
||||||
|
//
|
||||||
|
QList<std::pair<QString, QJsonObject>> serverList;
|
||||||
|
//
|
||||||
|
|
||||||
|
// iterate through the servers
|
||||||
|
for (const auto &server : obj["servers"].toArray())
|
||||||
|
{
|
||||||
|
SERVER_SHOULD_BE_OBJECT(server);
|
||||||
|
const auto serverObject = server.toObject();
|
||||||
|
ShadowSocksServerObject ssObject;
|
||||||
|
|
||||||
|
// encryption
|
||||||
|
ssObject.method = encryption;
|
||||||
|
|
||||||
|
// password
|
||||||
|
ssObject.password = password;
|
||||||
|
|
||||||
|
// address :-> "server"
|
||||||
|
SHOULD_STRING("server");
|
||||||
|
const auto serverAddress = serverObject["server"].toString();
|
||||||
|
ssObject.address = serverAddress;
|
||||||
|
|
||||||
|
// port selection:
|
||||||
|
// normal: use global settings
|
||||||
|
// overriding: use current config
|
||||||
|
if (serverObject["port"].isUndefined())
|
||||||
|
{
|
||||||
|
ssObject.port = port;
|
||||||
|
}
|
||||||
|
else if (auto currPort = serverObject["port"].toInt(-1); (currPort >= 0 && currPort <= 65535))
|
||||||
|
{
|
||||||
|
ssObject.port = currPort;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ssObject.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
// name decision:
|
||||||
|
// untitled: using server:port as name
|
||||||
|
// entitled: using given name
|
||||||
|
QString nodeName;
|
||||||
|
if (serverObject["remarks"].isUndefined())
|
||||||
|
{
|
||||||
|
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||||
|
}
|
||||||
|
else if (serverObject["remarks"].isString())
|
||||||
|
{
|
||||||
|
nodeName = serverObject["remarks"].toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ratio decision:
|
||||||
|
// unspecified: ratio = 1
|
||||||
|
// specified: use given value
|
||||||
|
double ratio = 1.0;
|
||||||
|
if (auto currRatio = serverObject["ratio"].toDouble(-1.0); currRatio != -1.0)
|
||||||
|
{
|
||||||
|
ratio = currRatio;
|
||||||
|
}
|
||||||
|
// else if (!serverObject["ratio"].isUndefined())
|
||||||
|
// {
|
||||||
|
// //*logList << QObject::tr("Invalid ratio encountered. using fallback value.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// format the total name of the node.
|
||||||
|
const auto finalName = QV2RAY_SSD_DEFAULT_NAME_PATTERN.arg(*groupName, nodeName).arg(ratio);
|
||||||
|
// appending to the total list
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ ssObject }), {}));
|
||||||
|
root["outbounds"] = outbounds;
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
serverList.append({ finalName, root });
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the current result
|
||||||
|
return serverList;
|
||||||
|
}
|
||||||
|
#undef MUST_EXIST
|
||||||
|
#undef MUST_PORT
|
||||||
|
#undef MUST_ARRAY
|
||||||
|
#undef MUST_STRING
|
||||||
|
#undef SERVER_SHOULD_BE_OBJECT
|
||||||
|
#undef SHOULD_EXIST
|
||||||
|
#undef SHOULD_STRING
|
||||||
|
} // namespace amnezia::serialization::ssd
|
||||||
|
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
#ifndef TRANSFER_H
|
||||||
|
#define TRANSFER_H
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
|
||||||
|
#define JADDEx(field) root.insert(#field, field);
|
||||||
|
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||||
|
|
||||||
|
constexpr auto VMESS_USER_ALTERID_DEFAULT = 0;
|
||||||
|
|
||||||
|
namespace amnezia::serialization {
|
||||||
|
|
||||||
|
struct ShadowSocksServerObject
|
||||||
|
{
|
||||||
|
QString address;
|
||||||
|
QString method;
|
||||||
|
QString password;
|
||||||
|
int port;
|
||||||
|
JSONSTRUCT_COMPARE(ShadowSocksServerObject, address, method, password)
|
||||||
|
JSONSTRUCT_REGISTER(ShadowSocksServerObject, F(address, port, method, password))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct VMessServerObject
|
||||||
|
{
|
||||||
|
struct UserObject
|
||||||
|
{
|
||||||
|
QString id;
|
||||||
|
int alterId = VMESS_USER_ALTERID_DEFAULT;
|
||||||
|
QString security = "auto";
|
||||||
|
int level = 0;
|
||||||
|
JSONSTRUCT_COMPARE(UserObject, id, alterId, security, level)
|
||||||
|
JSONSTRUCT_REGISTER(UserObject, F(id, alterId, security, level))
|
||||||
|
};
|
||||||
|
|
||||||
|
QString address;
|
||||||
|
int port;
|
||||||
|
QList<UserObject> users;
|
||||||
|
JSONSTRUCT_COMPARE(VMessServerObject, address, port, users)
|
||||||
|
JSONSTRUCT_REGISTER(VMessServerObject, F(address, port, users))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace transfer
|
||||||
|
{
|
||||||
|
|
||||||
|
struct HTTPRequestObject
|
||||||
|
{
|
||||||
|
QString version = "1.1";
|
||||||
|
QString method = "GET";
|
||||||
|
QList<QString> path = { "/" };
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
HTTPRequestObject()
|
||||||
|
{
|
||||||
|
headers = {
|
||||||
|
{ "Host", { "www.baidu.com", "www.bing.com" } },
|
||||||
|
{ "User-Agent",
|
||||||
|
{ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46" } },
|
||||||
|
{ "Accept-Encoding", { "gzip, deflate" } },
|
||||||
|
{ "Connection", { "keep-alive" } },
|
||||||
|
{ "Pragma", { "no-cache" } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
JSONSTRUCT_COMPARE(HTTPRequestObject, version, method, path, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HTTPRequestObject, F(version, method, path, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct HTTPResponseObject
|
||||||
|
{
|
||||||
|
QString version = "1.1";
|
||||||
|
QString status = "200";
|
||||||
|
QString reason = "OK";
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
HTTPResponseObject()
|
||||||
|
{
|
||||||
|
headers = { { "Content-Type", { "application/octet-stream", "video/mpeg" } }, //
|
||||||
|
{ "Transfer-Encoding", { "chunked" } }, //
|
||||||
|
{ "Connection", { "keep-alive" } }, //
|
||||||
|
{ "Pragma", { "no-cache" } } };
|
||||||
|
}
|
||||||
|
JSONSTRUCT_COMPARE(HTTPResponseObject, version, status, reason, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HTTPResponseObject, F(version, status, reason, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TCPHeader_Internal
|
||||||
|
{
|
||||||
|
QString type = "none";
|
||||||
|
HTTPRequestObject request;
|
||||||
|
HTTPResponseObject response;
|
||||||
|
JSONSTRUCT_COMPARE(TCPHeader_Internal, type, request, response)
|
||||||
|
JSONSTRUCT_REGISTER(TCPHeader_Internal, A(type), F(request, response))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct ObfsHeaderObject
|
||||||
|
{
|
||||||
|
QString type = "none";
|
||||||
|
JSONSTRUCT_COMPARE(ObfsHeaderObject, type)
|
||||||
|
JSONSTRUCT_REGISTER(ObfsHeaderObject, F(type))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TCPObject
|
||||||
|
{
|
||||||
|
TCPHeader_Internal header;
|
||||||
|
JSONSTRUCT_COMPARE(TCPObject, header)
|
||||||
|
JSONSTRUCT_REGISTER(TCPObject, F(header))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct KCPObject
|
||||||
|
{
|
||||||
|
int mtu = 1350;
|
||||||
|
int tti = 50;
|
||||||
|
int uplinkCapacity = 5;
|
||||||
|
int downlinkCapacity = 20;
|
||||||
|
bool congestion = false;
|
||||||
|
int readBufferSize = 2;
|
||||||
|
int writeBufferSize = 2;
|
||||||
|
QString seed;
|
||||||
|
ObfsHeaderObject header;
|
||||||
|
KCPObject(){};
|
||||||
|
JSONSTRUCT_COMPARE(KCPObject, mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, seed, header)
|
||||||
|
JSONSTRUCT_REGISTER(KCPObject, F(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header, seed))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct WebSocketObject
|
||||||
|
{
|
||||||
|
QString path = "/";
|
||||||
|
QMap<QString, QString> headers;
|
||||||
|
int maxEarlyData = 0;
|
||||||
|
bool useBrowserForwarding = false;
|
||||||
|
QString earlyDataHeaderName;
|
||||||
|
JSONSTRUCT_COMPARE(WebSocketObject, path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName)
|
||||||
|
JSONSTRUCT_REGISTER(WebSocketObject, F(path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct HttpObject
|
||||||
|
{
|
||||||
|
QList<QString> host;
|
||||||
|
QString path = "/";
|
||||||
|
QString method = "PUT";
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
JSONSTRUCT_COMPARE(HttpObject, host, path, method, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HttpObject, F(host, path, method, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct DomainSocketObject
|
||||||
|
{
|
||||||
|
QString path = "/";
|
||||||
|
JSONSTRUCT_COMPARE(DomainSocketObject, path)
|
||||||
|
JSONSTRUCT_REGISTER(DomainSocketObject, F(path))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct QuicObject
|
||||||
|
{
|
||||||
|
QString security = "none";
|
||||||
|
QString key;
|
||||||
|
ObfsHeaderObject header;
|
||||||
|
JSONSTRUCT_COMPARE(QuicObject, security, key, header)
|
||||||
|
JSONSTRUCT_REGISTER(QuicObject, F(security, key, header))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct gRPCObject
|
||||||
|
{
|
||||||
|
QString serviceName;
|
||||||
|
bool multiMode = false;
|
||||||
|
JSONSTRUCT_COMPARE(gRPCObject, serviceName, multiMode)
|
||||||
|
JSONSTRUCT_REGISTER(gRPCObject, F(serviceName, multiMode))
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct SockoptObject
|
||||||
|
{
|
||||||
|
int mark = 0;
|
||||||
|
bool tcpFastOpen = false;
|
||||||
|
QString tproxy = "off";
|
||||||
|
int tcpKeepAliveInterval = 0;
|
||||||
|
JSONSTRUCT_COMPARE(SockoptObject, mark, tcpFastOpen, tproxy, tcpKeepAliveInterval)
|
||||||
|
JSONSTRUCT_REGISTER(SockoptObject, F(mark, tcpFastOpen, tproxy, tcpKeepAliveInterval))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct CertificateObject
|
||||||
|
{
|
||||||
|
QString usage = "encipherment";
|
||||||
|
QString certificateFile;
|
||||||
|
QString keyFile;
|
||||||
|
QList<QString> certificate;
|
||||||
|
QList<QString> key;
|
||||||
|
JSONSTRUCT_COMPARE(CertificateObject, usage, certificateFile, keyFile, certificate, key)
|
||||||
|
JSONSTRUCT_REGISTER(CertificateObject, F(usage, certificateFile, keyFile, certificate, key))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TLSObject
|
||||||
|
{
|
||||||
|
QString serverName;
|
||||||
|
bool allowInsecure = false;
|
||||||
|
bool enableSessionResumption = false;
|
||||||
|
bool disableSystemRoot = false;
|
||||||
|
QList<QString> alpn;
|
||||||
|
QList<QString> pinnedPeerCertificateChainSha256;
|
||||||
|
QList<CertificateObject> certificates;
|
||||||
|
JSONSTRUCT_COMPARE(TLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||||
|
pinnedPeerCertificateChainSha256, certificates)
|
||||||
|
JSONSTRUCT_REGISTER(TLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||||
|
pinnedPeerCertificateChainSha256, certificates))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct XTLSObject
|
||||||
|
{
|
||||||
|
QString serverName;
|
||||||
|
bool allowInsecure = false;
|
||||||
|
bool enableSessionResumption = false;
|
||||||
|
bool disableSystemRoot = false;
|
||||||
|
QList<QString> alpn;
|
||||||
|
QList<CertificateObject> certificates;
|
||||||
|
JSONSTRUCT_COMPARE(XTLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates)
|
||||||
|
JSONSTRUCT_REGISTER(XTLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates))
|
||||||
|
};
|
||||||
|
} // namespace transfer
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TrojanObject
|
||||||
|
{
|
||||||
|
quint16 port;
|
||||||
|
QString address;
|
||||||
|
QString password;
|
||||||
|
QString sni;
|
||||||
|
bool ignoreCertificate = false;
|
||||||
|
bool ignoreHostname = false;
|
||||||
|
bool reuseSession = false;
|
||||||
|
bool sessionTicket = false;
|
||||||
|
bool reusePort = false;
|
||||||
|
bool tcpFastOpen = false;
|
||||||
|
|
||||||
|
#define _X(name) json[#name] = name
|
||||||
|
QJsonObject toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject json;
|
||||||
|
_X(port);
|
||||||
|
_X(address);
|
||||||
|
_X(password);
|
||||||
|
_X(sni);
|
||||||
|
_X(ignoreCertificate);
|
||||||
|
_X(ignoreHostname);
|
||||||
|
_X(reuseSession);
|
||||||
|
_X(reusePort);
|
||||||
|
_X(sessionTicket);
|
||||||
|
_X(tcpFastOpen);
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
#undef _X
|
||||||
|
|
||||||
|
#define _X(name, type) name = root[#name].to##type()
|
||||||
|
void loadJson(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
_X(port, Int);
|
||||||
|
_X(address, String);
|
||||||
|
_X(password, String);
|
||||||
|
_X(sni, String);
|
||||||
|
_X(ignoreHostname, Bool);
|
||||||
|
_X(ignoreCertificate, Bool);
|
||||||
|
_X(reuseSession, Bool);
|
||||||
|
_X(reusePort, Bool);
|
||||||
|
_X(sessionTicket, Bool);
|
||||||
|
_X(tcpFastOpen, Bool);
|
||||||
|
}
|
||||||
|
#undef _X
|
||||||
|
|
||||||
|
[[nodiscard]] static TrojanObject fromJson(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
TrojanObject o;
|
||||||
|
o.loadJson(root);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StreamSettingsObject
|
||||||
|
{
|
||||||
|
QString network = "tcp";
|
||||||
|
QString security = "none";
|
||||||
|
transfer::SockoptObject sockopt;
|
||||||
|
transfer::TLSObject tlsSettings;
|
||||||
|
transfer::XTLSObject xtlsSettings;
|
||||||
|
transfer::TCPObject tcpSettings;
|
||||||
|
transfer::KCPObject kcpSettings;
|
||||||
|
transfer::WebSocketObject wsSettings;
|
||||||
|
transfer::HttpObject httpSettings;
|
||||||
|
transfer::DomainSocketObject dsSettings;
|
||||||
|
transfer::QuicObject quicSettings;
|
||||||
|
transfer::gRPCObject grpcSettings;
|
||||||
|
JSONSTRUCT_COMPARE(StreamSettingsObject, network, security, sockopt, //
|
||||||
|
tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings)
|
||||||
|
JSONSTRUCT_REGISTER(StreamSettingsObject, F(network, security, sockopt),
|
||||||
|
F(tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings))
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //TRANSFER_H
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::trojan
|
||||||
|
{
|
||||||
|
|
||||||
|
const QString Serialize(const TrojanObject &object, const QString &alias)
|
||||||
|
{
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
if (object.ignoreHostname)
|
||||||
|
query.addQueryItem("allowInsecureHostname", "1");
|
||||||
|
if (object.ignoreCertificate)
|
||||||
|
query.addQueryItem("allowInsecureCertificate", "1");
|
||||||
|
if (object.sessionTicket)
|
||||||
|
query.addQueryItem("sessionTicket", "1");
|
||||||
|
if (object.ignoreCertificate || object.ignoreHostname)
|
||||||
|
query.addQueryItem("allowInsecure", "1");
|
||||||
|
if (object.tcpFastOpen)
|
||||||
|
query.addQueryItem("tfo", "1");
|
||||||
|
|
||||||
|
if (!object.sni.isEmpty())
|
||||||
|
query.addQueryItem("sni", object.sni);
|
||||||
|
|
||||||
|
QUrl link;
|
||||||
|
if (!object.password.isEmpty())
|
||||||
|
link.setUserName(object.password, QUrl::DecodedMode);
|
||||||
|
link.setPort(object.port);
|
||||||
|
link.setHost(object.address);
|
||||||
|
link.setFragment(alias);
|
||||||
|
link.setQuery(query);
|
||||||
|
link.setScheme("trojan");
|
||||||
|
|
||||||
|
return link.toString(QUrl::FullyEncoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject Deserialize(const QString &trojanUri, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
const QString prefix = "trojan://";
|
||||||
|
if (!trojanUri.startsWith(prefix))
|
||||||
|
{
|
||||||
|
*errMessage = ("Invalid Trojan URI");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//
|
||||||
|
const auto trueList = QStringList{ "true", "1", "yes", "y" };
|
||||||
|
const QUrl trojanUrl(trojanUri.trimmed());
|
||||||
|
const QUrlQuery query(trojanUrl.query());
|
||||||
|
*alias = trojanUrl.fragment(QUrl::FullyDecoded);
|
||||||
|
|
||||||
|
auto getQueryValue = [&](const QString &key) {
|
||||||
|
return query.queryItemValue(key, QUrl::FullyDecoded);
|
||||||
|
};
|
||||||
|
//
|
||||||
|
TrojanObject result;
|
||||||
|
result.address = trojanUrl.host();
|
||||||
|
result.password = QUrl::fromPercentEncoding(trojanUrl.userInfo().toUtf8());
|
||||||
|
result.port = trojanUrl.port();
|
||||||
|
// process sni (and also "peer")
|
||||||
|
if (query.hasQueryItem("sni"))
|
||||||
|
{
|
||||||
|
result.sni = getQueryValue("sni");
|
||||||
|
}
|
||||||
|
else if (query.hasQueryItem("peer"))
|
||||||
|
{
|
||||||
|
// This is evil and may be removed in a future version.
|
||||||
|
qWarning() << "use of 'peer' in trojan url is deprecated";
|
||||||
|
result.sni = getQueryValue("peer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use the hostname
|
||||||
|
result.sni = result.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
result.tcpFastOpen = trueList.contains(getQueryValue("tfo").toLower());
|
||||||
|
result.sessionTicket = trueList.contains(getQueryValue("sessionTicket").toLower());
|
||||||
|
//
|
||||||
|
bool allowAllInsecure = trueList.contains(getQueryValue("allowInsecure").toLower());
|
||||||
|
result.ignoreHostname = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureHostname").toLower());
|
||||||
|
result.ignoreCertificate = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureCertificate").toLower());
|
||||||
|
|
||||||
|
QJsonObject stream;
|
||||||
|
// handle type
|
||||||
|
const auto hasType = query.hasQueryItem("type");
|
||||||
|
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||||
|
if (type != "tcp")
|
||||||
|
QJsonIO::SetValue(stream, type, "network");
|
||||||
|
|
||||||
|
|
||||||
|
// type-wise settings
|
||||||
|
if (type == "kcp")
|
||||||
|
{
|
||||||
|
const auto hasSeed = query.hasQueryItem("seed");
|
||||||
|
if (hasSeed)
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
else if (type == "http")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||||
|
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "ws")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "quic")
|
||||||
|
{
|
||||||
|
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||||
|
if (hasQuicSecurity)
|
||||||
|
{
|
||||||
|
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||||
|
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||||
|
|
||||||
|
if (quicSecurity != "none")
|
||||||
|
{
|
||||||
|
const auto key = query.queryItemValue("key");
|
||||||
|
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "grpc")
|
||||||
|
{
|
||||||
|
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||||
|
if (hasServiceName)
|
||||||
|
{
|
||||||
|
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasMode = query.hasQueryItem("mode");
|
||||||
|
if (hasMode)
|
||||||
|
{
|
||||||
|
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||||
|
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tls-wise settings
|
||||||
|
const auto hasSecurity = query.hasQueryItem("security");
|
||||||
|
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||||
|
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||||
|
if (security != "none")
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, security, "security");
|
||||||
|
}
|
||||||
|
// sni
|
||||||
|
const auto hasSNI = query.hasQueryItem("sni");
|
||||||
|
if (hasSNI)
|
||||||
|
{
|
||||||
|
const auto sni = query.queryItemValue("sni");
|
||||||
|
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||||
|
}
|
||||||
|
// alpn
|
||||||
|
const auto hasALPN = query.hasQueryItem("alpn");
|
||||||
|
if (hasALPN)
|
||||||
|
{
|
||||||
|
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||||
|
QStringList aplnElems = alpnRaw.split(",");
|
||||||
|
// h2 protocol is not supported by xray
|
||||||
|
aplnElems.removeAll("h2");
|
||||||
|
if (!aplnElems.isEmpty()) {
|
||||||
|
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||||
|
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (security == "reality")
|
||||||
|
{
|
||||||
|
if (query.hasQueryItem("fp"))
|
||||||
|
{
|
||||||
|
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("pbk"))
|
||||||
|
{
|
||||||
|
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("spiderX"))
|
||||||
|
{
|
||||||
|
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("sid"))
|
||||||
|
{
|
||||||
|
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
QJsonObject outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "trojan", outbounds::GenerateTrojanOUT({ result }), {});
|
||||||
|
outbound["streamSettings"] = stream;
|
||||||
|
outbounds.append(outbound);
|
||||||
|
JADD(outbounds)
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::trojan
|
||||||
|
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::vless
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
// must start with vless://
|
||||||
|
if (!str.startsWith("vless://"))
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("VLESS link should start with vless://");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse url
|
||||||
|
QUrl url(str);
|
||||||
|
if (!url.isValid())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("link parse failed: %1").arg(url.errorString());
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch host
|
||||||
|
const auto hostRaw = url.host();
|
||||||
|
if (hostRaw.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("empty host");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
const auto host = (hostRaw.startsWith('[') && hostRaw.endsWith(']')) ? hostRaw.mid(1, hostRaw.length() - 2) : hostRaw;
|
||||||
|
|
||||||
|
// fetch port
|
||||||
|
const auto port = url.port();
|
||||||
|
if (port == -1)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("missing port");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch remarks
|
||||||
|
const auto remarks = url.fragment();
|
||||||
|
if (!remarks.isEmpty())
|
||||||
|
{
|
||||||
|
*alias = remarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch uuid
|
||||||
|
const auto uuid = url.userInfo();
|
||||||
|
if (uuid.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("missing uuid");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize QJsonObject with basic info
|
||||||
|
QJsonObject outbound;
|
||||||
|
QJsonObject stream;
|
||||||
|
|
||||||
|
QJsonIO::SetValue(outbound, "vless", "protocol");
|
||||||
|
QJsonIO::SetValue(outbound, host, { "settings", "vnext", 0, "address" });
|
||||||
|
QJsonIO::SetValue(outbound, port, { "settings", "vnext", 0, "port" });
|
||||||
|
QJsonIO::SetValue(outbound, uuid, { "settings", "vnext", 0, "users", 0, "id" });
|
||||||
|
|
||||||
|
// parse query
|
||||||
|
QUrlQuery query(url.query());
|
||||||
|
|
||||||
|
// handle type
|
||||||
|
const auto hasType = query.hasQueryItem("type");
|
||||||
|
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||||
|
if (type != "tcp")
|
||||||
|
QJsonIO::SetValue(stream, type, "network");
|
||||||
|
|
||||||
|
// handle encryption
|
||||||
|
const auto hasEncryption = query.hasQueryItem("encryption");
|
||||||
|
const auto encryption = hasEncryption ? query.queryItemValue("encryption") : "none";
|
||||||
|
QJsonIO::SetValue(outbound, encryption, { "settings", "vnext", 0, "users", 0, "encryption" });
|
||||||
|
|
||||||
|
// type-wise settings
|
||||||
|
if (type == "kcp")
|
||||||
|
{
|
||||||
|
const auto hasSeed = query.hasQueryItem("seed");
|
||||||
|
if (hasSeed)
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
else if (type == "http")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||||
|
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "ws")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "quic")
|
||||||
|
{
|
||||||
|
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||||
|
if (hasQuicSecurity)
|
||||||
|
{
|
||||||
|
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||||
|
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||||
|
|
||||||
|
if (quicSecurity != "none")
|
||||||
|
{
|
||||||
|
const auto key = query.queryItemValue("key");
|
||||||
|
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "grpc")
|
||||||
|
{
|
||||||
|
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||||
|
if (hasServiceName)
|
||||||
|
{
|
||||||
|
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasMode = query.hasQueryItem("mode");
|
||||||
|
if (hasMode)
|
||||||
|
{
|
||||||
|
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||||
|
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tls-wise settings
|
||||||
|
const auto hasSecurity = query.hasQueryItem("security");
|
||||||
|
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||||
|
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||||
|
if (security != "none")
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, security, "security");
|
||||||
|
}
|
||||||
|
// sni
|
||||||
|
const auto hasSNI = query.hasQueryItem("sni");
|
||||||
|
if (hasSNI)
|
||||||
|
{
|
||||||
|
const auto sni = query.queryItemValue("sni");
|
||||||
|
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||||
|
}
|
||||||
|
// alpn
|
||||||
|
const auto hasALPN = query.hasQueryItem("alpn");
|
||||||
|
if (hasALPN)
|
||||||
|
{
|
||||||
|
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||||
|
QStringList aplnElems = alpnRaw.split(",");
|
||||||
|
// h2 protocol is not supported by xray
|
||||||
|
aplnElems.removeAll("h2");
|
||||||
|
if (!aplnElems.isEmpty()) {
|
||||||
|
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||||
|
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// xtls-specific
|
||||||
|
if (security == "xtls" || security == "reality")
|
||||||
|
{
|
||||||
|
const auto flow = query.queryItemValue("flow");
|
||||||
|
QJsonIO::SetValue(outbound, flow, { "settings", "vnext", 0, "users", 0, "flow" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (security == "reality")
|
||||||
|
{
|
||||||
|
if (query.hasQueryItem("fp"))
|
||||||
|
{
|
||||||
|
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("pbk"))
|
||||||
|
{
|
||||||
|
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("spiderX"))
|
||||||
|
{
|
||||||
|
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("sid"))
|
||||||
|
{
|
||||||
|
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assembling config
|
||||||
|
QJsonObject root;
|
||||||
|
outbound["streamSettings"] = stream;
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["outbounds"] = QJsonArray{ outbound };
|
||||||
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::vless
|
||||||
|
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define nothing
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::vmess
|
||||||
|
{
|
||||||
|
// From https://github.com/2dust/v2rayN/wiki/分享链接格式说明(ver-2)
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias)
|
||||||
|
{
|
||||||
|
QJsonObject vmessUriRoot;
|
||||||
|
// Constant
|
||||||
|
vmessUriRoot["v"] = 2;
|
||||||
|
vmessUriRoot["ps"] = alias;
|
||||||
|
vmessUriRoot["add"] = server.address;
|
||||||
|
vmessUriRoot["port"] = server.port;
|
||||||
|
vmessUriRoot["id"] = server.users.front().id;
|
||||||
|
vmessUriRoot["aid"] = server.users.front().alterId;
|
||||||
|
const auto scy = server.users.front().security;
|
||||||
|
vmessUriRoot["scy"] = (scy == "aes-128-gcm" || scy == "chacha20-poly1305" || scy == "none" || scy == "zero") ? scy : "auto";
|
||||||
|
vmessUriRoot["net"] = transfer.network == "http" ? "h2" : transfer.network;
|
||||||
|
vmessUriRoot["tls"] = (transfer.security == "tls" || transfer.security == "xtls") ? "tls" : "none";
|
||||||
|
if (transfer.security == "tls")
|
||||||
|
{
|
||||||
|
vmessUriRoot["sni"] = transfer.tlsSettings.serverName;
|
||||||
|
}
|
||||||
|
else if (transfer.security == "xtls")
|
||||||
|
{
|
||||||
|
vmessUriRoot["sni"] = transfer.xtlsSettings.serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transfer.network == "tcp")
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = transfer.tcpSettings.header.type;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "kcp")
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = transfer.kcpSettings.header.type;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "quic")
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = transfer.quicSettings.header.type;
|
||||||
|
vmessUriRoot["host"] = transfer.quicSettings.security;
|
||||||
|
vmessUriRoot["path"] = transfer.quicSettings.key;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "ws")
|
||||||
|
{
|
||||||
|
auto x = transfer.wsSettings.headers;
|
||||||
|
auto host = x.contains("host");
|
||||||
|
auto CapHost = x.contains("Host");
|
||||||
|
auto realHost = host ? x["host"] : (CapHost ? x["Host"] : "");
|
||||||
|
//
|
||||||
|
vmessUriRoot["host"] = realHost;
|
||||||
|
vmessUriRoot["path"] = transfer.wsSettings.path;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "h2" || transfer.network == "http")
|
||||||
|
{
|
||||||
|
vmessUriRoot["host"] = transfer.httpSettings.host.join(",");
|
||||||
|
vmessUriRoot["path"] = transfer.httpSettings.path;
|
||||||
|
}
|
||||||
|
else if (transfer.network == "grpc")
|
||||||
|
{
|
||||||
|
vmessUriRoot["path"] = transfer.grpcSettings.serviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vmessUriRoot.contains("type") || vmessUriRoot["type"].toString().isEmpty())
|
||||||
|
{
|
||||||
|
vmessUriRoot["type"] = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
QString jString = Utils::JsonToString(vmessUriRoot, QJsonDocument::JsonFormat::Compact);
|
||||||
|
auto vmessPart = jString.toUtf8().toBase64();
|
||||||
|
return "vmess://" + vmessPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This generates global config containing only one outbound....
|
||||||
|
QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
#define default QJsonObject()
|
||||||
|
QString vmess = vmessStr;
|
||||||
|
|
||||||
|
if (vmess.trimmed() != vmess)
|
||||||
|
{
|
||||||
|
vmess = vmessStr.trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset errMessage
|
||||||
|
*errMessage = "";
|
||||||
|
|
||||||
|
if (!vmess.toLower().startsWith("vmess://"))
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("VMess string should start with 'vmess://'");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto b64Str = vmess.mid(8, vmess.length() - 8);
|
||||||
|
if (b64Str.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("VMess string should be a valid base64 string");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vmessString = Utils::SafeBase64Decode(b64Str);
|
||||||
|
auto jsonErr = Utils::VerifyJsonString(vmessString);
|
||||||
|
|
||||||
|
if (!jsonErr.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = jsonErr;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto vmessConf = Utils::JsonFromString(vmessString);
|
||||||
|
|
||||||
|
if (vmessConf.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("JSON should not be empty");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------------------
|
||||||
|
QJsonObject root;
|
||||||
|
QString ps, add, id, net, type, host, path, tls, scy, sni;
|
||||||
|
int port, aid;
|
||||||
|
//
|
||||||
|
// __vmess_checker__func(key, values)
|
||||||
|
//
|
||||||
|
// - Key = Key in JSON and the variable name.
|
||||||
|
// - Values = Candidate variable list, if not match, the first one is used as default.
|
||||||
|
//
|
||||||
|
// - [[val.size() <= 1]] is used when only the default value exists.
|
||||||
|
//
|
||||||
|
// - It can be empty, if so, if the key is not in the JSON, or the value is empty, report an error.
|
||||||
|
// - Else if it contains one thing. if the key is not in the JSON, or the value is empty, use that one.
|
||||||
|
// - Else if it contains many things, when the key IS in the JSON but not within the THINGS, use the first in the THINGS
|
||||||
|
// - Else -------------------------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> use the JSON value
|
||||||
|
//
|
||||||
|
#define __vmess_checker__func(key, values) \
|
||||||
|
{ \
|
||||||
|
auto val = QStringList() values; \
|
||||||
|
if (vmessConf.contains(#key) && !vmessConf[#key].toVariant().toString().trimmed().isEmpty() && \
|
||||||
|
(val.size() <= 1 || val.contains(vmessConf[#key].toVariant().toString()))) \
|
||||||
|
{ \
|
||||||
|
key = vmessConf[#key].toVariant().toString(); \
|
||||||
|
} \
|
||||||
|
else if (!val.isEmpty()) \
|
||||||
|
{ \
|
||||||
|
key = val.first(); \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
*errMessage = QObject::tr(#key " does not exist."); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
// vmess v1 upgrader
|
||||||
|
if (!vmessConf.contains("v"))
|
||||||
|
{
|
||||||
|
qDebug() << "Detected deprecated vmess v1. Trying to upgrade...";
|
||||||
|
if (const auto network = vmessConf["net"].toString(); network == "ws" || network == "h2")
|
||||||
|
{
|
||||||
|
const QStringList hostComponents = vmessConf["host"].toString().replace(" ", "").split(";");
|
||||||
|
if (const auto nParts = hostComponents.length(); nParts == 1)
|
||||||
|
vmessConf["path"] = hostComponents[0], vmessConf["host"] = "";
|
||||||
|
else if (nParts == 2)
|
||||||
|
vmessConf["path"] = hostComponents[0], vmessConf["host"] = hostComponents[1];
|
||||||
|
else
|
||||||
|
vmessConf["path"] = "/", vmessConf["host"] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strict check of VMess protocol, to check if the specified value
|
||||||
|
// is in the correct range.
|
||||||
|
//
|
||||||
|
// Get Alias (AKA ps) from address and port.
|
||||||
|
{
|
||||||
|
// Some idiot vmess:// links are using alterId...
|
||||||
|
aid = vmessConf.contains("aid") ? vmessConf.value("aid").toInt(VMESS_USER_ALTERID_DEFAULT) :
|
||||||
|
vmessConf.value("alterId").toInt(VMESS_USER_ALTERID_DEFAULT);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
__vmess_checker__func(ps, << vmessConf["add"].toVariant().toString() + ":" + vmessConf["port"].toVariant().toString()); //
|
||||||
|
__vmess_checker__func(add, nothing); //
|
||||||
|
__vmess_checker__func(id, nothing); //
|
||||||
|
__vmess_checker__func(scy, << "aes-128-gcm" //
|
||||||
|
<< "chacha20-poly1305" //
|
||||||
|
<< "auto" //
|
||||||
|
<< "none" //
|
||||||
|
<< "zero"); //
|
||||||
|
//
|
||||||
|
__vmess_checker__func(type, << "none" //
|
||||||
|
<< "http" //
|
||||||
|
<< "srtp" //
|
||||||
|
<< "utp" //
|
||||||
|
<< "wechat-video"); //
|
||||||
|
//
|
||||||
|
__vmess_checker__func(net, << "tcp" //
|
||||||
|
<< "http" //
|
||||||
|
<< "h2" //
|
||||||
|
<< "ws" //
|
||||||
|
<< "kcp" //
|
||||||
|
<< "quic" //
|
||||||
|
<< "grpc"); //
|
||||||
|
//
|
||||||
|
__vmess_checker__func(tls, << "none" //
|
||||||
|
<< "tls"); //
|
||||||
|
//
|
||||||
|
path = vmessConf.contains("path") ? vmessConf["path"].toVariant().toString() : (net == "quic" ? "" : "/");
|
||||||
|
host = vmessConf.contains("host") ? vmessConf["host"].toVariant().toString() : (net == "quic" ? "none" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respect connection type rather than obfs type
|
||||||
|
if (QStringList{ "srtp", "utp", "wechat-video" }.contains(type)) //
|
||||||
|
{ //
|
||||||
|
if (net != "quic" && net != "kcp") //
|
||||||
|
{ //
|
||||||
|
type = "none"; //
|
||||||
|
} //
|
||||||
|
}
|
||||||
|
|
||||||
|
port = vmessConf["port"].toVariant().toInt();
|
||||||
|
aid = vmessConf["aid"].toVariant().toInt();
|
||||||
|
//
|
||||||
|
// Apply the settings.
|
||||||
|
// User
|
||||||
|
VMessServerObject::UserObject user;
|
||||||
|
user.id = id;
|
||||||
|
user.alterId = aid;
|
||||||
|
user.security = scy;
|
||||||
|
//
|
||||||
|
// Server
|
||||||
|
VMessServerObject serv;
|
||||||
|
serv.port = port;
|
||||||
|
serv.address = add;
|
||||||
|
serv.users.push_back(user);
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Stream Settings
|
||||||
|
StreamSettingsObject streaming;
|
||||||
|
|
||||||
|
if (net == "tcp")
|
||||||
|
{
|
||||||
|
streaming.tcpSettings.header.type = type;
|
||||||
|
}
|
||||||
|
else if (net == "http" || net == "h2")
|
||||||
|
{
|
||||||
|
// Fill hosts for HTTP
|
||||||
|
for (const auto &_host : host.split(','))
|
||||||
|
{
|
||||||
|
if (!_host.isEmpty())
|
||||||
|
{
|
||||||
|
streaming.httpSettings.host << _host.trimmed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
streaming.httpSettings.path = path;
|
||||||
|
}
|
||||||
|
else if (net == "ws")
|
||||||
|
{
|
||||||
|
if (!host.isEmpty())
|
||||||
|
streaming.wsSettings.headers["Host"] = host;
|
||||||
|
streaming.wsSettings.path = path;
|
||||||
|
}
|
||||||
|
else if (net == "kcp")
|
||||||
|
{
|
||||||
|
streaming.kcpSettings.header.type = type;
|
||||||
|
}
|
||||||
|
else if (net == "quic")
|
||||||
|
{
|
||||||
|
streaming.quicSettings.security = host;
|
||||||
|
streaming.quicSettings.header.type = type;
|
||||||
|
streaming.quicSettings.key = path;
|
||||||
|
}
|
||||||
|
else if (net == "grpc")
|
||||||
|
{
|
||||||
|
streaming.grpcSettings.serviceName = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
streaming.security = tls;
|
||||||
|
if (tls == "tls")
|
||||||
|
{
|
||||||
|
if (sni.isEmpty() && !host.isEmpty())
|
||||||
|
sni = host;
|
||||||
|
streaming.tlsSettings.serverName = sni;
|
||||||
|
streaming.tlsSettings.allowInsecure = false;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Network type
|
||||||
|
// NOTE(DuckSoft): Damn vmess:// just don't write 'http' properly
|
||||||
|
if (net == "h2")
|
||||||
|
net = "http";
|
||||||
|
streaming.network = net;
|
||||||
|
//
|
||||||
|
// VMess root config
|
||||||
|
QJsonObject vConf;
|
||||||
|
vConf["vnext"] = QJsonArray{ serv.toJson() };
|
||||||
|
const auto outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "vmess", vConf, streaming.toJson());
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["outbounds"] = QJsonArray{ outbound };
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
// If previous alias is empty, just the PS is needed, else, append a "_"
|
||||||
|
*alias = alias->trimmed().isEmpty() ? ps : *alias + "_" + ps;
|
||||||
|
return root;
|
||||||
|
#undef default
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::vmess
|
||||||
|
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::vmess_new
|
||||||
|
{
|
||||||
|
const static QStringList NetworkType{ "tcp", "http", "ws", "kcp", "quic", "grpc" };
|
||||||
|
const static QStringList QuicSecurityTypes{ "none", "aes-128-gcm", "chacha20-poly1305" };
|
||||||
|
const static QStringList QuicKcpHeaderTypes{ "none", "srtp", "utp", "wechat-video", "dtls", "wireguard" };
|
||||||
|
const static QStringList FalseTypes{ "false", "False", "No", "Off", "0" };
|
||||||
|
|
||||||
|
QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
QUrl url{ vmessStr };
|
||||||
|
QUrlQuery query{ url };
|
||||||
|
//
|
||||||
|
#define default QJsonObject()
|
||||||
|
if (!url.isValid())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("vmess:// url is invalid");
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If previous alias is empty, just the PS is needed, else, append a "_"
|
||||||
|
const auto name = url.fragment(QUrl::FullyDecoded).trimmed();
|
||||||
|
*alias = alias->isEmpty() ? name : (*alias + "_" + name);
|
||||||
|
|
||||||
|
VMessServerObject server;
|
||||||
|
server.users << VMessServerObject::UserObject{};
|
||||||
|
|
||||||
|
StreamSettingsObject stream;
|
||||||
|
QString net;
|
||||||
|
bool tls = false;
|
||||||
|
// Check streamSettings
|
||||||
|
{
|
||||||
|
for (const auto &_protocol : url.userName().split("+"))
|
||||||
|
{
|
||||||
|
if (_protocol == "tls")
|
||||||
|
tls = true;
|
||||||
|
else
|
||||||
|
net = _protocol;
|
||||||
|
}
|
||||||
|
if (!NetworkType.contains(net))
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Invalid streamSettings protocol: ") + net;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
stream.network = net;
|
||||||
|
stream.security = tls ? "tls" : "";
|
||||||
|
}
|
||||||
|
// Host Port UUID AlterID
|
||||||
|
{
|
||||||
|
const auto host = url.host();
|
||||||
|
int port = url.port();
|
||||||
|
QString uuid;
|
||||||
|
int aid;
|
||||||
|
{
|
||||||
|
const auto pswd = url.password();
|
||||||
|
const auto index = pswd.lastIndexOf("-");
|
||||||
|
uuid = pswd.mid(0, index);
|
||||||
|
aid = pswd.right(pswd.length() - index - 1).toInt();
|
||||||
|
}
|
||||||
|
server.address = host;
|
||||||
|
server.port = port;
|
||||||
|
server.users.first().id = uuid;
|
||||||
|
server.users.first().alterId = aid;
|
||||||
|
server.users.first().security = "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
const static auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
|
||||||
|
if (query.hasQueryItem(key))
|
||||||
|
return query.queryItemValue(key, QUrl::FullyDecoded);
|
||||||
|
else
|
||||||
|
return defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Begin transport settings parser
|
||||||
|
{
|
||||||
|
if (net == "tcp")
|
||||||
|
{
|
||||||
|
stream.tcpSettings.header.type = getQueryValue("type", "none");
|
||||||
|
}
|
||||||
|
else if (net == "http")
|
||||||
|
{
|
||||||
|
stream.httpSettings.host.append(getQueryValue("host", ""));
|
||||||
|
stream.httpSettings.path = getQueryValue("path", "/");
|
||||||
|
}
|
||||||
|
else if (net == "ws")
|
||||||
|
{
|
||||||
|
stream.wsSettings.headers["Host"] = getQueryValue("host", "");
|
||||||
|
stream.wsSettings.path = getQueryValue("path", "/");
|
||||||
|
}
|
||||||
|
else if (net == "kcp")
|
||||||
|
{
|
||||||
|
stream.kcpSettings.seed = getQueryValue("seed", "");
|
||||||
|
stream.kcpSettings.header.type = getQueryValue("type", "none");
|
||||||
|
}
|
||||||
|
else if (net == "quic")
|
||||||
|
{
|
||||||
|
stream.quicSettings.security = getQueryValue("security", "none");
|
||||||
|
stream.quicSettings.key = getQueryValue("key", "");
|
||||||
|
stream.quicSettings.header.type = getQueryValue("type", "none");
|
||||||
|
}
|
||||||
|
else if (net == "grpc")
|
||||||
|
{
|
||||||
|
stream.grpcSettings.serviceName = getQueryValue("serviceName", "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Unknown transport method: ") + net;
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef default
|
||||||
|
if (tls)
|
||||||
|
{
|
||||||
|
stream.tlsSettings.allowInsecure = !FalseTypes.contains(getQueryValue("allowInsecure", "false"));
|
||||||
|
stream.tlsSettings.serverName = getQueryValue("tlsServerName", "");
|
||||||
|
}
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonObject vConf;
|
||||||
|
QJsonArray vnextArray;
|
||||||
|
vnextArray.append(server.toJson());
|
||||||
|
vConf["vnext"] = vnextArray;
|
||||||
|
auto outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "vmess", vConf, stream.toJson());
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
|
||||||
|
//
|
||||||
|
root["outbounds"] = QJsonArray{ outbound };
|
||||||
|
root["inbound"] = QJsonArray{ inbound };
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::vmess_new
|
||||||
@@ -51,6 +51,13 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
<key>CFBundleIcons</key>
|
<key>CFBundleIcons</key>
|
||||||
<dict/>
|
<dict/>
|
||||||
<key>CFBundleIcons~ipad</key>
|
<key>CFBundleIcons~ipad</key>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||||
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
||||||
|
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
|
|||||||
callActivityMethod("start", "(Ljava/lang/String;)V",
|
callActivityMethod("start", "(Ljava/lang/String;)V",
|
||||||
QJniObject::fromString(config).object<jstring>());
|
QJniObject::fromString(config).object<jstring>());
|
||||||
|
|
||||||
return NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AndroidController::stop()
|
void AndroidController::stop()
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
#include "MobileUtils.h"
|
|
||||||
|
|
||||||
MobileUtils::MobileUtils(QObject *parent) : QObject(parent)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MobileUtils::shareText(const QStringList &)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MobileUtils::openFile()
|
|
||||||
{
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#ifndef MOBILEUTILS_H
|
|
||||||
#define MOBILEUTILS_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QStringList>
|
|
||||||
|
|
||||||
class MobileUtils : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit MobileUtils(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
bool shareText(const QStringList &filesToSend);
|
|
||||||
QString openFile();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void finished();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // MOBILEUTILS_H
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
#include "MobileUtils.h"
|
|
||||||
|
|
||||||
#include <UIKit/UIKit.h>
|
|
||||||
#include <Security/Security.h>
|
|
||||||
|
|
||||||
#include <QEventLoop>
|
|
||||||
|
|
||||||
static UIViewController* getViewController() {
|
|
||||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
|
||||||
for (UIWindow *window in windows) {
|
|
||||||
if (window.isKeyWindow) {
|
|
||||||
return window.rootViewController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
MobileUtils::MobileUtils(QObject *parent) : QObject(parent) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MobileUtils::shareText(const QStringList& filesToSend) {
|
|
||||||
NSMutableArray *sharingItems = [NSMutableArray new];
|
|
||||||
|
|
||||||
for (int i = 0; i < filesToSend.size(); i++) {
|
|
||||||
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
|
||||||
[sharingItems addObject:logFileUrl];
|
|
||||||
}
|
|
||||||
|
|
||||||
UIViewController *qtController = getViewController();
|
|
||||||
if (!qtController) return;
|
|
||||||
|
|
||||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
|
||||||
|
|
||||||
__block bool isAccepted = false;
|
|
||||||
|
|
||||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
|
||||||
isAccepted = completed;
|
|
||||||
emit finished();
|
|
||||||
}];
|
|
||||||
|
|
||||||
[qtController presentViewController:activityController animated:YES completion:nil];
|
|
||||||
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
|
||||||
if (popController) {
|
|
||||||
popController.sourceView = qtController.view;
|
|
||||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit);
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
return isAccepted;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
|
||||||
|
|
||||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
|
||||||
|
|
||||||
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation DocumentPickerDelegate
|
|
||||||
|
|
||||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
|
||||||
for (NSURL *url in urls) {
|
|
||||||
if (self.documentPickerClosedCallback) {
|
|
||||||
self.documentPickerClosedCallback([url path]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
|
||||||
if (self.documentPickerClosedCallback) {
|
|
||||||
self.documentPickerClosedCallback(nil);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
QString MobileUtils::openFile() {
|
|
||||||
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
|
||||||
|
|
||||||
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
|
||||||
documentPicker.delegate = documentPickerDelegate;
|
|
||||||
|
|
||||||
UIViewController *qtController = getViewController();
|
|
||||||
if (!qtController) return;
|
|
||||||
|
|
||||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
|
||||||
|
|
||||||
__block QString filePath;
|
|
||||||
|
|
||||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
|
||||||
if (path) {
|
|
||||||
filePath = QString::fromUtf8(path.UTF8String);
|
|
||||||
} else {
|
|
||||||
filePath = QString();
|
|
||||||
}
|
|
||||||
emit finished();
|
|
||||||
};
|
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit);
|
|
||||||
wait.exec();
|
|
||||||
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
@@ -22,8 +22,6 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// ovpnLog(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self))")
|
|
||||||
|
|
||||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||||
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
|
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
|
||||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||||
@@ -52,38 +50,39 @@ extension PacketTunnelProvider {
|
|||||||
configuration.setPTCloak()
|
configuration.setPTCloak()
|
||||||
}
|
}
|
||||||
|
|
||||||
let evaluation: OpenVPNConfigurationEvaluation
|
let evaluation: OpenVPNConfigurationEvaluation?
|
||||||
do {
|
do {
|
||||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
ovpnAdapter = OpenVPNAdapter()
|
||||||
|
ovpnAdapter?.delegate = self
|
||||||
|
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
completionHandler(error)
|
completionHandler(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !evaluation.autologin {
|
if evaluation?.autologin == false {
|
||||||
ovpnLog(.info, message: "Implement login with user credentials")
|
ovpnLog(.info, message: "Implement login with user credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
vpnReachability.startTracking { [weak self] status in
|
||||||
guard status == .reachableViaWiFi else { return }
|
guard status == .reachableViaWiFi else { return }
|
||||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
startHandler = completionHandler
|
startHandler = completionHandler
|
||||||
ovpnAdapter.connect(using: packetFlow)
|
ovpnAdapter?.connect(using: packetFlow)
|
||||||
|
|
||||||
// let ifaces = Interface.allInterfaces()
|
|
||||||
// .filter { $0.family == .ipv4 }
|
|
||||||
// .map { iface in iface.name }
|
|
||||||
|
|
||||||
// ovpn_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
let bytesin = ovpnAdapter?.transportStatistics.bytesIn
|
||||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
let bytesout = ovpnAdapter?.transportStatistics.bytesOut
|
||||||
|
|
||||||
|
guard let bytesin, let bytesout else {
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let response: [String: Any] = [
|
let response: [String: Any] = [
|
||||||
"rx_bytes": bytesin,
|
"rx_bytes": bytesin,
|
||||||
@@ -100,7 +99,7 @@ extension PacketTunnelProvider {
|
|||||||
if vpnReachability.isTracking {
|
if vpnReachability.isTracking {
|
||||||
vpnReachability.stopTracking()
|
vpnReachability.stopTracking()
|
||||||
}
|
}
|
||||||
ovpnAdapter.disconnect()
|
ovpnAdapter?.disconnect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +122,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||||||
if splitTunnelType == 1 {
|
if splitTunnelType == 1 {
|
||||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||||
|
|
||||||
|
guard let splitTunnelSites else {
|
||||||
|
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for allowedIPString in splitTunnelSites {
|
for allowedIPString in splitTunnelSites {
|
||||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||||
ipv4IncludedRoutes.append(NEIPv4Route(
|
ipv4IncludedRoutes.append(NEIPv4Route(
|
||||||
@@ -138,6 +142,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||||||
var ipv4IncludedRoutes = [NEIPv4Route]()
|
var ipv4IncludedRoutes = [NEIPv4Route]()
|
||||||
var ipv6IncludedRoutes = [NEIPv6Route]()
|
var ipv6IncludedRoutes = [NEIPv6Route]()
|
||||||
|
|
||||||
|
guard let splitTunnelSites else {
|
||||||
|
completionHandler(NSError(domain: "Split tunnel sited not setted up", code: 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for excludeIPString in splitTunnelSites {
|
for excludeIPString in splitTunnelSites {
|
||||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||||
ipv4ExcludedRoutes.append(NEIPv4Route(
|
ipv4ExcludedRoutes.append(NEIPv4Route(
|
||||||
|
|||||||
@@ -55,9 +55,13 @@ extension PacketTunnelProvider {
|
|||||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||||
|
|
||||||
// Start the tunnel
|
// Start the tunnel
|
||||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
wgAdapter = WireGuardAdapter(with: self) { logLevel, message in
|
||||||
|
wg_log(logLevel.osLogLevel, message: message)
|
||||||
|
}
|
||||||
|
|
||||||
|
wgAdapter?.start(tunnelConfiguration: tunnelConfiguration) { [weak self] adapterError in
|
||||||
guard let adapterError else {
|
guard let adapterError else {
|
||||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
let interfaceName = self?.wgAdapter?.interfaceName ?? "unknown"
|
||||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
@@ -97,12 +101,7 @@ extension PacketTunnelProvider {
|
|||||||
|
|
||||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
var data: Data?
|
|
||||||
if let settings {
|
|
||||||
data = settings.data(using: .utf8)!
|
|
||||||
}
|
|
||||||
|
|
||||||
let components = settings!.components(separatedBy: "\n")
|
let components = settings!.components(separatedBy: "\n")
|
||||||
|
|
||||||
var settingsDictionary: [String: String] = [:]
|
var settingsDictionary: [String: String] = [:]
|
||||||
@@ -125,7 +124,7 @@ extension PacketTunnelProvider {
|
|||||||
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
if messageData.count == 1 && messageData[0] == 0 {
|
if messageData.count == 1 && messageData[0] == 0 {
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
var data: Data?
|
var data: Data?
|
||||||
if let settings {
|
if let settings {
|
||||||
data = settings.data(using: .utf8)!
|
data = settings.data(using: .utf8)!
|
||||||
@@ -143,14 +142,14 @@ extension PacketTunnelProvider {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
wgAdapter?.update(tunnelConfiguration: tunnelConfiguration) { [weak self] error in
|
||||||
if let error {
|
if let error {
|
||||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
self?.wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
var data: Data?
|
var data: Data?
|
||||||
if let settings {
|
if let settings {
|
||||||
data = settings.data(using: .utf8)!
|
data = settings.data(using: .utf8)!
|
||||||
@@ -166,43 +165,10 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
|
||||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
|
||||||
//
|
|
||||||
// let emptyTunnelConfiguration = TunnelConfiguration(
|
|
||||||
// name: nil,
|
|
||||||
// interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
|
||||||
// peers: []
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
|
||||||
// self.dispatchQueue.async {
|
|
||||||
// if let error {
|
|
||||||
// wg_log(.error, message: "Failed to start an empty tunnel")
|
|
||||||
// completionHandler(error)
|
|
||||||
// } else {
|
|
||||||
// wg_log(.info, message: "Started an empty tunnel")
|
|
||||||
// self.tunnelAdapterDidStart()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
|
||||||
//
|
|
||||||
// self.setTunnelNetworkSettings(settings) { error in
|
|
||||||
// completionHandler(error)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private func tunnelAdapterDidStart() {
|
|
||||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
|
||||||
// // ...
|
|
||||||
// }
|
|
||||||
|
|
||||||
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
||||||
|
|
||||||
wgAdapter.stop { error in
|
wgAdapter?.stop { error in
|
||||||
ErrorNotifier.removeLastErrorFile()
|
ErrorNotifier.removeLastErrorFile()
|
||||||
|
|
||||||
if let error {
|
if let error {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Darwin
|
|||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
|
|
||||||
enum TunnelProtoType: String {
|
enum TunnelProtoType: String {
|
||||||
case wireguard, openvpn, shadowsocks, none
|
case wireguard, openvpn
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Constants {
|
struct Constants {
|
||||||
@@ -34,29 +34,17 @@ struct Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
lazy var wgAdapter = {
|
var wgAdapter: WireGuardAdapter?
|
||||||
WireGuardAdapter(with: self) { logLevel, message in
|
var ovpnAdapter: OpenVPNAdapter?
|
||||||
wg_log(logLevel.osLogLevel, message: message)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var ovpnAdapter: OpenVPNAdapter = {
|
var splitTunnelType: Int?
|
||||||
let adapter = OpenVPNAdapter()
|
var splitTunnelSites: [String]?
|
||||||
adapter.delegate = self
|
|
||||||
return adapter
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// Internal queue.
|
|
||||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
|
||||||
|
|
||||||
var splitTunnelType: Int!
|
|
||||||
var splitTunnelSites: [String]!
|
|
||||||
|
|
||||||
let vpnReachability = OpenVPNReachability()
|
let vpnReachability = OpenVPNReachability()
|
||||||
|
|
||||||
var startHandler: ((Error?) -> Void)?
|
var startHandler: ((Error?) -> Void)?
|
||||||
var stopHandler: (() -> Void)?
|
var stopHandler: (() -> Void)?
|
||||||
var protoType: TunnelProtoType = .none
|
var protoType: TunnelProtoType?
|
||||||
|
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let message = String(data: messageData, encoding: .utf8) else {
|
guard let message = String(data: messageData, encoding: .utf8) else {
|
||||||
@@ -85,85 +73,75 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if action == Constants.kActionStatus {
|
if action == Constants.kActionStatus {
|
||||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
handleStatusAppMessage(messageData,
|
||||||
|
completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
override func startTunnel(options: [String : NSObject]? = nil,
|
||||||
dispatchQueue.async {
|
completionHandler: @escaping ((any Error)?) -> Void) {
|
||||||
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
||||||
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
||||||
|
|
||||||
neLog(.info, message: "Start tunnel")
|
neLog(.info, message: "Start tunnel")
|
||||||
|
|
||||||
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
|
if let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol {
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration
|
let providerConfiguration = protocolConfiguration.providerConfiguration
|
||||||
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
||||||
self.protoType = .openvpn
|
protoType = .openvpn
|
||||||
} else if (providerConfiguration?[Constants.wireGuardConfigKey] as? Data) != nil {
|
} else if (providerConfiguration?[Constants.wireGuardConfigKey] as? Data) != nil {
|
||||||
self.protoType = .wireguard
|
protoType = .wireguard
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.protoType = .none
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch self.protoType {
|
guard let protoType else {
|
||||||
|
let error = NSError(domain: "Protocol is not selected", code: 0)
|
||||||
|
completionHandler(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protoType {
|
||||||
case .wireguard:
|
case .wireguard:
|
||||||
self.startWireguard(activationAttemptId: activationAttemptId,
|
startWireguard(activationAttemptId: activationAttemptId,
|
||||||
errorNotifier: errorNotifier,
|
errorNotifier: errorNotifier,
|
||||||
completionHandler: completionHandler)
|
completionHandler: completionHandler)
|
||||||
case .openvpn:
|
case .openvpn:
|
||||||
self.startOpenVPN(completionHandler: completionHandler)
|
startOpenVPN(completionHandler: completionHandler)
|
||||||
case .shadowsocks:
|
|
||||||
break
|
|
||||||
// startShadowSocks(completionHandler: completionHandler)
|
|
||||||
case .none:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
dispatchQueue.async {
|
guard let protoType else {
|
||||||
switch self.protoType {
|
completionHandler()
|
||||||
case .wireguard:
|
return
|
||||||
self.stopWireguard(with: reason, completionHandler: completionHandler)
|
|
||||||
case .openvpn:
|
|
||||||
self.stopOpenVPN(with: reason, completionHandler: completionHandler)
|
|
||||||
case .shadowsocks:
|
|
||||||
break
|
|
||||||
// stopShadowSocks(with: reason, completionHandler: completionHandler)
|
|
||||||
case .none:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch protoType {
|
||||||
|
case .wireguard:
|
||||||
|
stopWireguard(with: reason,
|
||||||
|
completionHandler: completionHandler)
|
||||||
|
case .openvpn:
|
||||||
|
stopOpenVPN(with: reason,
|
||||||
|
completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
|
guard let protoType else {
|
||||||
|
completionHandler?(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
switch protoType {
|
switch protoType {
|
||||||
case .wireguard:
|
case .wireguard:
|
||||||
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
|
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
|
||||||
case .openvpn:
|
case .openvpn:
|
||||||
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
|
||||||
case .shadowsocks:
|
|
||||||
break
|
|
||||||
// handleShadowSocksAppMessage(messageData, completionHandler: completionHandler)
|
|
||||||
case .none:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Network observing methods
|
// MARK: Network observing methods
|
||||||
|
|
||||||
private func startListeningForNetworkChanges() {
|
|
||||||
stopListeningForNetworkChanges()
|
|
||||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func stopListeningForNetworkChanges() {
|
|
||||||
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func observeValue(forKeyPath keyPath: String?,
|
override func observeValue(forKeyPath keyPath: String?,
|
||||||
of object: Any?,
|
of object: Any?,
|
||||||
change: [NSKeyValueChangeKey: Any]?,
|
change: [NSKeyValueChangeKey: Any]?,
|
||||||
|
|||||||
@@ -50,12 +50,19 @@ public:
|
|||||||
|
|
||||||
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
||||||
void checkStatus();
|
void checkStatus();
|
||||||
|
|
||||||
|
bool shareText(const QStringList &filesToSend);
|
||||||
|
QString openFile();
|
||||||
|
|
||||||
|
void requestInetAccess();
|
||||||
signals:
|
signals:
|
||||||
void connectionStateChanged(Vpn::ConnectionState state);
|
void connectionStateChanged(Vpn::ConnectionState state);
|
||||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||||
void importConfigFromOutside(const QString);
|
void importConfigFromOutside(const QString);
|
||||||
void importBackupFromOutside(const QString);
|
void importBackupFromOutside(const QString);
|
||||||
|
|
||||||
|
void finished();
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QEventLoop>
|
||||||
|
|
||||||
#include "../protocols/vpnprotocol.h"
|
#include "../protocols/vpnprotocol.h"
|
||||||
#import "ios_controller_wrapper.h"
|
#import "ios_controller_wrapper.h"
|
||||||
@@ -26,6 +27,15 @@ const char* MessageKey::isOnDemand = "is-on-demand";
|
|||||||
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||||
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||||
|
|
||||||
|
static UIViewController* getViewController() {
|
||||||
|
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||||
|
for (UIWindow *window in windows) {
|
||||||
|
if (window.isKeyWindow) {
|
||||||
|
return window.rootViewController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -703,3 +713,86 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IosController::shareText(const QStringList& filesToSend) {
|
||||||
|
NSMutableArray *sharingItems = [NSMutableArray new];
|
||||||
|
|
||||||
|
for (int i = 0; i < filesToSend.size(); i++) {
|
||||||
|
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
||||||
|
[sharingItems addObject:logFileUrl];
|
||||||
|
}
|
||||||
|
|
||||||
|
UIViewController *qtController = getViewController();
|
||||||
|
if (!qtController) return;
|
||||||
|
|
||||||
|
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||||
|
|
||||||
|
__block bool isAccepted = false;
|
||||||
|
|
||||||
|
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||||
|
isAccepted = completed;
|
||||||
|
emit finished();
|
||||||
|
}];
|
||||||
|
|
||||||
|
[qtController presentViewController:activityController animated:YES completion:nil];
|
||||||
|
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
||||||
|
if (popController) {
|
||||||
|
popController.sourceView = qtController.view;
|
||||||
|
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
return isAccepted;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString IosController::openFile() {
|
||||||
|
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||||
|
|
||||||
|
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||||
|
documentPicker.delegate = documentPickerDelegate;
|
||||||
|
|
||||||
|
UIViewController *qtController = getViewController();
|
||||||
|
if (!qtController) return;
|
||||||
|
|
||||||
|
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||||
|
|
||||||
|
__block QString filePath;
|
||||||
|
|
||||||
|
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||||
|
if (path) {
|
||||||
|
filePath = QString::fromUtf8(path.UTF8String);
|
||||||
|
} else {
|
||||||
|
filePath = QString();
|
||||||
|
}
|
||||||
|
emit finished();
|
||||||
|
};
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IosController::requestInetAccess() {
|
||||||
|
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
|
||||||
|
if (url) {
|
||||||
|
qDebug() << "IosController::requestInetAccess URL error";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSURLSession *session = [NSURLSession sharedSession];
|
||||||
|
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||||
|
if (error) {
|
||||||
|
qDebug() << "IosController::requestInetAccess error:" << error.localizedDescription;
|
||||||
|
} else {
|
||||||
|
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||||
|
QString responseBody = QString::fromUtf8((const char*)data.bytes, data.length);
|
||||||
|
qDebug() << "IosController::requestInetAccess server response:" << httpResponse.statusCode << "\n\n" <<responseBody;
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
[task resume];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#import <NetworkExtension/NetworkExtension.h>
|
#import <NetworkExtension/NetworkExtension.h>
|
||||||
#import <NetworkExtension/NETunnelProviderSession.h>
|
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <UIKit/UIKit.h>
|
||||||
|
#include <Security/Security.h>
|
||||||
|
|
||||||
class IosController;
|
class IosController;
|
||||||
|
|
||||||
@@ -13,3 +15,11 @@ class IosController;
|
|||||||
- (void)vpnConfigurationDidChange:(NSNotification *)notification;
|
- (void)vpnConfigurationDidChange:(NSNotification *)notification;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
||||||
|
|
||||||
|
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||||
|
|
||||||
|
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|||||||
@@ -24,5 +24,22 @@
|
|||||||
// cppController->vpnStatusDidChange(notification);
|
// cppController->vpnStatusDidChange(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation DocumentPickerDelegate
|
||||||
|
|
||||||
|
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||||
|
for (NSURL *url in urls) {
|
||||||
|
if (self.documentPickerClosedCallback) {
|
||||||
|
self.documentPickerClosedCallback([url path]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
||||||
|
if (self.documentPickerClosedCallback) {
|
||||||
|
self.documentPickerClosedCallback(nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard";
|
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Logger logger("WireguardUtilsLinux");
|
Logger logger("WireguardUtilsLinux");
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard";
|
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Logger logger("WireguardUtilsMacos");
|
Logger logger("WireguardUtilsMacos");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include "windowstunnelservice.h"
|
#include "windowstunnelservice.h"
|
||||||
#include "wireguardutilswindows.h"
|
#include "wireguardutilswindows.h"
|
||||||
|
|
||||||
#define TUNNEL_SERVICE_NAME L"WireGuardTunnel$AmneziaVPN"
|
#define TUNNEL_SERVICE_NAME L"AmneziaWGTunnel$AmneziaVPN"
|
||||||
|
|
||||||
class WindowsDaemon final : public Daemon {
|
class WindowsDaemon final : public Daemon {
|
||||||
Q_DISABLE_COPY_MOVE(WindowsDaemon)
|
Q_DISABLE_COPY_MOVE(WindowsDaemon)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
#define TUNNEL_NAMED_PIPE \
|
#define TUNNEL_NAMED_PIPE \
|
||||||
"\\\\." \
|
"\\\\." \
|
||||||
"\\pipe\\ProtectedPrefix\\Administrators\\WireGuard\\AmneziaVPN"
|
"\\pipe\\ProtectedPrefix\\Administrators\\AmneziaWG\\AmneziaVPN"
|
||||||
|
|
||||||
constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000;
|
constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000;
|
||||||
|
|
||||||
|
|||||||
@@ -72,10 +72,13 @@ QMap<amnezia::Proto, QString> ProtocolProps::protocolHumanNames()
|
|||||||
{ Proto::Ikev2, "IKEv2" },
|
{ Proto::Ikev2, "IKEv2" },
|
||||||
{ Proto::L2tp, "L2TP" },
|
{ Proto::L2tp, "L2TP" },
|
||||||
{ Proto::Xray, "XRay" },
|
{ Proto::Xray, "XRay" },
|
||||||
|
{ Proto::SSXray, "ShadowSocks"},
|
||||||
|
|
||||||
|
|
||||||
{ Proto::TorWebSite, "Website in Tor network" },
|
{ Proto::TorWebSite, "Website in Tor network" },
|
||||||
{ Proto::Dns, "DNS Service" },
|
{ Proto::Dns, "DNS Service" },
|
||||||
{ Proto::Sftp, QObject::tr("Sftp service") } };
|
{ Proto::Sftp, QObject::tr("Sftp service") },
|
||||||
|
{ Proto::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<amnezia::Proto, QString> ProtocolProps::protocolDescriptions()
|
QMap<amnezia::Proto, QString> ProtocolProps::protocolDescriptions()
|
||||||
@@ -87,6 +90,8 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p)
|
|||||||
{
|
{
|
||||||
switch (p) {
|
switch (p) {
|
||||||
case Proto::Any: return ServiceType::None;
|
case Proto::Any: return ServiceType::None;
|
||||||
|
case Proto::SSXray: return ServiceType::None;
|
||||||
|
|
||||||
case Proto::OpenVpn: return ServiceType::Vpn;
|
case Proto::OpenVpn: return ServiceType::Vpn;
|
||||||
case Proto::Cloak: return ServiceType::Vpn;
|
case Proto::Cloak: return ServiceType::Vpn;
|
||||||
case Proto::ShadowSocks: return ServiceType::Vpn;
|
case Proto::ShadowSocks: return ServiceType::Vpn;
|
||||||
@@ -98,6 +103,7 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p)
|
|||||||
case Proto::TorWebSite: return ServiceType::Other;
|
case Proto::TorWebSite: return ServiceType::Other;
|
||||||
case Proto::Dns: return ServiceType::Other;
|
case Proto::Dns: return ServiceType::Other;
|
||||||
case Proto::Sftp: return ServiceType::Other;
|
case Proto::Sftp: return ServiceType::Other;
|
||||||
|
case Proto::Socks5Proxy: return ServiceType::Other;
|
||||||
default: return ServiceType::Other;
|
default: return ServiceType::Other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,6 +115,7 @@ int ProtocolProps::getPortForInstall(Proto p)
|
|||||||
case WireGuard:
|
case WireGuard:
|
||||||
case ShadowSocks:
|
case ShadowSocks:
|
||||||
case OpenVpn:
|
case OpenVpn:
|
||||||
|
case Socks5Proxy:
|
||||||
return QRandomGenerator::global()->bounded(30000, 50000);
|
return QRandomGenerator::global()->bounded(30000, 50000);
|
||||||
default:
|
default:
|
||||||
return defaultPort(p);
|
return defaultPort(p);
|
||||||
@@ -131,6 +138,7 @@ int ProtocolProps::defaultPort(Proto p)
|
|||||||
case Proto::TorWebSite: return -1;
|
case Proto::TorWebSite: return -1;
|
||||||
case Proto::Dns: return 53;
|
case Proto::Dns: return 53;
|
||||||
case Proto::Sftp: return 222;
|
case Proto::Sftp: return 222;
|
||||||
|
case Proto::Socks5Proxy: return 38080;
|
||||||
default: return -1;
|
default: return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,6 +158,7 @@ bool ProtocolProps::defaultPortChangeable(Proto p)
|
|||||||
case Proto::TorWebSite: return false;
|
case Proto::TorWebSite: return false;
|
||||||
case Proto::Dns: return false;
|
case Proto::Dns: return false;
|
||||||
case Proto::Sftp: return true;
|
case Proto::Sftp: return true;
|
||||||
|
case Proto::Socks5Proxy: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,7 +169,7 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p)
|
|||||||
case Proto::Any: return TransportProto::Udp;
|
case Proto::Any: return TransportProto::Udp;
|
||||||
case Proto::OpenVpn: return TransportProto::Udp;
|
case Proto::OpenVpn: return TransportProto::Udp;
|
||||||
case Proto::Cloak: return TransportProto::Tcp;
|
case Proto::Cloak: return TransportProto::Tcp;
|
||||||
case Proto::ShadowSocks: return TransportProto::Tcp;
|
case Proto::ShadowSocks: return TransportProto::TcpAndUdp;
|
||||||
case Proto::WireGuard: return TransportProto::Udp;
|
case Proto::WireGuard: return TransportProto::Udp;
|
||||||
case Proto::Awg: return TransportProto::Udp;
|
case Proto::Awg: return TransportProto::Udp;
|
||||||
case Proto::Ikev2: return TransportProto::Udp;
|
case Proto::Ikev2: return TransportProto::Udp;
|
||||||
@@ -171,6 +180,7 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p)
|
|||||||
case Proto::TorWebSite: return TransportProto::Tcp;
|
case Proto::TorWebSite: return TransportProto::Tcp;
|
||||||
case Proto::Dns: return TransportProto::Udp;
|
case Proto::Dns: return TransportProto::Udp;
|
||||||
case Proto::Sftp: return TransportProto::Tcp;
|
case Proto::Sftp: return TransportProto::Tcp;
|
||||||
|
case Proto::Socks5Proxy: return TransportProto::Tcp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +201,7 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p)
|
|||||||
case Proto::TorWebSite: return false;
|
case Proto::TorWebSite: return false;
|
||||||
case Proto::Dns: return false;
|
case Proto::Dns: return false;
|
||||||
case Proto::Sftp: return false;
|
case Proto::Sftp: return false;
|
||||||
|
case Proto::Socks5Proxy: return false;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -83,6 +83,8 @@ namespace amnezia
|
|||||||
constexpr char sftp[] = "sftp";
|
constexpr char sftp[] = "sftp";
|
||||||
constexpr char awg[] = "awg";
|
constexpr char awg[] = "awg";
|
||||||
constexpr char xray[] = "xray";
|
constexpr char xray[] = "xray";
|
||||||
|
constexpr char ssxray[] = "ssxray";
|
||||||
|
constexpr char socks5proxy[] = "socks5proxy";
|
||||||
|
|
||||||
constexpr char configVersion[] = "config_version";
|
constexpr char configVersion[] = "config_version";
|
||||||
|
|
||||||
@@ -173,7 +175,12 @@ namespace amnezia
|
|||||||
constexpr char defaultSubnetCidr[] = "24";
|
constexpr char defaultSubnetCidr[] = "24";
|
||||||
|
|
||||||
constexpr char defaultPort[] = "51820";
|
constexpr char defaultPort[] = "51820";
|
||||||
constexpr char defaultMtu[] = "1420";
|
|
||||||
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
|
constexpr char defaultMtu[] = "1280";
|
||||||
|
#else
|
||||||
|
constexpr char defaultMtu[] = "1376";
|
||||||
|
#endif
|
||||||
constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf";
|
constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf";
|
||||||
constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key";
|
constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key";
|
||||||
constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key";
|
constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key";
|
||||||
@@ -189,7 +196,11 @@ namespace amnezia
|
|||||||
namespace awg
|
namespace awg
|
||||||
{
|
{
|
||||||
constexpr char defaultPort[] = "55424";
|
constexpr char defaultPort[] = "55424";
|
||||||
constexpr char defaultMtu[] = "1420";
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
|
constexpr char defaultMtu[] = "1280";
|
||||||
|
#else
|
||||||
|
constexpr char defaultMtu[] = "1376";
|
||||||
|
#endif
|
||||||
|
|
||||||
constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf";
|
constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf";
|
||||||
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";
|
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";
|
||||||
@@ -206,6 +217,14 @@ namespace amnezia
|
|||||||
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace socks5Proxy
|
||||||
|
{
|
||||||
|
constexpr char defaultUserName[] = "proxy_user";
|
||||||
|
constexpr char defaultPort[] = "38080";
|
||||||
|
|
||||||
|
constexpr char proxyConfigPath[] = "/usr/local/3proxy/conf/3proxy.cfg";
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace protocols
|
} // namespace protocols
|
||||||
|
|
||||||
namespace ProtocolEnumNS
|
namespace ProtocolEnumNS
|
||||||
@@ -214,7 +233,8 @@ namespace amnezia
|
|||||||
|
|
||||||
enum TransportProto {
|
enum TransportProto {
|
||||||
Udp,
|
Udp,
|
||||||
Tcp
|
Tcp,
|
||||||
|
TcpAndUdp
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(TransportProto)
|
Q_ENUM_NS(TransportProto)
|
||||||
|
|
||||||
@@ -228,11 +248,13 @@ namespace amnezia
|
|||||||
Ikev2,
|
Ikev2,
|
||||||
L2tp,
|
L2tp,
|
||||||
Xray,
|
Xray,
|
||||||
|
SSXray,
|
||||||
|
|
||||||
// non-vpn
|
// non-vpn
|
||||||
TorWebSite,
|
TorWebSite,
|
||||||
Dns,
|
Dns,
|
||||||
Sftp
|
Sftp,
|
||||||
|
Socks5Proxy
|
||||||
};
|
};
|
||||||
Q_ENUM_NS(Proto)
|
Q_ENUM_NS(Proto)
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
|
|||||||
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
|
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
|
||||||
case DockerContainer::Awg: return new WireguardProtocol(configuration);
|
case DockerContainer::Awg: return new WireguardProtocol(configuration);
|
||||||
case DockerContainer::Xray: return new XrayProtocol(configuration);
|
case DockerContainer::Xray: return new XrayProtocol(configuration);
|
||||||
|
case DockerContainer::SSXray: return new XrayProtocol(configuration);
|
||||||
#endif
|
#endif
|
||||||
default: return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,6 +233,9 @@ void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
|
|||||||
{
|
{
|
||||||
m_configData = configuration;
|
m_configData = configuration;
|
||||||
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
|
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
|
||||||
|
if (xrayConfiguration.isEmpty()) {
|
||||||
|
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
|
||||||
|
}
|
||||||
m_xrayConfig = xrayConfiguration;
|
m_xrayConfig = xrayConfiguration;
|
||||||
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
|
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
|
||||||
m_remoteAddress = configuration.value(amnezia::config_key::hostName).toString();
|
m_remoteAddress = configuration.value(amnezia::config_key::hostName).toString();
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
<file>server_scripts/openvpn/template.ovpn</file>
|
<file>server_scripts/openvpn/template.ovpn</file>
|
||||||
<file>server_scripts/openvpn/Dockerfile</file>
|
<file>server_scripts/openvpn/Dockerfile</file>
|
||||||
<file>server_scripts/openvpn/start.sh</file>
|
<file>server_scripts/openvpn/start.sh</file>
|
||||||
|
<file>server_scripts/openvpn/password_auth.sh</file>
|
||||||
<file>server_scripts/openvpn_shadowsocks/configure_container.sh</file>
|
<file>server_scripts/openvpn_shadowsocks/configure_container.sh</file>
|
||||||
<file>server_scripts/openvpn_shadowsocks/Dockerfile</file>
|
<file>server_scripts/openvpn_shadowsocks/Dockerfile</file>
|
||||||
<file>server_scripts/openvpn_shadowsocks/run_container.sh</file>
|
<file>server_scripts/openvpn_shadowsocks/run_container.sh</file>
|
||||||
@@ -239,5 +240,10 @@
|
|||||||
<file>images/controls/alert-circle.svg</file>
|
<file>images/controls/alert-circle.svg</file>
|
||||||
<file>images/controls/file-check-2.svg</file>
|
<file>images/controls/file-check-2.svg</file>
|
||||||
<file>ui/qml/Controls2/WarningType.qml</file>
|
<file>ui/qml/Controls2/WarningType.qml</file>
|
||||||
|
<file>ui/qml/Pages2/PageServiceSocksProxySettings.qml</file>
|
||||||
|
<file>server_scripts/socks5_proxy/run_container.sh</file>
|
||||||
|
<file>server_scripts/socks5_proxy/Dockerfile</file>
|
||||||
|
<file>server_scripts/socks5_proxy/configure_container.sh</file>
|
||||||
|
<file>server_scripts/socks5_proxy/start.sh</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "secure_qsettings.h"
|
#include "secure_qsettings.h"
|
||||||
#include "platforms/ios/MobileUtils.h"
|
|
||||||
|
|
||||||
#include "QAead.h"
|
#include "QAead.h"
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
@@ -124,8 +123,27 @@ QByteArray SecureQSettings::backupAppConfig() const
|
|||||||
{
|
{
|
||||||
QJsonObject cfg;
|
QJsonObject cfg;
|
||||||
|
|
||||||
|
const auto needToBackup = [this](const auto &key) {
|
||||||
|
for (const auto &item : m_fieldsToBackup)
|
||||||
|
{
|
||||||
|
if (key == "Conf/installationUuid")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.startsWith(item))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
for (const QString &key : m_settings.allKeys()) {
|
for (const QString &key : m_settings.allKeys()) {
|
||||||
if (key == "Conf/installationUuid") {
|
|
||||||
|
if (!needToBackup(key))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ private:
|
|||||||
mutable QMap<QString, QVariant> m_cache;
|
mutable QMap<QString, QVariant> m_cache;
|
||||||
|
|
||||||
QStringList encryptedKeys; // encode only key listed here
|
QStringList encryptedKeys; // encode only key listed here
|
||||||
|
// only this fields need for backup
|
||||||
|
QStringList m_fieldsToBackup = {
|
||||||
|
"Conf/", "Servers/",
|
||||||
|
};
|
||||||
|
|
||||||
mutable QByteArray m_key;
|
mutable QByteArray m_key;
|
||||||
mutable QByteArray m_iv;
|
mutable QByteArray m_iv;
|
||||||
|
|||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
readarray -t lines < $1
|
||||||
|
current_login=${lines[0]}
|
||||||
|
current_password=${lines[1]}
|
||||||
|
|
||||||
|
credentials_file_path=/opt/amnezia/openvpn/auth_credentials.txt
|
||||||
|
|
||||||
|
saved_login=$(awk 'NR==1' $credentials_file_path)
|
||||||
|
saved_password=$(awk 'NR==2' $credentials_file_path)
|
||||||
|
|
||||||
|
if [ "$current_login" == "$saved_login" ] && [ "$current_password" == "$saved_password" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
@@ -40,6 +40,7 @@ cat > /opt/amnezia/shadowsocks/ss-config.json <<EOF
|
|||||||
"password": "$SHADOWSOCKS_PASSWORD",
|
"password": "$SHADOWSOCKS_PASSWORD",
|
||||||
"server": "0.0.0.0",
|
"server": "0.0.0.0",
|
||||||
"server_port": $SHADOWSOCKS_SERVER_PORT,
|
"server_port": $SHADOWSOCKS_SERVER_PORT,
|
||||||
"timeout": 60
|
"timeout": 60,
|
||||||
|
"mode" : "tcp_and_udp"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ sudo docker run -d \
|
|||||||
--restart always \
|
--restart always \
|
||||||
--cap-add=NET_ADMIN \
|
--cap-add=NET_ADMIN \
|
||||||
-p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp \
|
-p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp \
|
||||||
|
-p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/udp \
|
||||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||||
|
|
||||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
FROM 3proxy/3proxy:latest
|
||||||
|
|
||||||
|
LABEL maintainer="AmneziaVPN"
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/amnezia
|
||||||
|
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||||
|
RUN chmod a+x /opt/amnezia/start.sh
|
||||||
|
|
||||||
|
ENTRYPOINT [ "/bin/sh", "/opt/amnezia/start.sh" ]
|
||||||
|
CMD [ "" ]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo -e "#!/bin/3proxy" > /usr/local/3proxy/conf/3proxy.cfg
|
||||||
|
echo -e "config /usr/local/3proxy/conf/3proxy.cfg" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||||
|
echo -e "timeouts 1 5 30 60 180 1800 15 60" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||||
|
|
||||||
|
echo -e "$SOCKS5_USER" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||||
|
|
||||||
|
echo -e "log /usr/local/3proxy/logs/3proxy.log" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||||
|
echo -e "logformat \"-\\\"\"+_G{\"\"time_unix\"\":%t, \"\"proxy\"\":{\"\"type:\"\":\"\"%N\"\", \"\"port\"\":%p}, \"\"error\"\":{\"\"code\"\":\"\"%E\"\"}, \"\"auth\"\":{\"\"user\"\":\"\"%U\"\"}, \"\"client\"\":{\"\"ip\"\":\"\"%C\"\", \"\"port\"\":%c}, \"\"server\"\":{\"\"ip\"\":\"\"%R\"\", \"\"port\"\":%r}, \"\"bytes\"\":{\"\"sent\"\":%O, \"\"received\"\":%I}, \"\"request\"\":{\"\"hostname\"\":\"\"%n\"\"}, \"\"message\"\":\"\"%T\"\"}\"" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||||
|
echo -e "auth $SOCKS5_AUTH_TYPE" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||||
|
echo -e "socks -p$SOCKS5_PROXY_PORT" >> /usr/local/3proxy/conf/3proxy.cfg
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
sudo docker run -d \
|
||||||
|
--restart always \
|
||||||
|
-p $SOCKS5_PROXY_PORT:$SOCKS5_PROXY_PORT/tcp \
|
||||||
|
--name $CONTAINER_NAME \
|
||||||
|
$CONTAINER_NAME
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
|
||||||
|
|
||||||
|
echo "Container startup"
|
||||||
|
|
||||||
|
/bin/3proxy /usr/local/3proxy/conf/3proxy.cfg
|
||||||
@@ -2944,8 +2944,8 @@ For more detailed information, you can
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="115"/>
|
<location filename="../containers/containers_defs.cpp" line="115"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>بروتوكول IKEv2 - بروتوكول مستقر حديث, اسرع بقليل من الباقي, يسترجع الاتصال بعد خسارة الاشارة. لدية يتمتع بدعم أصلي على أحدث إصدارات Android وiOS.</translation>
|
<translation>بروتوكول IKEv2 - بروتوكول مستقر حديث, اسرع بقليل من الباقي, يسترجع الاتصال بعد خسارة الاشارة.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="118"/>
|
<location filename="../containers/containers_defs.cpp" line="118"/>
|
||||||
|
|||||||
@@ -2850,8 +2850,8 @@ While it offers a blend of security, stability, and speed, it's essential t
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="115"/>
|
<location filename="../containers/containers_defs.cpp" line="115"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>پروتکل IKEv2 پروتکلی پایدار و مدرن که مقداری سریعتر از سایر پروتکلهاست. بعد از قطع سیگنال دوباره اتصال را بازیابی میکند. به صورت پیشفرض بر روی آخرین نسخه دستگاههای اندروید و iOS پیشتیبانی میشود.</translation>
|
<translation>پروتکل IKEv2 پروتکلی پایدار و مدرن که مقداری سریعتر از سایر پروتکلهاست. بعد از قطع سیگنال دوباره اتصال را بازیابی میکند.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="118"/>
|
<location filename="../containers/containers_defs.cpp" line="118"/>
|
||||||
|
|||||||
@@ -1144,7 +1144,7 @@ Already installed containers were found on the server. All installed containers
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="278"/>
|
<location filename="../ui/qml/Pages2/PageSettingsAppSplitTunneling.qml" line="278"/>
|
||||||
<source>Executable file (*.*)</source>
|
<source>Executable file (*.*)</source>
|
||||||
<translation>निष्पादनीय फाइल (*।*)</translation>
|
<translation>निष्पादनीय फाइल (*.*)</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
@@ -1276,7 +1276,7 @@ Already installed containers were found on the server. All installed containers
|
|||||||
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="102"/>
|
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="102"/>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="134"/>
|
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="134"/>
|
||||||
<source>Backup files (*.backup)</source>
|
<source>Backup files (*.backup)</source>
|
||||||
<translation>बैकअप फ़ाइलें (*.बैकअप)</translation>
|
<translation>बैकअप फ़ाइलें (*.backup)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="111"/>
|
<location filename="../ui/qml/Pages2/PageSettingsBackup.qml" line="111"/>
|
||||||
@@ -1480,7 +1480,7 @@ Already installed containers were found on the server. All installed containers
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="143"/>
|
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="143"/>
|
||||||
<source>Logs files (*.log)</source>
|
<source>Logs files (*.log)</source>
|
||||||
<translation>लॉग फ़ाइलें (*.लॉग)</translation>
|
<translation>लॉग फ़ाइलें (*.log)</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="152"/>
|
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="152"/>
|
||||||
@@ -2998,8 +2998,8 @@ For more detailed information, you can
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="124"/>
|
<location filename="../containers/containers_defs.cpp" line="124"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>IKEv2 - आधुनिक स्थिर प्रोटोकॉल, दूसरों की तुलना में थोड़ा तेज़, सिग्नल हानि के बाद कनेक्शन पुनर्स्थापित करता है। इसे Android और iOS के नवीनतम संस्करणों पर मूल समर्थन प्राप्त है.</translation>
|
<translation>IKEv2 - आधुनिक स्थिर प्रोटोकॉल, दूसरों की तुलना में थोड़ा तेज़, सिग्नल हानि के बाद कनेक्शन पुनर्स्थापित करता है।</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="127"/>
|
<location filename="../containers/containers_defs.cpp" line="127"/>
|
||||||
|
|||||||
@@ -2851,8 +2851,8 @@ IKEv2 သည် လုံခြုံရေး၊ တည်ငြိမ်မှ
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="115"/>
|
<location filename="../containers/containers_defs.cpp" line="115"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>IKEv2 - ခေတ်မီတည်ငြိမ်သောပရိုတိုကော၊ အခြားအရာများထက်အနည်းငယ်ပိုမြန်သည်၊ signal ပျောက်ဆုံးပြီးနောက် ချိတ်ဆက်မှုကို ပြန်လည်ရယူပေးသည်. ၎င်းသည် Android နှင့် iOS ၏နောက်ဆုံးဗားရှင်းများတွင် မူရင်းအတိုင်းထောက်ပံ့မှုရရှိသည်.</translation>
|
<translation>IKEv2 - ခေတ်မီတည်ငြိမ်သောပရိုတိုကော၊ အခြားအရာများထက်အနည်းငယ်ပိုမြန်သည်၊ signal ပျောက်ဆုံးပြီးနောက် ချိတ်ဆက်မှုကို ပြန်လည်ရယူပေးသည်.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="118"/>
|
<location filename="../containers/containers_defs.cpp" line="118"/>
|
||||||
|
|||||||
@@ -2997,8 +2997,8 @@ While it offers a blend of security, stability, and speed, it's essential t
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="121"/>
|
<location filename="../containers/containers_defs.cpp" line="121"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>IKEv2 Современный стабильный протокол, немного быстрее других восстанавливает соединение после потери сигнала. Имеет нативную поддержку последних версиий Android и iOS.</translation>
|
<translation>IKEv2 Современный стабильный протокол, немного быстрее других восстанавливает соединение после потери сигнала.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="124"/>
|
<location filename="../containers/containers_defs.cpp" line="124"/>
|
||||||
|
|||||||
@@ -3191,8 +3191,8 @@ While it offers a blend of security, stability, and speed, it's essential t
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="124"/>
|
<location filename="../containers/containers_defs.cpp" line="124"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>IKEv2 сучасний стабільний протокол, трішки швидше за інших відновлює підключення. Підтримується в останніх версіях Android и iOS самими операційними системами.</translation>
|
<translation>IKEv2 сучасний стабільний протокол, трішки швидше за інших відновлює підключення.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="127"/>
|
<location filename="../containers/containers_defs.cpp" line="127"/>
|
||||||
|
|||||||
@@ -2947,8 +2947,8 @@ For more detailed information, you can
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="124"/>
|
<location filename="../containers/containers_defs.cpp" line="124"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>IKEv2 - جدید مستحکم پروٹوکول، دوسروں کے مقابلے میں تھوڑا تیز، سگنل ضائع ہونے کے بعد کنکشن بحال کرتا ہے۔ اسے اینڈرائیڈ اور آئی او ایس کے تازہ ترین ورژنز پر مقامی حمایت حاصل ہے۔</translation>
|
<translation>IKEv2 - جدید مستحکم پروٹوکول، دوسروں کے مقابلے میں تھوڑا تیز، سگنل ضائع ہونے کے بعد کنکشن بحال کرتا ہے۔</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="127"/>
|
<location filename="../containers/containers_defs.cpp" line="127"/>
|
||||||
|
|||||||
@@ -3057,8 +3057,8 @@ WireGuard非常容易被阻挡,因为其独特的数据包签名。与一些
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="115"/>
|
<location filename="../containers/containers_defs.cpp" line="115"/>
|
||||||
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS.</source>
|
<source>IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss.</source>
|
||||||
<translation>IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。</translation>
|
<translation>IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../containers/containers_defs.cpp" line="118"/>
|
<location filename="../containers/containers_defs.cpp" line="118"/>
|
||||||
|
|||||||
@@ -7,10 +7,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include "utilities.h"
|
|
||||||
#include "core/controllers/apiController.h"
|
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "core/errorstrings.h"
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel,
|
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel,
|
||||||
@@ -19,6 +16,7 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
|||||||
const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||||
QObject *parent)
|
QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
|
m_apiController(this),
|
||||||
m_serversModel(serversModel),
|
m_serversModel(serversModel),
|
||||||
m_containersModel(containersModel),
|
m_containersModel(containersModel),
|
||||||
m_clientManagementModel(clientManagementModel),
|
m_clientManagementModel(clientManagementModel),
|
||||||
@@ -29,6 +27,10 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
|
|||||||
connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||||
connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
connect(&m_apiController, &ApiController::configUpdated, this,
|
||||||
|
static_cast<void (ConnectionController::*)(const bool, const QJsonObject &, const int)>(&ConnectionController::openConnection));
|
||||||
|
connect(&m_apiController, qOverload<ErrorCode>(&ApiController::errorOccurred), this, qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred));
|
||||||
|
|
||||||
m_state = Vpn::ConnectionState::Disconnected;
|
m_state = Vpn::ConnectionState::Disconnected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,70 +39,22 @@ void ConnectionController::openConnection()
|
|||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
|
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
|
||||||
{
|
{
|
||||||
emit connectionErrorOccurred(errorString(ErrorCode::AmneziaServiceNotRunning));
|
emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int serverIndex = m_serversModel->getDefaultServerIndex();
|
int serverIndex = m_serversModel->getDefaultServerIndex();
|
||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
|
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
|
||||||
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt()
|
if (serverConfig.value(config_key::configVersion).toInt()
|
||||||
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
&& !m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
ApiController apiController;
|
m_apiController.updateServerConfigFromApi(m_settings->getInstallationUuid(true), serverIndex, serverConfig);
|
||||||
errorCode = apiController.updateServerConfigFromApi(m_settings->getInstallationUuid(true), serverConfig);
|
} else {
|
||||||
if (errorCode != ErrorCode::NoError) {
|
openConnection(false, serverConfig, serverIndex);
|
||||||
emit connectionErrorOccurred(errorString(errorCode));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_serversModel->editServer(serverConfig, serverIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
|
||||||
emit noInstalledContainers();
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
|
||||||
|
|
||||||
if (!m_containersModel->isSupportedByCurrentPlatform(container)) {
|
|
||||||
emit connectionErrorOccurred(tr("The selected protocol is not supported on the current platform"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (container == DockerContainer::None) {
|
|
||||||
emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qApp->processEvents();
|
|
||||||
|
|
||||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
|
||||||
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
|
||||||
|
|
||||||
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
|
||||||
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
|
||||||
errorCode = updateProtocolConfig(container, credentials, containerConfig, serverController);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit connectionErrorOccurred(errorString(errorCode));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto dns = m_serversModel->getDnsPair(serverIndex);
|
|
||||||
serverConfig = m_serversModel->getServerConfig(serverIndex);
|
|
||||||
|
|
||||||
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container, errorCode);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
emit connectionErrorOccurred(tr("unable to create configuration"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionController::closeConnection()
|
void ConnectionController::closeConnection()
|
||||||
@@ -108,9 +62,9 @@ void ConnectionController::closeConnection()
|
|||||||
emit disconnectFromVpn();
|
emit disconnectFromVpn();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ConnectionController::getLastConnectionError()
|
ErrorCode ConnectionController::getLastConnectionError()
|
||||||
{
|
{
|
||||||
return errorString(m_vpnConnection->lastError());
|
return m_vpnConnection->lastError();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||||
@@ -231,6 +185,53 @@ bool ConnectionController::isProtocolConfigExists(const QJsonObject &containerCo
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ConnectionController::openConnection(const bool updateConfig, const QJsonObject &config, const int serverIndex)
|
||||||
|
{
|
||||||
|
// Update config for this server as it was received from API
|
||||||
|
if (updateConfig) {
|
||||||
|
m_serversModel->editServer(config, serverIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
|
||||||
|
emit noInstalledContainers();
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
|
||||||
|
|
||||||
|
if (!m_containersModel->isSupportedByCurrentPlatform(container)) {
|
||||||
|
emit connectionErrorOccurred(tr("The selected protocol is not supported on the current platform"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container == DockerContainer::None) {
|
||||||
|
emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
|
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
|
||||||
|
|
||||||
|
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
||||||
|
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
|
||||||
|
ErrorCode errorCode = updateProtocolConfig(container, credentials, containerConfig, serverController);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit connectionErrorOccurred(errorCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dns = m_serversModel->getDnsPair(serverIndex);
|
||||||
|
|
||||||
|
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, config, containerConfig, container, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
emit connectionErrorOccurred(tr("unable to create configuration"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
ErrorCode ConnectionController::updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials,
|
ErrorCode ConnectionController::updateProtocolConfig(const DockerContainer container, const ServerCredentials &credentials,
|
||||||
QJsonObject &containerConfig, QSharedPointer<ServerController> serverController)
|
QJsonObject &containerConfig, QSharedPointer<ServerController> serverController)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef CONNECTIONCONTROLLER_H
|
#ifndef CONNECTIONCONTROLLER_H
|
||||||
#define CONNECTIONCONTROLLER_H
|
#define CONNECTIONCONTROLLER_H
|
||||||
|
|
||||||
|
#include "core/controllers/apiController.h"
|
||||||
#include "protocols/vpnprotocol.h"
|
#include "protocols/vpnprotocol.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/containers_model.h"
|
#include "ui/models/containers_model.h"
|
||||||
@@ -33,7 +34,7 @@ public slots:
|
|||||||
void openConnection();
|
void openConnection();
|
||||||
void closeConnection();
|
void closeConnection();
|
||||||
|
|
||||||
QString getLastConnectionError();
|
ErrorCode getLastConnectionError();
|
||||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||||
|
|
||||||
void onCurrentContainerUpdated();
|
void onCurrentContainerUpdated();
|
||||||
@@ -49,6 +50,7 @@ signals:
|
|||||||
void connectionStateChanged();
|
void connectionStateChanged();
|
||||||
|
|
||||||
void connectionErrorOccurred(const QString &errorMessage);
|
void connectionErrorOccurred(const QString &errorMessage);
|
||||||
|
void connectionErrorOccurred(ErrorCode errorCode);
|
||||||
void reconnectWithUpdatedContainer(const QString &message);
|
void reconnectWithUpdatedContainer(const QString &message);
|
||||||
|
|
||||||
void noInstalledContainers();
|
void noInstalledContainers();
|
||||||
@@ -60,6 +62,10 @@ private:
|
|||||||
Vpn::ConnectionState getCurrentConnectionState();
|
Vpn::ConnectionState getCurrentConnectionState();
|
||||||
bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container);
|
bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container);
|
||||||
|
|
||||||
|
void openConnection(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
||||||
|
|
||||||
|
ApiController m_apiController;
|
||||||
|
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "core/errorstrings.h"
|
|
||||||
#include "systemController.h"
|
#include "systemController.h"
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include "platforms/android/android_utils.h"
|
#include "platforms/android/android_utils.h"
|
||||||
@@ -101,7 +100,7 @@ void ExportController::generateConnectionConfig(const QString &clientName)
|
|||||||
|
|
||||||
errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, clientName, serverController);
|
errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig, clientName, serverController);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +170,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +188,7 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
|
|||||||
QJsonObject nativeConfig;
|
QJsonObject nativeConfig;
|
||||||
ErrorCode errorCode = generateNativeConfig(DockerContainer::WireGuard, clientName, Proto::WireGuard, nativeConfig);
|
ErrorCode errorCode = generateNativeConfig(DockerContainer::WireGuard, clientName, Proto::WireGuard, nativeConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +208,7 @@ void ExportController::generateAwgConfig(const QString &clientName)
|
|||||||
QJsonObject nativeConfig;
|
QJsonObject nativeConfig;
|
||||||
ErrorCode errorCode = generateNativeConfig(DockerContainer::Awg, clientName, Proto::Awg, nativeConfig);
|
ErrorCode errorCode = generateNativeConfig(DockerContainer::Awg, clientName, Proto::Awg, nativeConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +236,7 @@ void ExportController::generateShadowSocksConfig()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +262,7 @@ void ExportController::generateCloakConfig()
|
|||||||
QJsonObject nativeConfig;
|
QJsonObject nativeConfig;
|
||||||
ErrorCode errorCode = generateNativeConfig(DockerContainer::Cloak, "", Proto::Cloak, nativeConfig);
|
ErrorCode errorCode = generateNativeConfig(DockerContainer::Cloak, "", Proto::Cloak, nativeConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +282,7 @@ void ExportController::generateXrayConfig()
|
|||||||
QJsonObject nativeConfig;
|
QJsonObject nativeConfig;
|
||||||
ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, "", Proto::Xray, nativeConfig);
|
ErrorCode errorCode = generateNativeConfig(DockerContainer::Xray, "", Proto::Xray, nativeConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,7 +319,7 @@ void ExportController::updateClientManagementModel(const DockerContainer contain
|
|||||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
ErrorCode errorCode = m_clientManagementModel->updateModel(container, credentials, serverController);
|
ErrorCode errorCode = m_clientManagementModel->updateModel(container, credentials, serverController);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +329,7 @@ void ExportController::revokeConfig(const int row, const DockerContainer contain
|
|||||||
ErrorCode errorCode =
|
ErrorCode errorCode =
|
||||||
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
m_clientManagementModel->revokeClient(row, container, credentials, m_serversModel->getProcessedServerIndex(), serverController);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +338,7 @@ void ExportController::renameClient(const int row, const QString &clientName, co
|
|||||||
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
|
||||||
ErrorCode errorCode = m_clientManagementModel->renameClient(row, clientName, container, credentials, serverController);
|
ErrorCode errorCode = m_clientManagementModel->renameClient(row, clientName, container, credentials, serverController);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit exportErrorOccurred(errorString(errorCode));
|
emit exportErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ public slots:
|
|||||||
signals:
|
signals:
|
||||||
void generateConfig(int type);
|
void generateConfig(int type);
|
||||||
void exportErrorOccurred(const QString &errorMessage);
|
void exportErrorOccurred(const QString &errorMessage);
|
||||||
|
void exportErrorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
void exportConfigChanged();
|
void exportConfigChanged();
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,13 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
#include <QRandomGenerator>
|
#include <QRandomGenerator>
|
||||||
|
#include <QUrlQuery>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "core/serialization/serialization.h"
|
||||||
#include "core/errorstrings.h"
|
#include "core/errorstrings.h"
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -87,6 +91,55 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
|
|||||||
bool ImportController::extractConfigFromData(QString data)
|
bool ImportController::extractConfigFromData(QString data)
|
||||||
{
|
{
|
||||||
QString config = data;
|
QString config = data;
|
||||||
|
QString prefix;
|
||||||
|
QString errormsg;
|
||||||
|
|
||||||
|
if (config.startsWith("vless://")) {
|
||||||
|
m_configType = ConfigTypes::Xray;
|
||||||
|
m_config = extractXrayConfig(Utils::JsonToString(serialization::vless::Deserialize(config, &prefix, &errormsg),
|
||||||
|
QJsonDocument::JsonFormat::Compact), prefix);
|
||||||
|
return m_config.empty() ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.startsWith("vmess://") && config.contains("@")) {
|
||||||
|
m_configType = ConfigTypes::Xray;
|
||||||
|
m_config = extractXrayConfig(Utils::JsonToString(serialization::vmess_new::Deserialize(config, &prefix, &errormsg),
|
||||||
|
QJsonDocument::JsonFormat::Compact), prefix);
|
||||||
|
return m_config.empty() ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.startsWith("vmess://")) {
|
||||||
|
m_configType = ConfigTypes::Xray;
|
||||||
|
m_config = extractXrayConfig(Utils::JsonToString(serialization::vmess::Deserialize(config, &prefix, &errormsg),
|
||||||
|
QJsonDocument::JsonFormat::Compact), prefix);
|
||||||
|
return m_config.empty() ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.startsWith("trojan://")) {
|
||||||
|
m_configType = ConfigTypes::Xray;
|
||||||
|
m_config = extractXrayConfig(Utils::JsonToString(serialization::trojan::Deserialize(config, &prefix, &errormsg),
|
||||||
|
QJsonDocument::JsonFormat::Compact), prefix);
|
||||||
|
return m_config.empty() ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.startsWith("ss://") && !config.contains("plugin=")) {
|
||||||
|
m_configType = ConfigTypes::ShadowSocks;
|
||||||
|
m_config = extractXrayConfig(Utils::JsonToString(serialization::ss::Deserialize(config, &prefix, &errormsg),
|
||||||
|
QJsonDocument::JsonFormat::Compact), prefix);
|
||||||
|
return m_config.empty() ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.startsWith("ssd://")) {
|
||||||
|
QStringList tmp;
|
||||||
|
QList<std::pair<QString, QJsonObject>> servers = serialization::ssd::Deserialize(config, &prefix, &tmp);
|
||||||
|
m_configType = ConfigTypes::ShadowSocks;
|
||||||
|
// Took only first config from list
|
||||||
|
if (!servers.isEmpty()) {
|
||||||
|
m_config = extractXrayConfig(servers.first().first);
|
||||||
|
}
|
||||||
|
return m_config.empty() ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
m_configType = checkConfigFormat(config);
|
m_configType = checkConfigFormat(config);
|
||||||
if (m_configType == ConfigTypes::Invalid) {
|
if (m_configType == ConfigTypes::Invalid) {
|
||||||
data.replace("vpn://", "");
|
data.replace("vpn://", "");
|
||||||
@@ -221,7 +274,7 @@ void ImportController::importConfig()
|
|||||||
} else if (m_config.contains(config_key::configVersion)) {
|
} else if (m_config.contains(config_key::configVersion)) {
|
||||||
quint16 crc = qChecksum(QJsonDocument(m_config).toJson());
|
quint16 crc = qChecksum(QJsonDocument(m_config).toJson());
|
||||||
if (m_serversModel->isServerFromApiAlreadyExists(crc)) {
|
if (m_serversModel->isServerFromApiAlreadyExists(crc)) {
|
||||||
emit importErrorOccurred(errorString(ErrorCode::ApiConfigAlreadyAdded), true);
|
emit importErrorOccurred(ErrorCode::ApiConfigAlreadyAdded, true);
|
||||||
} else {
|
} else {
|
||||||
m_config.insert(config_key::crc, crc);
|
m_config.insert(config_key::crc, crc);
|
||||||
|
|
||||||
@@ -231,7 +284,7 @@ void ImportController::importConfig()
|
|||||||
} else {
|
} else {
|
||||||
qDebug() << "Failed to import profile";
|
qDebug() << "Failed to import profile";
|
||||||
qDebug().noquote() << QJsonDocument(m_config).toJson();
|
qDebug().noquote() << QJsonDocument(m_config).toJson();
|
||||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
|
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_config = {};
|
m_config = {};
|
||||||
@@ -308,7 +361,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
|||||||
hostName = hostNameAndPortMatch.captured(1);
|
hostName = hostNameAndPortMatch.captured(1);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Key parameter 'Endpoint' is missing";
|
qDebug() << "Key parameter 'Endpoint' is missing";
|
||||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
|
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +387,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
|||||||
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
|
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
||||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
|
emit importErrorOccurred(ErrorCode::ImportInvalidConfigError, false);
|
||||||
return QJsonObject();
|
return QJsonObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +451,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject ImportController::extractXrayConfig(const QString &data)
|
QJsonObject ImportController::extractXrayConfig(const QString &data, const QString &description)
|
||||||
{
|
{
|
||||||
QJsonParseError parserErr;
|
QJsonParseError parserErr;
|
||||||
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
|
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
|
||||||
@@ -410,8 +463,13 @@ QJsonObject ImportController::extractXrayConfig(const QString &data)
|
|||||||
lastConfig[config_key::isThirdPartyConfig] = true;
|
lastConfig[config_key::isThirdPartyConfig] = true;
|
||||||
|
|
||||||
QJsonObject containers;
|
QJsonObject containers;
|
||||||
|
if (m_configType == ConfigTypes::ShadowSocks) {
|
||||||
|
containers.insert(config_key::ssxray, QJsonValue(lastConfig));
|
||||||
|
containers.insert(config_key::container, QJsonValue("amnezia-ssxray"));
|
||||||
|
} else {
|
||||||
containers.insert(config_key::container, QJsonValue("amnezia-xray"));
|
containers.insert(config_key::container, QJsonValue("amnezia-xray"));
|
||||||
containers.insert(config_key::xray, QJsonValue(lastConfig));
|
containers.insert(config_key::xray, QJsonValue(lastConfig));
|
||||||
|
}
|
||||||
|
|
||||||
QJsonArray arr;
|
QJsonArray arr;
|
||||||
arr.push_back(containers);
|
arr.push_back(containers);
|
||||||
@@ -426,9 +484,17 @@ QJsonObject ImportController::extractXrayConfig(const QString &data)
|
|||||||
|
|
||||||
QJsonObject config;
|
QJsonObject config;
|
||||||
config[config_key::containers] = arr;
|
config[config_key::containers] = arr;
|
||||||
config[config_key::defaultContainer] = "amnezia-xray";
|
|
||||||
config[config_key::description] = m_settings->nextAvailableServerName();
|
|
||||||
|
|
||||||
|
if (m_configType == ConfigTypes::ShadowSocks) {
|
||||||
|
config[config_key::defaultContainer] = "amnezia-ssxray";
|
||||||
|
} else {
|
||||||
|
config[config_key::defaultContainer] = "amnezia-xray";
|
||||||
|
}
|
||||||
|
if (description.isEmpty()) {
|
||||||
|
config[config_key::description] = m_settings->nextAvailableServerName();
|
||||||
|
} else {
|
||||||
|
config[config_key::description] = description;
|
||||||
|
}
|
||||||
config[config_key::hostName] = hostName;
|
config[config_key::hostName] = hostName;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ namespace
|
|||||||
WireGuard,
|
WireGuard,
|
||||||
Awg,
|
Awg,
|
||||||
Xray,
|
Xray,
|
||||||
|
ShadowSocks,
|
||||||
Backup,
|
Backup,
|
||||||
Invalid
|
Invalid
|
||||||
};
|
};
|
||||||
@@ -54,6 +55,7 @@ public slots:
|
|||||||
signals:
|
signals:
|
||||||
void importFinished();
|
void importFinished();
|
||||||
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
|
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
|
||||||
|
void importErrorOccurred(ErrorCode errorCode, bool goToPageHome);
|
||||||
|
|
||||||
void qrDecodingFinished();
|
void qrDecodingFinished();
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ signals:
|
|||||||
private:
|
private:
|
||||||
QJsonObject extractOpenVpnConfig(const QString &data);
|
QJsonObject extractOpenVpnConfig(const QString &data);
|
||||||
QJsonObject extractWireGuardConfig(const QString &data);
|
QJsonObject extractWireGuardConfig(const QString &data);
|
||||||
QJsonObject extractXrayConfig(const QString &data);
|
QJsonObject extractXrayConfig(const QString &data, const QString &description = "");
|
||||||
|
|
||||||
void checkForMaliciousStrings(const QJsonObject &protocolConfig);
|
void checkForMaliciousStrings(const QJsonObject &protocolConfig);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
|
|
||||||
#include "core/controllers/serverController.h"
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/controllers/vpnConfigurationController.h"
|
#include "core/controllers/vpnConfigurationController.h"
|
||||||
#include "core/errorstrings.h"
|
|
||||||
#include "core/networkUtilities.h"
|
#include "core/networkUtilities.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/models/protocols/awgConfigModel.h"
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
@@ -124,6 +123,9 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
} else if (container == DockerContainer::Sftp) {
|
} else if (container == DockerContainer::Sftp) {
|
||||||
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
|
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
|
||||||
containerConfig.insert(config_key::password, Utils::getRandomString(10));
|
containerConfig.insert(config_key::password, Utils::getRandomString(10));
|
||||||
|
} else if (container == DockerContainer::Socks5Proxy) {
|
||||||
|
containerConfig.insert(config_key::userName, protocols::socks5Proxy::defaultUserName);
|
||||||
|
containerConfig.insert(config_key::password, Utils::getRandomString(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||||
@@ -149,7 +151,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
QMap<DockerContainer, QJsonObject> installedContainers;
|
QMap<DockerContainer, QJsonObject> installedContainers;
|
||||||
ErrorCode errorCode = getAlreadyInstalledContainers(serverCredentials, serverController, installedContainers);
|
ErrorCode errorCode = getAlreadyInstalledContainers(serverCredentials, serverController, installedContainers);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +160,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
if (!installedContainers.contains(container)) {
|
if (!installedContainers.contains(container)) {
|
||||||
errorCode = serverController->setupContainer(serverCredentials, container, config);
|
errorCode = serverController->setupContainer(serverCredentials, container, config);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +171,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +206,7 @@ void InstallController::installServer(const DockerContainer container, const QMa
|
|||||||
auto errorCode = vpnConfigurationController.createProtocolConfigForContainer(m_processedServerCredentials, iterator.key(),
|
auto errorCode = vpnConfigurationController.createProtocolConfigForContainer(m_processedServerCredentials, iterator.key(),
|
||||||
containerConfig);
|
containerConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
containerConfigs.append(containerConfig);
|
containerConfigs.append(containerConfig);
|
||||||
@@ -212,7 +214,7 @@ void InstallController::installServer(const DockerContainer container, const QMa
|
|||||||
errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig,
|
errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig,
|
||||||
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -244,7 +246,7 @@ void InstallController::installContainer(const DockerContainer container, const
|
|||||||
auto errorCode =
|
auto errorCode =
|
||||||
vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, iterator.key(), containerConfig);
|
vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, iterator.key(), containerConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_serversModel->addContainerConfig(iterator.key(), containerConfig);
|
m_serversModel->addContainerConfig(iterator.key(), containerConfig);
|
||||||
@@ -252,7 +254,7 @@ void InstallController::installContainer(const DockerContainer container, const
|
|||||||
errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig,
|
errorCode = m_clientManagementModel->appendClient(iterator.key(), serverCredentials, containerConfig,
|
||||||
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -310,7 +312,7 @@ void InstallController::scanServerForInstalledContainers()
|
|||||||
auto errorCode =
|
auto errorCode =
|
||||||
vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, container, containerConfig);
|
vpnConfigurationController.createProtocolConfigForContainer(serverCredentials, container, containerConfig);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_serversModel->addContainerConfig(container, containerConfig);
|
m_serversModel->addContainerConfig(container, containerConfig);
|
||||||
@@ -319,7 +321,7 @@ void InstallController::scanServerForInstalledContainers()
|
|||||||
QString("Admin [%1]").arg(QSysInfo::prettyProductName()),
|
QString("Admin [%1]").arg(QSysInfo::prettyProductName()),
|
||||||
serverController);
|
serverController);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -334,7 +336,7 @@ void InstallController::scanServerForInstalledContainers()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
||||||
@@ -363,7 +365,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
|||||||
if (containerInfo.isEmpty()) {
|
if (containerInfo.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
|
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z0-9]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
|
||||||
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
|
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
|
||||||
if (containerAndPortMatch.hasMatch()) {
|
if (containerAndPortMatch.hasMatch()) {
|
||||||
QString name = containerAndPortMatch.captured(1);
|
QString name = containerAndPortMatch.captured(1);
|
||||||
@@ -428,6 +430,20 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
|||||||
|
|
||||||
containerConfig.insert(config_key::userName, userName);
|
containerConfig.insert(config_key::userName, userName);
|
||||||
containerConfig.insert(config_key::password, password);
|
containerConfig.insert(config_key::password, password);
|
||||||
|
} else if (protocol == Proto::Socks5Proxy) {
|
||||||
|
QString proxyConfig = serverController->getTextFileFromContainer(container, credentials,
|
||||||
|
protocols::socks5Proxy::proxyConfigPath, errorCode);
|
||||||
|
|
||||||
|
const static QRegularExpression usernameAndPasswordRegExp("users (\\w+):CL:(\\w+)");
|
||||||
|
QRegularExpressionMatch usernameAndPasswordMatch = usernameAndPasswordRegExp.match(proxyConfig);
|
||||||
|
|
||||||
|
if (usernameAndPasswordMatch.hasMatch()) {
|
||||||
|
QString userName = usernameAndPasswordMatch.captured(1);
|
||||||
|
QString password = usernameAndPasswordMatch.captured(2);
|
||||||
|
|
||||||
|
containerConfig.insert(config_key::userName, userName);
|
||||||
|
containerConfig.insert(config_key::password, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
config.insert(config_key::container, ContainerProps::containerToString(container));
|
||||||
@@ -515,7 +531,7 @@ void InstallController::updateContainer(QJsonObject config)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallController::rebootProcessedServer()
|
void InstallController::rebootProcessedServer()
|
||||||
@@ -528,7 +544,7 @@ void InstallController::rebootProcessedServer()
|
|||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
emit rebootProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName));
|
emit rebootProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName));
|
||||||
} else {
|
} else {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +568,7 @@ void InstallController::removeAllContainers()
|
|||||||
emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName));
|
emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallController::removeProcessedContainer()
|
void InstallController::removeProcessedContainer()
|
||||||
@@ -570,7 +586,7 @@ void InstallController::removeProcessedContainer()
|
|||||||
emit removeProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName, serverName));
|
emit removeProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName, serverName));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallController::removeApiConfig(const int serverIndex)
|
void InstallController::removeApiConfig(const int serverIndex)
|
||||||
@@ -604,6 +620,10 @@ void InstallController::clearCachedProfile(QSharedPointer<ServerController> serv
|
|||||||
|
|
||||||
int serverIndex = m_serversModel->getProcessedServerIndex();
|
int serverIndex = m_serversModel->getProcessedServerIndex();
|
||||||
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getProcessedContainerIndex());
|
DockerContainer container = static_cast<DockerContainer>(m_containersModel->getProcessedContainerIndex());
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Other) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
|
||||||
ServerCredentials serverCredentials =
|
ServerCredentials serverCredentials =
|
||||||
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
|
qvariant_cast<ServerCredentials>(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole));
|
||||||
@@ -738,7 +758,7 @@ bool InstallController::checkSshConnection(QSharedPointer<ServerController> serv
|
|||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
m_processedServerCredentials.secretData = decryptedPrivateKey;
|
m_processedServerCredentials.secretData = decryptedPrivateKey;
|
||||||
} else {
|
} else {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -747,7 +767,7 @@ bool InstallController::checkSshConnection(QSharedPointer<ServerController> serv
|
|||||||
output = serverController->checkSshConnection(m_processedServerCredentials, errorCode);
|
output = serverController->checkSshConnection(m_processedServerCredentials, errorCode);
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit installationErrorOccurred(errorString(errorCode));
|
emit installationErrorOccurred(errorCode);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
if (output.contains(tr("Please login as the user"))) {
|
if (output.contains(tr("Please login as the user"))) {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ signals:
|
|||||||
void removeProcessedContainerFinished(const QString &finishedMessage);
|
void removeProcessedContainerFinished(const QString &finishedMessage);
|
||||||
|
|
||||||
void installationErrorOccurred(const QString &errorMessage);
|
void installationErrorOccurred(const QString &errorMessage);
|
||||||
|
void installationErrorOccurred(ErrorCode errorCode);
|
||||||
|
|
||||||
void serverAlreadyExists(int serverIndex);
|
void serverAlreadyExists(int serverIndex);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "pageController.h"
|
#include "pageController.h"
|
||||||
|
#include "utils/converter.h"
|
||||||
|
#include "core/errorstrings.h"
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
@@ -39,6 +41,8 @@ PageController::PageController(const QSharedPointer<ServersModel> &serversModel,
|
|||||||
connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); });
|
connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); });
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
connect(this, qOverload<ErrorCode>(&PageController::showErrorMessage), this, &PageController::onShowErrorMessage);
|
||||||
|
|
||||||
m_isTriggeredByConnectButton = false;
|
m_isTriggeredByConnectButton = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,3 +165,13 @@ int PageController::getDrawerDepth()
|
|||||||
{
|
{
|
||||||
return m_drawerDepth;
|
return m_drawerDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PageController::onShowErrorMessage(ErrorCode errorCode)
|
||||||
|
{
|
||||||
|
const auto fullErrorMessage = errorString(errorCode);
|
||||||
|
const auto errorMessage = fullErrorMessage.mid(fullErrorMessage.indexOf(". ") + 1); // remove ErrorCode %1.
|
||||||
|
const auto errorUrl = QStringLiteral("https://docs.amnezia.org/troubleshooting/error-codes/#error-%1-%2").arg(static_cast<int>(errorCode)).arg(utils::enumToString(errorCode).toLower());
|
||||||
|
const auto fullMessage = QStringLiteral("<a href=\"%1\" style=\"color: #FBB26A;\">ErrorCode: %2</a>. %3").arg(errorUrl).arg(static_cast<int>(errorCode)).arg(errorMessage);
|
||||||
|
|
||||||
|
emit showErrorMessage(fullMessage);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include "core/defs.h"
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
|
|
||||||
namespace PageLoader
|
namespace PageLoader
|
||||||
@@ -34,6 +35,7 @@ namespace PageLoader
|
|||||||
PageServiceSftpSettings,
|
PageServiceSftpSettings,
|
||||||
PageServiceTorWebsiteSettings,
|
PageServiceTorWebsiteSettings,
|
||||||
PageServiceDnsSettings,
|
PageServiceDnsSettings,
|
||||||
|
PageServiceSocksProxySettings,
|
||||||
|
|
||||||
PageSetupWizardStart,
|
PageSetupWizardStart,
|
||||||
PageSetupWizardCredentials,
|
PageSetupWizardCredentials,
|
||||||
@@ -93,6 +95,9 @@ public slots:
|
|||||||
void setDrawerDepth(const int depth);
|
void setDrawerDepth(const int depth);
|
||||||
int getDrawerDepth();
|
int getDrawerDepth();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onShowErrorMessage(amnezia::ErrorCode errorCode);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void goToPage(PageLoader::PageEnum page, bool slide = true);
|
void goToPage(PageLoader::PageEnum page, bool slide = true);
|
||||||
void goToStartPage();
|
void goToStartPage();
|
||||||
@@ -107,6 +112,7 @@ signals:
|
|||||||
void restorePageHomeState(bool isContainerInstalled = false);
|
void restorePageHomeState(bool isContainerInstalled = false);
|
||||||
void replaceStartPage();
|
void replaceStartPage();
|
||||||
|
|
||||||
|
void showErrorMessage(amnezia::ErrorCode);
|
||||||
void showErrorMessage(const QString &errorMessage);
|
void showErrorMessage(const QString &errorMessage);
|
||||||
void showNotificationMessage(const QString &message);
|
void showNotificationMessage(const QString &message);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
#include "platforms/ios/MobileUtils.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -46,13 +46,19 @@ void SystemController::saveFile(QString fileName, const QString &data)
|
|||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
QStringList filesToSend;
|
QStringList filesToSend;
|
||||||
filesToSend.append(fileUrl.toString());
|
filesToSend.append(fileUrl.toString());
|
||||||
MobileUtils mobileUtils;
|
|
||||||
// todo check if save successful
|
// todo check if save successful
|
||||||
mobileUtils.shareText(filesToSend);
|
IosController::Instance()->shareText(filesToSend);
|
||||||
return;
|
return;
|
||||||
#else
|
#else
|
||||||
QFileInfo fi(fileName);
|
QFileInfo fi(fileName);
|
||||||
QDesktopServices::openUrl(fi.absoluteDir().absolutePath());
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
const auto url = "file://" + fi.absoluteDir().absolutePath();
|
||||||
|
#else
|
||||||
|
const auto url = fi.absoluteDir().absolutePath();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QDesktopServices::openUrl(url);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +73,7 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
|
|||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
|
|
||||||
MobileUtils mobileUtils;
|
fileName = IosController::Instance()->openFile();
|
||||||
fileName = mobileUtils.openFile();
|
|
||||||
if (fileName.isEmpty()) {
|
if (fileName.isEmpty()) {
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,7 +281,8 @@ ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const S
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < peerList.size() && i < transferredDataList.size(); ++i) {
|
for (int i = 0; i < peerList.size() && i < transferredDataList.size() && i < latestHandshakeList.size(); ++i) {
|
||||||
|
|
||||||
const auto transferredData = getStrValue(transferredDataList[i]).split(",");
|
const auto transferredData = getStrValue(transferredDataList[i]).split(",");
|
||||||
auto latestHandshake = getStrValue(latestHandshakeList[i]);
|
auto latestHandshake = getStrValue(latestHandshakeList[i]);
|
||||||
auto serverBytesReceived = transferredData.front().trimmed();
|
auto serverBytesReceived = transferredData.front().trimmed();
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ bool OpenVpnConfigModel::setData(const QModelIndex &index, const QVariant &value
|
|||||||
break;
|
break;
|
||||||
case Roles::AdditionalServerCommandsRole:
|
case Roles::AdditionalServerCommandsRole:
|
||||||
m_protocolConfig.insert(config_key::additional_server_config, value.toString());
|
m_protocolConfig.insert(config_key::additional_server_config, value.toString());
|
||||||
|
break;
|
||||||
|
case Roles::AuthLogin:
|
||||||
|
|
||||||
|
break;
|
||||||
|
case Roles::AuthPassword:
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +78,8 @@ QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const
|
|||||||
case Roles::IsPortEditable: return m_container == DockerContainer::OpenVpn ? true : false;
|
case Roles::IsPortEditable: return m_container == DockerContainer::OpenVpn ? true : false;
|
||||||
case Roles::IsTransportProtoEditable: return m_container == DockerContainer::OpenVpn ? true : false;
|
case Roles::IsTransportProtoEditable: return m_container == DockerContainer::OpenVpn ? true : false;
|
||||||
case Roles::HasRemoveButton: return m_container == DockerContainer::OpenVpn ? true : false;
|
case Roles::HasRemoveButton: return m_container == DockerContainer::OpenVpn ? true : false;
|
||||||
|
case Roles::AuthLogin:return {};
|
||||||
|
case Roles::AuthPassword: return {};
|
||||||
}
|
}
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
@@ -146,6 +154,8 @@ QHash<int, QByteArray> OpenVpnConfigModel::roleNames() const
|
|||||||
roles[IsTransportProtoEditable] = "isTransportProtoEditable";
|
roles[IsTransportProtoEditable] = "isTransportProtoEditable";
|
||||||
|
|
||||||
roles[HasRemoveButton] = "hasRemoveButton";
|
roles[HasRemoveButton] = "hasRemoveButton";
|
||||||
|
roles[AuthLogin] = "authLogin";
|
||||||
|
roles[AuthPassword] = "authPassword";
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ public:
|
|||||||
IsPortEditable,
|
IsPortEditable,
|
||||||
IsTransportProtoEditable,
|
IsTransportProtoEditable,
|
||||||
|
|
||||||
HasRemoveButton
|
HasRemoveButton,
|
||||||
|
|
||||||
|
AuthLogin,
|
||||||
|
AuthPassword,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit OpenVpnConfigModel(QObject *parent = nullptr);
|
explicit OpenVpnConfigModel(QObject *parent = nullptr);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const
|
|||||||
case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings;
|
case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings;
|
||||||
case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings;
|
case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings;
|
||||||
case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings;
|
case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings;
|
||||||
|
case Proto::Socks5Proxy: return PageLoader::PageEnum::PageServiceSocksProxySettings;
|
||||||
default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
|
default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -548,6 +548,8 @@ QStringList ServersModel::getAllInstalledServicesName(const int serverIndex)
|
|||||||
servicesName.append("SFTP");
|
servicesName.append("SFTP");
|
||||||
} else if (container == DockerContainer::TorWebSite) {
|
} else if (container == DockerContainer::TorWebSite) {
|
||||||
servicesName.append("TOR");
|
servicesName.append("TOR");
|
||||||
|
} else if (container == DockerContainer::Socks5Proxy) {
|
||||||
|
servicesName.append("SOCKS5");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -580,6 +582,9 @@ bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
|
|||||||
if (ContainerProps::containerService(container) == ServiceType::Vpn) {
|
if (ContainerProps::containerService(container) == ServiceType::Vpn) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (container == DockerContainer::SSXray) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -612,15 +617,18 @@ bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
|
|||||||
{
|
{
|
||||||
auto server = m_servers.at(m_defaultServerIndex).toObject();
|
auto server = m_servers.at(m_defaultServerIndex).toObject();
|
||||||
auto defaultContainer = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString());
|
auto defaultContainer = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString());
|
||||||
auto containerConfig = server.value(config_key::containers).toArray().at(defaultContainer).toObject();
|
|
||||||
auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(defaultContainer)).toObject();
|
|
||||||
|
|
||||||
|
auto containers = server.value(config_key::containers).toArray();
|
||||||
|
for (auto i = 0; i < containers.size(); i++) {
|
||||||
|
auto container = containers.at(i).toObject();
|
||||||
if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) {
|
if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) {
|
||||||
return !(protocolConfig.value(config_key::last_config).toString().contains("AllowedIPs = 0.0.0.0/0, ::/0"));
|
auto containerConfig = container.value(ContainerProps::containerTypeToString(defaultContainer)).toObject();
|
||||||
|
return !(containerConfig.value(config_key::last_config).toString().contains("AllowedIPs = 0.0.0.0/0, ::/0"));
|
||||||
} else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn
|
} else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn
|
||||||
|| defaultContainer == DockerContainer::ShadowSocks) {
|
|| defaultContainer == DockerContainer::ShadowSocks) {
|
||||||
return !(protocolConfig.value(config_key::last_config).toString().contains("redirect-gateway"));
|
auto containerConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)).toObject();
|
||||||
|
return !(containerConfig.value(config_key::last_config).toString().contains("redirect-gateway"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include "socks5ProxyConfigModel.h"
|
||||||
|
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
|
||||||
|
Socks5ProxyConfigModel::Socks5ProxyConfigModel(QObject *parent) : QAbstractListModel(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int Socks5ProxyConfigModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Socks5ProxyConfigModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
|
||||||
|
case Roles::UserNameRole: m_protocolConfig.insert(config_key::userName, value.toString()); break;
|
||||||
|
case Roles::PasswordRole: m_protocolConfig.insert(config_key::password, value.toString()); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit dataChanged(index, index, QList { role });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Socks5ProxyConfigModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (role) {
|
||||||
|
case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString();
|
||||||
|
case Roles::UserNameRole:
|
||||||
|
return m_protocolConfig.value(config_key::userName).toString();
|
||||||
|
case Roles::PasswordRole: return m_protocolConfig.value(config_key::password).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Socks5ProxyConfigModel::updateModel(const QJsonObject &config)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_container = ContainerProps::containerFromString(config.value(config_key::container).toString());
|
||||||
|
|
||||||
|
m_fullConfig = config;
|
||||||
|
QJsonObject protocolConfig = config.value(config_key::socks5proxy).toObject();
|
||||||
|
|
||||||
|
m_protocolConfig.insert(config_key::userName,
|
||||||
|
protocolConfig.value(config_key::userName).toString());
|
||||||
|
|
||||||
|
m_protocolConfig.insert(config_key::password, protocolConfig.value(config_key::password).toString());
|
||||||
|
|
||||||
|
m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString());
|
||||||
|
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject Socks5ProxyConfigModel::getConfig()
|
||||||
|
{
|
||||||
|
m_fullConfig.insert(config_key::socks5proxy, m_protocolConfig);
|
||||||
|
return m_fullConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> Socks5ProxyConfigModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
|
||||||
|
roles[PortRole] = "port";
|
||||||
|
roles[UserNameRole] = "username";
|
||||||
|
roles[PasswordRole] = "password";
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef SOCKS5PROXYCONFIGMODEL_H
|
||||||
|
#define SOCKS5PROXYCONFIGMODEL_H
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "containers/containers_defs.h"
|
||||||
|
|
||||||
|
class Socks5ProxyConfigModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
PortRole = Qt::UserRole + 1,
|
||||||
|
UserNameRole,
|
||||||
|
PasswordRole
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Socks5ProxyConfigModel(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(const QJsonObject &config);
|
||||||
|
QJsonObject getConfig();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
DockerContainer m_container;
|
||||||
|
QJsonObject m_protocolConfig;
|
||||||
|
QJsonObject m_fullConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SOCKS5PROXYCONFIGMODEL_H
|
||||||
@@ -57,7 +57,17 @@ Popup {
|
|||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
onLinkActivated: function(link) {
|
||||||
|
Qt.openUrlExternally(link)
|
||||||
|
}
|
||||||
|
|
||||||
text: root.text
|
text: root.text
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@@ -426,6 +426,68 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SwitcherType {
|
||||||
|
id: authCredentialsSwitcher
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
parentFlickable: fl
|
||||||
|
|
||||||
|
checked: authLogin !== "" && authPassword !== ""
|
||||||
|
|
||||||
|
text: qsTr("Authentication credentials")
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
//if (!checked) {
|
||||||
|
// additionalServerCommands = ""
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: loginTextField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
parentFlickable: fl
|
||||||
|
|
||||||
|
//enabled: isPortEditable
|
||||||
|
|
||||||
|
headerText: qsTr("Login")
|
||||||
|
textFieldText: authLogin
|
||||||
|
//textField.maximumLength: 5
|
||||||
|
//textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
|
textField.onEditingFinished: {
|
||||||
|
if (textFieldText !== authLogin) {
|
||||||
|
authLogin = textFieldText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyNavigation.tab: autoNegotiateEncryprionSwitcher
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: passwordTextField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 20
|
||||||
|
parentFlickable: fl
|
||||||
|
|
||||||
|
//enabled: isPortEditable
|
||||||
|
|
||||||
|
headerText: qsTr("Password")
|
||||||
|
textFieldText: authPassword
|
||||||
|
//textField.maximumLength: 5
|
||||||
|
//textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
|
textField.onEditingFinished: {
|
||||||
|
if (textFieldText !== authPassword) {
|
||||||
|
authPassword = textFieldText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeyNavigation.tab: autoNegotiateEncryprionSwitcher
|
||||||
|
}
|
||||||
|
|
||||||
BasicButtonType {
|
BasicButtonType {
|
||||||
id: saveRestartButton
|
id: saveRestartButton
|
||||||
|
|
||||||
@@ -449,6 +511,7 @@ PageType {
|
|||||||
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
|
InstallController.updateContainer(OpenVpnConfigModel.getConfig())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,385 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
|
import PageEnum 1.0
|
||||||
|
import ContainerProps 1.0
|
||||||
|
|
||||||
|
import "./"
|
||||||
|
import "../Controls2"
|
||||||
|
import "../Controls2/TextTypes"
|
||||||
|
import "../Config"
|
||||||
|
import "../Components"
|
||||||
|
|
||||||
|
PageType {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
defaultActiveFocusItem: listview
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: InstallController
|
||||||
|
|
||||||
|
function onUpdateContainerFinished() {
|
||||||
|
PageController.showNotificationMessage(qsTr("Settings updated successfully"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: focusItem
|
||||||
|
KeyNavigation.tab: backButton
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: backButtonLayout
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
anchors.topMargin: 20
|
||||||
|
|
||||||
|
BackButtonType {
|
||||||
|
id: backButton
|
||||||
|
KeyNavigation.tab: listview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlickableType {
|
||||||
|
id: fl
|
||||||
|
anchors.top: backButtonLayout.bottom
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
contentHeight: listview.implicitHeight
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: listview
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: listview.contentItem.height
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
interactive: false
|
||||||
|
|
||||||
|
model: Socks5ProxyConfigModel
|
||||||
|
|
||||||
|
onFocusChanged: {
|
||||||
|
if (focus) {
|
||||||
|
listview.currentItem.focusItemId.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
implicitWidth: listview.width
|
||||||
|
implicitHeight: content.implicitHeight
|
||||||
|
|
||||||
|
property alias focusItemId: hostLabel.rightButton
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: content
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
HeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
headerText: qsTr("SOCKS5 settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: hostLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 32
|
||||||
|
|
||||||
|
parentFlickable: fl
|
||||||
|
KeyNavigation.tab: portLabel.rightButton
|
||||||
|
|
||||||
|
text: qsTr("Host")
|
||||||
|
descriptionText: ServersModel.getProcessedServerData("hostName")
|
||||||
|
|
||||||
|
descriptionOnTop: true
|
||||||
|
|
||||||
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
rightImageColor: "#D7D8DB"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: portLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Port")
|
||||||
|
descriptionText: port
|
||||||
|
|
||||||
|
descriptionOnTop: true
|
||||||
|
|
||||||
|
parentFlickable: fl
|
||||||
|
KeyNavigation.tab: usernameLabel.rightButton
|
||||||
|
|
||||||
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
rightImageColor: "#D7D8DB"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: usernameLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("User name")
|
||||||
|
descriptionText: username
|
||||||
|
|
||||||
|
descriptionOnTop: true
|
||||||
|
|
||||||
|
parentFlickable: fl
|
||||||
|
KeyNavigation.tab: passwordLabel.eyeButton
|
||||||
|
|
||||||
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
rightImageColor: "#D7D8DB"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LabelWithButtonType {
|
||||||
|
id: passwordLabel
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: qsTr("Password")
|
||||||
|
descriptionText: password
|
||||||
|
|
||||||
|
descriptionOnTop: true
|
||||||
|
|
||||||
|
parentFlickable: fl
|
||||||
|
eyeButton.KeyNavigation.tab: passwordLabel.rightButton
|
||||||
|
rightButton.KeyNavigation.tab: changeSettingsButton
|
||||||
|
|
||||||
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
||||||
|
rightImageColor: "#D7D8DB"
|
||||||
|
|
||||||
|
buttonImageSource: hideDescription ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg"
|
||||||
|
|
||||||
|
clickedFunction: function() {
|
||||||
|
GC.copyToClipBoard(descriptionText)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
this.rightButton.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawerType2 {
|
||||||
|
id: changeSettingsDrawer
|
||||||
|
parent: root
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
expandedHeight: root.height * 0.9
|
||||||
|
|
||||||
|
onClosed: {
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
focusItem.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedContent: ColumnLayout {
|
||||||
|
property string tempPort: port
|
||||||
|
property string tempUsername: username
|
||||||
|
property string tempPassword: password
|
||||||
|
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 32
|
||||||
|
anchors.leftMargin: 16
|
||||||
|
anchors.rightMargin: 16
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: changeSettingsDrawer
|
||||||
|
function onOpened() {
|
||||||
|
if (!GC.isMobile()) {
|
||||||
|
drawerFocusItem.forceActiveFocus()
|
||||||
|
}
|
||||||
|
tempPort = port
|
||||||
|
tempUsername = username
|
||||||
|
tempPassword = password
|
||||||
|
}
|
||||||
|
function onClosed() {
|
||||||
|
port = tempPort
|
||||||
|
username = tempUsername
|
||||||
|
password = tempPassword
|
||||||
|
portTextField.textFieldText = port
|
||||||
|
usernameTextField.textFieldText = username
|
||||||
|
passwordTextField.textFieldText = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: drawerFocusItem
|
||||||
|
KeyNavigation.tab: portTextField.textField
|
||||||
|
}
|
||||||
|
|
||||||
|
HeaderType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
headerText: qsTr("SOCKS5 settings")
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: portTextField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 40
|
||||||
|
parentFlickable: fl
|
||||||
|
|
||||||
|
headerText: qsTr("Port")
|
||||||
|
textFieldText: port
|
||||||
|
textField.maximumLength: 5
|
||||||
|
textField.validator: IntValidator { bottom: 1; top: 65535 }
|
||||||
|
|
||||||
|
textField.onEditingFinished: {
|
||||||
|
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
|
if (textFieldText !== port) {
|
||||||
|
port = textFieldText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyNavigation.tab: usernameTextField.textField
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: usernameTextField
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
parentFlickable: fl
|
||||||
|
|
||||||
|
headerText: qsTr("Username")
|
||||||
|
textFieldPlaceholderText: "username"
|
||||||
|
textFieldText: username
|
||||||
|
textField.maximumLength: 32
|
||||||
|
|
||||||
|
textField.onEditingFinished: {
|
||||||
|
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
|
if (textFieldText !== username) {
|
||||||
|
username = textFieldText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyNavigation.tab: passwordTextField.textField
|
||||||
|
}
|
||||||
|
|
||||||
|
TextFieldWithHeaderType {
|
||||||
|
id: passwordTextField
|
||||||
|
|
||||||
|
property bool hidePassword: true
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 16
|
||||||
|
parentFlickable: fl
|
||||||
|
|
||||||
|
headerText: qsTr("Password")
|
||||||
|
textFieldPlaceholderText: "password"
|
||||||
|
textFieldText: password
|
||||||
|
textField.maximumLength: 32
|
||||||
|
|
||||||
|
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
|
||||||
|
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
|
||||||
|
: ""
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
hidePassword = !hidePassword
|
||||||
|
}
|
||||||
|
|
||||||
|
textField.onFocusChanged: {
|
||||||
|
textFieldText = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
|
if (textFieldText !== password) {
|
||||||
|
password = textFieldText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyNavigation.tab: saveButton
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: saveButton
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
Layout.bottomMargin: 24
|
||||||
|
|
||||||
|
text: qsTr("Change connection settings")
|
||||||
|
Keys.onTabPressed: lastItemTabClicked(drawerFocusItem)
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
forceActiveFocus()
|
||||||
|
|
||||||
|
if (!portTextField.textField.acceptableInput) {
|
||||||
|
portTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (usernameTextField.textFieldText && passwordTextField.textFieldText === "") {
|
||||||
|
passwordTextField.errorText = qsTr("Password cannot be empty")
|
||||||
|
return
|
||||||
|
} else if (usernameTextField.textFieldText === "" && passwordTextField.textFieldText) {
|
||||||
|
usernameTextField.errorText = qsTr("Username cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||||
|
InstallController.updateContainer(Socks5ProxyConfigModel.getConfig())
|
||||||
|
tempPort = portTextField.textFieldText
|
||||||
|
tempUsername = usernameTextField.textFieldText
|
||||||
|
tempPassword = passwordTextField.textFieldText
|
||||||
|
changeSettingsDrawer.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BasicButtonType {
|
||||||
|
id: changeSettingsButton
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: 24
|
||||||
|
Layout.bottomMargin: 24
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
|
text: qsTr("Change connection settings")
|
||||||
|
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||||
|
|
||||||
|
clickedFunc: function() {
|
||||||
|
forceActiveFocus()
|
||||||
|
changeSettingsDrawer.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ import "../Components"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool isClearCacheVisible: ServersModel.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ContainersModel.getProcessedContainerIndex())
|
||||||
|
|
||||||
defaultActiveFocusItem: focusItem
|
defaultActiveFocusItem: focusItem
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -103,6 +105,7 @@ PageType {
|
|||||||
case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
case ProtocolEnum.Awg: AwgConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||||
case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
case ProtocolEnum.Xray: XrayConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||||
case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||||
|
case ProtocolEnum.Socks5Proxy: Socks5ProxyConfigModel.updateModel(ProtocolsModel.getConfig()); break;
|
||||||
}
|
}
|
||||||
PageController.goToPage(protocolPage);
|
PageController.goToPage(protocolPage);
|
||||||
}
|
}
|
||||||
@@ -124,7 +127,7 @@ PageType {
|
|||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
visible: ServersModel.isProcessedServerHasWriteAccess()
|
visible: root.isClearCacheVisible
|
||||||
KeyNavigation.tab: removeButton
|
KeyNavigation.tab: removeButton
|
||||||
|
|
||||||
text: qsTr("Clear %1 profile").arg(ContainersModel.getProcessedContainerName())
|
text: qsTr("Clear %1 profile").arg(ContainersModel.getProcessedContainerName())
|
||||||
@@ -167,7 +170,7 @@ PageType {
|
|||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
|
|
||||||
visible: ServersModel.isProcessedServerHasWriteAccess()
|
visible: root.isClearCacheVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
LabelWithButtonType {
|
LabelWithButtonType {
|
||||||
|
|||||||
@@ -261,6 +261,11 @@ PageType {
|
|||||||
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
Keys.onTabPressed: lastItemTabClicked(focusItem)
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
|
if (!port.textField.acceptableInput) {
|
||||||
|
port.errorText = qsTr("The port must be in the range of 1 to 65535")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||||
InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex)
|
InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex)
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user