Connection string support for XRay protocol (#777)
* Connection string support for XRay protocol
This commit is contained in:
parent
d8020878d5
commit
e6ee9085a2
39 changed files with 20709 additions and 11 deletions
2
client/3rd/QJsonStruct/.gitignore
vendored
Normal file
2
client/3rd/QJsonStruct/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.user
|
||||
build/
|
19
client/3rd/QJsonStruct/CMakeLists.txt
Normal file
19
client/3rd/QJsonStruct/CMakeLists.txt
Normal file
|
@ -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()
|
21
client/3rd/QJsonStruct/LICENSE
Normal file
21
client/3rd/QJsonStruct/LICENSE
Normal file
|
@ -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.
|
190
client/3rd/QJsonStruct/QJsonIO.hpp
Normal file
190
client/3rd/QJsonStruct/QJsonIO.hpp
Normal file
|
@ -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();
|
||||
}
|
||||
};
|
5
client/3rd/QJsonStruct/QJsonStruct.cmake
Normal file
5
client/3rd/QJsonStruct/QJsonStruct.cmake
Normal file
|
@ -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)
|
215
client/3rd/QJsonStruct/QJsonStruct.hpp
Normal file
215
client/3rd/QJsonStruct/QJsonStruct.hpp
Normal file
|
@ -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;
|
||||
}
|
||||
};
|
74
client/3rd/QJsonStruct/macroexpansion.hpp
Normal file
74
client/3rd/QJsonStruct/macroexpansion.hpp
Normal file
|
@ -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__)
|
16
client/3rd/QJsonStruct/test/CMakeLists.txt
Normal file
16
client/3rd/QJsonStruct/test/CMakeLists.txt
Normal file
|
@ -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)
|
45
client/3rd/QJsonStruct/test/TestIO.hpp
Normal file
45
client/3rd/QJsonStruct/test/TestIO.hpp
Normal file
|
@ -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(){};
|
||||
};
|
19
client/3rd/QJsonStruct/test/TestOut.hpp
Normal file
19
client/3rd/QJsonStruct/test/TestOut.hpp
Normal file
|
@ -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)
|
||||
};
|
17698
client/3rd/QJsonStruct/test/catch.hpp
Normal file
17698
client/3rd/QJsonStruct/test/catch.hpp
Normal file
File diff suppressed because it is too large
Load diff
70
client/3rd/QJsonStruct/test/main.cpp
Normal file
70
client/3rd/QJsonStruct/test/main.cpp
Normal file
|
@ -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;
|
||||
}
|
181
client/3rd/QJsonStruct/test/serialize/main.cpp
Normal file
181
client/3rd/QJsonStruct/test/serialize/main.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -134,6 +134,8 @@ set(HEADERS ${HEADERS}
|
|||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.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
|
||||
|
@ -176,6 +178,14 @@ set(SOURCES ${SOURCES}
|
|||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.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
|
||||
|
|
|
@ -63,6 +63,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
|||
|
||||
case DockerContainer::Xray: return { Proto::Xray };
|
||||
|
||||
case DockerContainer::SSXray: return { Proto::SSXray };
|
||||
|
||||
case DockerContainer::Dns: return { Proto::Dns };
|
||||
|
||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||
|
@ -92,6 +94,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
|||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Xray, "XRay" },
|
||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||
{ DockerContainer::SSXray, "ShadowSocks"},
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
||||
|
@ -257,6 +260,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
|||
case DockerContainer::Awg: return Proto::Awg;
|
||||
case DockerContainer::Xray: return Proto::Xray;
|
||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||
case DockerContainer::SSXray: return Proto::SSXray;
|
||||
|
||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||
case DockerContainer::Dns: return Proto::Dns;
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace amnezia
|
|||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
|
|
|
@ -691,6 +691,30 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||
for (auto &port : fixedPorts) {
|
||||
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);
|
||||
|
||||
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::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(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>();
|
||||
}
|
||||
}
|
||||
|
|
38
client/core/serialization/inbound.cpp
Normal file
38
client/core/serialization/inbound.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include "transfer.h"
|
||||
#include "serialization.h"
|
||||
|
||||
namespace amnezia::serialization::inbounds
|
||||
{
|
||||
|
||||
//"inbounds": [
|
||||
// {
|
||||
// "listen": "127.0.0.1",
|
||||
// "port": 10808,
|
||||
// "protocol": "socks",
|
||||
// "settings": {
|
||||
// "udp": true
|
||||
// }
|
||||
// }
|
||||
//],
|
||||
|
||||
const static QString listen = "127.0.0.1";
|
||||
const static int port = 10808;
|
||||
const static QString protocol = "socks";
|
||||
|
||||
QJsonObject GenerateInboundEntry()
|
||||
{
|
||||
QJsonObject root;
|
||||
QJsonIO::SetValue(root, listen, "listen");
|
||||
QJsonIO::SetValue(root, port, "port");
|
||||
QJsonIO::SetValue(root, protocol, "protocol");
|
||||
QJsonIO::SetValue(root, true, "settings", "udp");
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
} // namespace amnezia::serialization::inbounds
|
||||
|
122
client/core/serialization/outbound.cpp
Normal file
122
client/core/serialization/outbound.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||
// This file is part of the Qv2ray VPN client.
|
||||
//
|
||||
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Copyright (c) 2024 AmneziaVPN
|
||||
// This file has been modified for AmneziaVPN
|
||||
//
|
||||
// This file is based on the work of the Qv2ray VPN client.
|
||||
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||
//
|
||||
// The modified version of this file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include "transfer.h"
|
||||
#include "serialization.h"
|
||||
|
||||
namespace amnezia::serialization::outbounds
|
||||
{
|
||||
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect)
|
||||
{
|
||||
QJsonObject root;
|
||||
JADD(domainStrategy, redirect)
|
||||
return root;
|
||||
}
|
||||
|
||||
QJsonObject GenerateBlackHoleOUT(bool useHTTP)
|
||||
{
|
||||
QJsonObject root;
|
||||
QJsonObject resp;
|
||||
resp.insert("type", useHTTP ? "http" : "none");
|
||||
root.insert("response", resp);
|
||||
return root;
|
||||
}
|
||||
|
||||
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password)
|
||||
{
|
||||
QJsonObject root;
|
||||
JADD(address, port, method, password)
|
||||
return root;
|
||||
}
|
||||
|
||||
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &_servers)
|
||||
{
|
||||
QJsonObject root;
|
||||
QJsonArray x;
|
||||
|
||||
for (const auto &server : _servers)
|
||||
{
|
||||
x.append(GenerateShadowSocksServerOUT(server.address, server.port, server.method, server.password));
|
||||
}
|
||||
|
||||
root.insert("servers", x);
|
||||
return root;
|
||||
}
|
||||
|
||||
QJsonObject GenerateHTTPSOCKSOut(const QString &addr, int port, bool useAuth, const QString &username, const QString &password)
|
||||
{
|
||||
QJsonObject root;
|
||||
QJsonIO::SetValue(root, addr, "servers", 0, "address");
|
||||
QJsonIO::SetValue(root, port, "servers", 0, "port");
|
||||
if (useAuth)
|
||||
{
|
||||
QJsonIO::SetValue(root, username, "servers", 0, "users", 0, "user");
|
||||
QJsonIO::SetValue(root, password, "servers", 0, "users", 0, "pass");
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
QJsonObject GenerateOutboundEntry(const QString &tag, const QString &protocol, const QJsonObject &settings, const QJsonObject &streamSettings,
|
||||
const QJsonObject &mux, const QString &sendThrough)
|
||||
{
|
||||
QJsonObject root;
|
||||
JADD(sendThrough, protocol, settings, tag, streamSettings, mux)
|
||||
return root;
|
||||
}
|
||||
|
||||
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &_servers)
|
||||
{
|
||||
QJsonObject root;
|
||||
QJsonArray x;
|
||||
|
||||
for (const auto &server : _servers)
|
||||
{
|
||||
x.append(GenerateTrojanServerOUT(server.address, server.port, server.password));
|
||||
}
|
||||
|
||||
root.insert("servers", x);
|
||||
return root;
|
||||
}
|
||||
|
||||
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password)
|
||||
{
|
||||
QJsonObject root;
|
||||
JADD(address, port, password)
|
||||
return root;
|
||||
}
|
||||
|
||||
} // namespace amnezia::serialization::outbounds
|
||||
|
66
client/core/serialization/serialization.h
Normal file
66
client/core/serialization/serialization.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef SERIALIZATION_H
|
||||
#define SERIALIZATION_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include "transfer.h"
|
||||
|
||||
namespace amnezia::serialization
|
||||
{
|
||||
namespace vmess
|
||||
{
|
||||
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||
} // namespace vmess
|
||||
|
||||
namespace vmess_new
|
||||
{
|
||||
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||
} // namespace vmess_new
|
||||
|
||||
namespace vless
|
||||
{
|
||||
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
||||
} // namespace vless
|
||||
|
||||
namespace ss
|
||||
{
|
||||
QJsonObject Deserialize(const QString &ss, QString *alias, QString *errMessage);
|
||||
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool isSip002);
|
||||
} // namespace ss
|
||||
|
||||
namespace ssd
|
||||
{
|
||||
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList);
|
||||
} // namespace ssd
|
||||
|
||||
namespace trojan
|
||||
{
|
||||
QJsonObject Deserialize(const QString &trojan, QString *alias, QString *errMessage);
|
||||
const QString Serialize(const TrojanObject &server, const QString &alias);
|
||||
} // namespace trojan
|
||||
|
||||
namespace outbounds
|
||||
{
|
||||
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect);
|
||||
QJsonObject GenerateBlackHoleOUT(bool useHTTP);
|
||||
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &servers);
|
||||
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password);
|
||||
QJsonObject GenerateHTTPSOCKSOut(const QString &address, int port, bool useAuth, const QString &username, const QString &password);
|
||||
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &servers);
|
||||
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password);
|
||||
QJsonObject GenerateOutboundEntry(const QString &tag, //
|
||||
const QString &protocol, //
|
||||
const QJsonObject &settings, //
|
||||
const QJsonObject &streamSettings, //
|
||||
const QJsonObject &mux = {}, //
|
||||
const QString &sendThrough = "0.0.0.0");
|
||||
} // namespace outbounds
|
||||
|
||||
namespace inbounds
|
||||
{
|
||||
QJsonObject GenerateInboundEntry();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SERIALIZATION_H
|
142
client/core/serialization/ss.cpp
Normal file
142
client/core/serialization/ss.cpp
Normal file
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||
// This file is part of the Qv2ray VPN client.
|
||||
//
|
||||
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Copyright (c) 2024 AmneziaVPN
|
||||
// This file has been modified for AmneziaVPN
|
||||
//
|
||||
// This file is based on the work of the Qv2ray VPN client.
|
||||
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||
//
|
||||
// The modified version of this file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||
#include "utilities.h"
|
||||
#include "serialization.h"
|
||||
|
||||
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||
|
||||
namespace amnezia::serialization::ss
|
||||
{
|
||||
QJsonObject Deserialize(const QString &ssUri, QString *alias, QString *errMessage)
|
||||
{
|
||||
ShadowSocksServerObject server;
|
||||
QString d_name;
|
||||
|
||||
// auto ssUri = _ssUri.toStdString();
|
||||
if (ssUri.length() < 5)
|
||||
{
|
||||
*errMessage = QObject::tr("SS URI is too short");
|
||||
}
|
||||
|
||||
auto uri = ssUri.mid(5);
|
||||
auto hashPos = uri.lastIndexOf("#");
|
||||
|
||||
if (hashPos >= 0)
|
||||
{
|
||||
// Get the name/remark
|
||||
d_name = uri.mid(uri.lastIndexOf("#") + 1);
|
||||
uri.truncate(hashPos);
|
||||
}
|
||||
|
||||
auto atPos = uri.indexOf('@');
|
||||
|
||||
if (atPos < 0)
|
||||
{
|
||||
// Old URI scheme
|
||||
QString decoded = QByteArray::fromBase64(uri.toUtf8(), QByteArray::Base64Option::OmitTrailingEquals);
|
||||
auto colonPos = decoded.indexOf(':');
|
||||
|
||||
if (colonPos < 0)
|
||||
{
|
||||
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||
}
|
||||
|
||||
server.method = decoded.left(colonPos);
|
||||
decoded.remove(0, colonPos + 1);
|
||||
atPos = decoded.lastIndexOf('@');
|
||||
|
||||
if (atPos < 0)
|
||||
{
|
||||
*errMessage = QObject::tr("Can't find the at separator between password and hostname");
|
||||
}
|
||||
|
||||
server.password = decoded.mid(0, atPos);
|
||||
decoded.remove(0, atPos + 1);
|
||||
colonPos = decoded.lastIndexOf(':');
|
||||
|
||||
if (colonPos < 0)
|
||||
{
|
||||
*errMessage = QObject::tr("Can't find the colon separator between hostname and port");
|
||||
}
|
||||
|
||||
server.address = decoded.mid(0, colonPos);
|
||||
server.port = decoded.mid(colonPos + 1).toInt();
|
||||
}
|
||||
else
|
||||
{
|
||||
// SIP002 URI scheme
|
||||
auto x = QUrl::fromUserInput(uri);
|
||||
server.address = x.host();
|
||||
server.port = x.port();
|
||||
const auto userInfo = Utils::SafeBase64Decode(x.userName());
|
||||
const auto userInfoSp = userInfo.indexOf(':');
|
||||
|
||||
if (userInfoSp < 0)
|
||||
{
|
||||
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||
return QJsonObject{};
|
||||
}
|
||||
|
||||
const auto method = userInfo.mid(0, userInfoSp);
|
||||
server.method = method;
|
||||
server.password = userInfo.mid(userInfoSp + 1);
|
||||
}
|
||||
|
||||
d_name = QUrl::fromPercentEncoding(d_name.toUtf8());
|
||||
QJsonObject root;
|
||||
QJsonArray outbounds;
|
||||
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ server }), {}));
|
||||
JADD(outbounds)
|
||||
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||
root["inbounds"] = QJsonArray{ inbound };
|
||||
*alias = alias->isEmpty() ? d_name : *alias + "_" + d_name;
|
||||
return root;
|
||||
}
|
||||
|
||||
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool)
|
||||
{
|
||||
QUrl url;
|
||||
const auto plainUserInfo = server.method + ":" + server.password;
|
||||
const auto userinfo = plainUserInfo.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
url.setUserInfo(userinfo);
|
||||
url.setScheme("ss");
|
||||
url.setHost(server.address);
|
||||
url.setPort(server.port);
|
||||
url.setFragment(alias);
|
||||
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
|
||||
}
|
||||
} // namespace amnezia::serialization::ss
|
||||
|
244
client/core/serialization/ssd.cpp
Normal file
244
client/core/serialization/ssd.cpp
Normal file
|
@ -0,0 +1,244 @@
|
|||
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||
// This file is part of the Qv2ray VPN client.
|
||||
//
|
||||
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Copyright (c) 2024 AmneziaVPN
|
||||
// This file has been modified for AmneziaVPN
|
||||
//
|
||||
// This file is based on the work of the Qv2ray VPN client.
|
||||
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||
//
|
||||
// The modified version of this file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* A Naive SSD Decoder for Qv2ray
|
||||
*
|
||||
* @author DuckSoft <realducksoft@gmail.com>
|
||||
* @copyright Licensed under GPLv3.
|
||||
*/
|
||||
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||
#include "utilities.h"
|
||||
#include "serialization.h"
|
||||
|
||||
const inline QString QV2RAY_SSD_DEFAULT_NAME_PATTERN = "%1 - %2 (rate %3)";
|
||||
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||
|
||||
namespace amnezia::serialization::ssd
|
||||
{
|
||||
// These below are super strict checking schemes, but necessary.
|
||||
#define MUST_EXIST(fieldName) \
|
||||
if (!obj.contains((fieldName)) || obj[(fieldName)].isUndefined() || obj[(fieldName)].isNull()) \
|
||||
{ \
|
||||
*logList << QObject::tr("Invalid ssd link: json: field %1 must exist").arg(fieldName); \
|
||||
return {}; \
|
||||
}
|
||||
#define MUST_PORT(fieldName) \
|
||||
MUST_EXIST(fieldName); \
|
||||
if (int value = obj[(fieldName)].toInt(-1); value < 0 || value > 65535) \
|
||||
{ \
|
||||
*logList << QObject::tr("Invalid ssd link: json: field %1 must be valid port number"); \
|
||||
return {}; \
|
||||
}
|
||||
#define MUST_STRING(fieldName) \
|
||||
MUST_EXIST(fieldName); \
|
||||
if (!obj[(fieldName)].isString()) \
|
||||
{ \
|
||||
*logList << QObject::tr("Invalid ssd link: json: field %1 must be of type 'string'").arg(fieldName); \
|
||||
return {}; \
|
||||
}
|
||||
#define MUST_ARRAY(fieldName) \
|
||||
MUST_EXIST(fieldName); \
|
||||
if (!obj[(fieldName)].isArray()) \
|
||||
{ \
|
||||
*logList << QObject::tr("Invalid ssd link: json: field %1 must be an array").arg(fieldName); \
|
||||
return {}; \
|
||||
}
|
||||
|
||||
#define SERVER_SHOULD_BE_OBJECT(server) \
|
||||
if (!server.isObject()) \
|
||||
{ \
|
||||
*logList << QObject::tr("Skipping invalid ssd server: server must be an object"); \
|
||||
continue; \
|
||||
}
|
||||
#define SHOULD_EXIST(fieldName) \
|
||||
if (serverObject[(fieldName)].isUndefined()) \
|
||||
{ \
|
||||
*logList << QObject::tr("Skipping invalid ssd server: missing required field %1").arg(fieldName); \
|
||||
continue; \
|
||||
}
|
||||
#define SHOULD_STRING(fieldName) \
|
||||
SHOULD_EXIST(fieldName); \
|
||||
if (!serverObject[(fieldName)].isString()) \
|
||||
{ \
|
||||
*logList << QObject::tr("Skipping invalid ssd server: field %1 should be of type 'string'").arg(fieldName); \
|
||||
continue; \
|
||||
}
|
||||
|
||||
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList)
|
||||
{
|
||||
// ssd links should begin with "ssd://"
|
||||
if (!uri.startsWith("ssd://"))
|
||||
{
|
||||
*logList << QObject::tr("Invalid ssd link: should begin with ssd://");
|
||||
return {};
|
||||
}
|
||||
|
||||
// decode base64
|
||||
const auto ssdURIBody = uri.mid(6, uri.length() - 6); //(&uri, 6, uri.length() - 6);
|
||||
const auto decodedJSON = Utils::SafeBase64Decode(ssdURIBody).toUtf8();
|
||||
|
||||
if (decodedJSON.length() == 0)
|
||||
{
|
||||
*logList << QObject::tr("Invalid ssd link: base64 parse failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto decodeError = Utils::VerifyJsonString(decodedJSON);
|
||||
if (!decodeError.isEmpty())
|
||||
{
|
||||
*logList << QObject::tr("Invalid ssd link: json parse failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
// casting to object
|
||||
const auto obj = Utils::JsonFromString(decodedJSON);
|
||||
|
||||
// obj.airport
|
||||
MUST_STRING("airport");
|
||||
*groupName = obj["airport"].toString();
|
||||
|
||||
// obj.port
|
||||
MUST_PORT("port");
|
||||
const int port = obj["port"].toInt();
|
||||
|
||||
// obj.encryption
|
||||
MUST_STRING("encryption");
|
||||
const auto encryption = obj["encryption"].toString();
|
||||
|
||||
// check: rc4-md5 is not supported by v2ray-core
|
||||
// TODO: more checks, including all algorithms
|
||||
if (encryption.toLower() == "rc4-md5")
|
||||
{
|
||||
*logList << QObject::tr("Invalid ssd link: rc4-md5 encryption is not supported by v2ray-core");
|
||||
return {};
|
||||
}
|
||||
|
||||
// obj.password
|
||||
MUST_STRING("password");
|
||||
const auto password = obj["password"].toString();
|
||||
// obj.servers
|
||||
MUST_ARRAY("servers");
|
||||
//
|
||||
QList<std::pair<QString, QJsonObject>> serverList;
|
||||
//
|
||||
|
||||
// iterate through the servers
|
||||
for (const auto &server : obj["servers"].toArray())
|
||||
{
|
||||
SERVER_SHOULD_BE_OBJECT(server);
|
||||
const auto serverObject = server.toObject();
|
||||
ShadowSocksServerObject ssObject;
|
||||
|
||||
// encryption
|
||||
ssObject.method = encryption;
|
||||
|
||||
// password
|
||||
ssObject.password = password;
|
||||
|
||||
// address :-> "server"
|
||||
SHOULD_STRING("server");
|
||||
const auto serverAddress = serverObject["server"].toString();
|
||||
ssObject.address = serverAddress;
|
||||
|
||||
// port selection:
|
||||
// normal: use global settings
|
||||
// overriding: use current config
|
||||
if (serverObject["port"].isUndefined())
|
||||
{
|
||||
ssObject.port = port;
|
||||
}
|
||||
else if (auto currPort = serverObject["port"].toInt(-1); (currPort >= 0 && currPort <= 65535))
|
||||
{
|
||||
ssObject.port = currPort;
|
||||
}
|
||||
else
|
||||
{
|
||||
ssObject.port = port;
|
||||
}
|
||||
|
||||
// name decision:
|
||||
// untitled: using server:port as name
|
||||
// entitled: using given name
|
||||
QString nodeName;
|
||||
if (serverObject["remarks"].isUndefined())
|
||||
{
|
||||
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||
}
|
||||
else if (serverObject["remarks"].isString())
|
||||
{
|
||||
nodeName = serverObject["remarks"].toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||
}
|
||||
|
||||
// ratio decision:
|
||||
// unspecified: ratio = 1
|
||||
// specified: use given value
|
||||
double ratio = 1.0;
|
||||
if (auto currRatio = serverObject["ratio"].toDouble(-1.0); currRatio != -1.0)
|
||||
{
|
||||
ratio = currRatio;
|
||||
}
|
||||
// else if (!serverObject["ratio"].isUndefined())
|
||||
// {
|
||||
// //*logList << QObject::tr("Invalid ratio encountered. using fallback value.");
|
||||
// }
|
||||
|
||||
// format the total name of the node.
|
||||
const auto finalName = QV2RAY_SSD_DEFAULT_NAME_PATTERN.arg(*groupName, nodeName).arg(ratio);
|
||||
// appending to the total list
|
||||
QJsonObject root;
|
||||
QJsonArray outbounds;
|
||||
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ ssObject }), {}));
|
||||
root["outbounds"] = outbounds;
|
||||
root["inbounds"] = QJsonArray{ inbound };
|
||||
serverList.append({ finalName, root });
|
||||
}
|
||||
|
||||
// returns the current result
|
||||
return serverList;
|
||||
}
|
||||
#undef MUST_EXIST
|
||||
#undef MUST_PORT
|
||||
#undef MUST_ARRAY
|
||||
#undef MUST_STRING
|
||||
#undef SERVER_SHOULD_BE_OBJECT
|
||||
#undef SHOULD_EXIST
|
||||
#undef SHOULD_STRING
|
||||
} // namespace amnezia::serialization::ssd
|
||||
|
313
client/core/serialization/transfer.h
Normal file
313
client/core/serialization/transfer.h
Normal file
|
@ -0,0 +1,313 @@
|
|||
#ifndef TRANSFER_H
|
||||
#define TRANSFER_H
|
||||
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||
|
||||
#define JADDEx(field) root.insert(#field, field);
|
||||
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||
|
||||
constexpr auto VMESS_USER_ALTERID_DEFAULT = 0;
|
||||
|
||||
namespace amnezia::serialization {
|
||||
|
||||
struct ShadowSocksServerObject
|
||||
{
|
||||
QString address;
|
||||
QString method;
|
||||
QString password;
|
||||
int port;
|
||||
JSONSTRUCT_COMPARE(ShadowSocksServerObject, address, method, password)
|
||||
JSONSTRUCT_REGISTER(ShadowSocksServerObject, F(address, port, method, password))
|
||||
};
|
||||
|
||||
|
||||
struct VMessServerObject
|
||||
{
|
||||
struct UserObject
|
||||
{
|
||||
QString id;
|
||||
int alterId = VMESS_USER_ALTERID_DEFAULT;
|
||||
QString security = "auto";
|
||||
int level = 0;
|
||||
JSONSTRUCT_COMPARE(UserObject, id, alterId, security, level)
|
||||
JSONSTRUCT_REGISTER(UserObject, F(id, alterId, security, level))
|
||||
};
|
||||
|
||||
QString address;
|
||||
int port;
|
||||
QList<UserObject> users;
|
||||
JSONSTRUCT_COMPARE(VMessServerObject, address, port, users)
|
||||
JSONSTRUCT_REGISTER(VMessServerObject, F(address, port, users))
|
||||
};
|
||||
|
||||
|
||||
namespace transfer
|
||||
{
|
||||
|
||||
struct HTTPRequestObject
|
||||
{
|
||||
QString version = "1.1";
|
||||
QString method = "GET";
|
||||
QList<QString> path = { "/" };
|
||||
QMap<QString, QList<QString>> headers;
|
||||
HTTPRequestObject()
|
||||
{
|
||||
headers = {
|
||||
{ "Host", { "www.baidu.com", "www.bing.com" } },
|
||||
{ "User-Agent",
|
||||
{ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46" } },
|
||||
{ "Accept-Encoding", { "gzip, deflate" } },
|
||||
{ "Connection", { "keep-alive" } },
|
||||
{ "Pragma", { "no-cache" } }
|
||||
};
|
||||
}
|
||||
JSONSTRUCT_COMPARE(HTTPRequestObject, version, method, path, headers)
|
||||
JSONSTRUCT_REGISTER(HTTPRequestObject, F(version, method, path, headers))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct HTTPResponseObject
|
||||
{
|
||||
QString version = "1.1";
|
||||
QString status = "200";
|
||||
QString reason = "OK";
|
||||
QMap<QString, QList<QString>> headers;
|
||||
HTTPResponseObject()
|
||||
{
|
||||
headers = { { "Content-Type", { "application/octet-stream", "video/mpeg" } }, //
|
||||
{ "Transfer-Encoding", { "chunked" } }, //
|
||||
{ "Connection", { "keep-alive" } }, //
|
||||
{ "Pragma", { "no-cache" } } };
|
||||
}
|
||||
JSONSTRUCT_COMPARE(HTTPResponseObject, version, status, reason, headers)
|
||||
JSONSTRUCT_REGISTER(HTTPResponseObject, F(version, status, reason, headers))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct TCPHeader_Internal
|
||||
{
|
||||
QString type = "none";
|
||||
HTTPRequestObject request;
|
||||
HTTPResponseObject response;
|
||||
JSONSTRUCT_COMPARE(TCPHeader_Internal, type, request, response)
|
||||
JSONSTRUCT_REGISTER(TCPHeader_Internal, A(type), F(request, response))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct ObfsHeaderObject
|
||||
{
|
||||
QString type = "none";
|
||||
JSONSTRUCT_COMPARE(ObfsHeaderObject, type)
|
||||
JSONSTRUCT_REGISTER(ObfsHeaderObject, F(type))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct TCPObject
|
||||
{
|
||||
TCPHeader_Internal header;
|
||||
JSONSTRUCT_COMPARE(TCPObject, header)
|
||||
JSONSTRUCT_REGISTER(TCPObject, F(header))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct KCPObject
|
||||
{
|
||||
int mtu = 1350;
|
||||
int tti = 50;
|
||||
int uplinkCapacity = 5;
|
||||
int downlinkCapacity = 20;
|
||||
bool congestion = false;
|
||||
int readBufferSize = 2;
|
||||
int writeBufferSize = 2;
|
||||
QString seed;
|
||||
ObfsHeaderObject header;
|
||||
KCPObject(){};
|
||||
JSONSTRUCT_COMPARE(KCPObject, mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, seed, header)
|
||||
JSONSTRUCT_REGISTER(KCPObject, F(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header, seed))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct WebSocketObject
|
||||
{
|
||||
QString path = "/";
|
||||
QMap<QString, QString> headers;
|
||||
int maxEarlyData = 0;
|
||||
bool useBrowserForwarding = false;
|
||||
QString earlyDataHeaderName;
|
||||
JSONSTRUCT_COMPARE(WebSocketObject, path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName)
|
||||
JSONSTRUCT_REGISTER(WebSocketObject, F(path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct HttpObject
|
||||
{
|
||||
QList<QString> host;
|
||||
QString path = "/";
|
||||
QString method = "PUT";
|
||||
QMap<QString, QList<QString>> headers;
|
||||
JSONSTRUCT_COMPARE(HttpObject, host, path, method, headers)
|
||||
JSONSTRUCT_REGISTER(HttpObject, F(host, path, method, headers))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct DomainSocketObject
|
||||
{
|
||||
QString path = "/";
|
||||
JSONSTRUCT_COMPARE(DomainSocketObject, path)
|
||||
JSONSTRUCT_REGISTER(DomainSocketObject, F(path))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct QuicObject
|
||||
{
|
||||
QString security = "none";
|
||||
QString key;
|
||||
ObfsHeaderObject header;
|
||||
JSONSTRUCT_COMPARE(QuicObject, security, key, header)
|
||||
JSONSTRUCT_REGISTER(QuicObject, F(security, key, header))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct gRPCObject
|
||||
{
|
||||
QString serviceName;
|
||||
bool multiMode = false;
|
||||
JSONSTRUCT_COMPARE(gRPCObject, serviceName, multiMode)
|
||||
JSONSTRUCT_REGISTER(gRPCObject, F(serviceName, multiMode))
|
||||
};
|
||||
|
||||
//
|
||||
//
|
||||
struct SockoptObject
|
||||
{
|
||||
int mark = 0;
|
||||
bool tcpFastOpen = false;
|
||||
QString tproxy = "off";
|
||||
int tcpKeepAliveInterval = 0;
|
||||
JSONSTRUCT_COMPARE(SockoptObject, mark, tcpFastOpen, tproxy, tcpKeepAliveInterval)
|
||||
JSONSTRUCT_REGISTER(SockoptObject, F(mark, tcpFastOpen, tproxy, tcpKeepAliveInterval))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct CertificateObject
|
||||
{
|
||||
QString usage = "encipherment";
|
||||
QString certificateFile;
|
||||
QString keyFile;
|
||||
QList<QString> certificate;
|
||||
QList<QString> key;
|
||||
JSONSTRUCT_COMPARE(CertificateObject, usage, certificateFile, keyFile, certificate, key)
|
||||
JSONSTRUCT_REGISTER(CertificateObject, F(usage, certificateFile, keyFile, certificate, key))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct TLSObject
|
||||
{
|
||||
QString serverName;
|
||||
bool allowInsecure = false;
|
||||
bool enableSessionResumption = false;
|
||||
bool disableSystemRoot = false;
|
||||
QList<QString> alpn;
|
||||
QList<QString> pinnedPeerCertificateChainSha256;
|
||||
QList<CertificateObject> certificates;
|
||||
JSONSTRUCT_COMPARE(TLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||
pinnedPeerCertificateChainSha256, certificates)
|
||||
JSONSTRUCT_REGISTER(TLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||
pinnedPeerCertificateChainSha256, certificates))
|
||||
};
|
||||
//
|
||||
//
|
||||
struct XTLSObject
|
||||
{
|
||||
QString serverName;
|
||||
bool allowInsecure = false;
|
||||
bool enableSessionResumption = false;
|
||||
bool disableSystemRoot = false;
|
||||
QList<QString> alpn;
|
||||
QList<CertificateObject> certificates;
|
||||
JSONSTRUCT_COMPARE(XTLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates)
|
||||
JSONSTRUCT_REGISTER(XTLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates))
|
||||
};
|
||||
} // namespace transfer
|
||||
|
||||
//
|
||||
//
|
||||
struct TrojanObject
|
||||
{
|
||||
quint16 port;
|
||||
QString address;
|
||||
QString password;
|
||||
QString sni;
|
||||
bool ignoreCertificate = false;
|
||||
bool ignoreHostname = false;
|
||||
bool reuseSession = false;
|
||||
bool sessionTicket = false;
|
||||
bool reusePort = false;
|
||||
bool tcpFastOpen = false;
|
||||
|
||||
#define _X(name) json[#name] = name
|
||||
QJsonObject toJson() const
|
||||
{
|
||||
QJsonObject json;
|
||||
_X(port);
|
||||
_X(address);
|
||||
_X(password);
|
||||
_X(sni);
|
||||
_X(ignoreCertificate);
|
||||
_X(ignoreHostname);
|
||||
_X(reuseSession);
|
||||
_X(reusePort);
|
||||
_X(sessionTicket);
|
||||
_X(tcpFastOpen);
|
||||
return json;
|
||||
};
|
||||
#undef _X
|
||||
|
||||
#define _X(name, type) name = root[#name].to##type()
|
||||
void loadJson(const QJsonObject &root)
|
||||
{
|
||||
_X(port, Int);
|
||||
_X(address, String);
|
||||
_X(password, String);
|
||||
_X(sni, String);
|
||||
_X(ignoreHostname, Bool);
|
||||
_X(ignoreCertificate, Bool);
|
||||
_X(reuseSession, Bool);
|
||||
_X(reusePort, Bool);
|
||||
_X(sessionTicket, Bool);
|
||||
_X(tcpFastOpen, Bool);
|
||||
}
|
||||
#undef _X
|
||||
|
||||
[[nodiscard]] static TrojanObject fromJson(const QJsonObject &root)
|
||||
{
|
||||
TrojanObject o;
|
||||
o.loadJson(root);
|
||||
return o;
|
||||
}
|
||||
};
|
||||
|
||||
struct StreamSettingsObject
|
||||
{
|
||||
QString network = "tcp";
|
||||
QString security = "none";
|
||||
transfer::SockoptObject sockopt;
|
||||
transfer::TLSObject tlsSettings;
|
||||
transfer::XTLSObject xtlsSettings;
|
||||
transfer::TCPObject tcpSettings;
|
||||
transfer::KCPObject kcpSettings;
|
||||
transfer::WebSocketObject wsSettings;
|
||||
transfer::HttpObject httpSettings;
|
||||
transfer::DomainSocketObject dsSettings;
|
||||
transfer::QuicObject quicSettings;
|
||||
transfer::gRPCObject grpcSettings;
|
||||
JSONSTRUCT_COMPARE(StreamSettingsObject, network, security, sockopt, //
|
||||
tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings)
|
||||
JSONSTRUCT_REGISTER(StreamSettingsObject, F(network, security, sockopt),
|
||||
F(tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings))
|
||||
};
|
||||
|
||||
}
|
||||
#endif //TRANSFER_H
|
271
client/core/serialization/trojan.cpp
Normal file
271
client/core/serialization/trojan.cpp
Normal file
|
@ -0,0 +1,271 @@
|
|||
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||
// This file is part of the Qv2ray VPN client.
|
||||
//
|
||||
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Copyright (c) 2024 AmneziaVPN
|
||||
// This file has been modified for AmneziaVPN
|
||||
//
|
||||
// This file is based on the work of the Qv2ray VPN client.
|
||||
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||
//
|
||||
// The modified version of this file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include <QUrlQuery>
|
||||
#include "serialization.h"
|
||||
|
||||
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||
|
||||
namespace amnezia::serialization::trojan
|
||||
{
|
||||
|
||||
const QString Serialize(const TrojanObject &object, const QString &alias)
|
||||
{
|
||||
|
||||
QUrlQuery query;
|
||||
if (object.ignoreHostname)
|
||||
query.addQueryItem("allowInsecureHostname", "1");
|
||||
if (object.ignoreCertificate)
|
||||
query.addQueryItem("allowInsecureCertificate", "1");
|
||||
if (object.sessionTicket)
|
||||
query.addQueryItem("sessionTicket", "1");
|
||||
if (object.ignoreCertificate || object.ignoreHostname)
|
||||
query.addQueryItem("allowInsecure", "1");
|
||||
if (object.tcpFastOpen)
|
||||
query.addQueryItem("tfo", "1");
|
||||
|
||||
if (!object.sni.isEmpty())
|
||||
query.addQueryItem("sni", object.sni);
|
||||
|
||||
QUrl link;
|
||||
if (!object.password.isEmpty())
|
||||
link.setUserName(object.password, QUrl::DecodedMode);
|
||||
link.setPort(object.port);
|
||||
link.setHost(object.address);
|
||||
link.setFragment(alias);
|
||||
link.setQuery(query);
|
||||
link.setScheme("trojan");
|
||||
|
||||
return link.toString(QUrl::FullyEncoded);
|
||||
}
|
||||
|
||||
QJsonObject Deserialize(const QString &trojanUri, QString *alias, QString *errMessage)
|
||||
{
|
||||
const QString prefix = "trojan://";
|
||||
if (!trojanUri.startsWith(prefix))
|
||||
{
|
||||
*errMessage = ("Invalid Trojan URI");
|
||||
return {};
|
||||
}
|
||||
//
|
||||
const auto trueList = QStringList{ "true", "1", "yes", "y" };
|
||||
const QUrl trojanUrl(trojanUri.trimmed());
|
||||
const QUrlQuery query(trojanUrl.query());
|
||||
*alias = trojanUrl.fragment(QUrl::FullyDecoded);
|
||||
|
||||
auto getQueryValue = [&](const QString &key) {
|
||||
return query.queryItemValue(key, QUrl::FullyDecoded);
|
||||
};
|
||||
//
|
||||
TrojanObject result;
|
||||
result.address = trojanUrl.host();
|
||||
result.password = QUrl::fromPercentEncoding(trojanUrl.userInfo().toUtf8());
|
||||
result.port = trojanUrl.port();
|
||||
// process sni (and also "peer")
|
||||
if (query.hasQueryItem("sni"))
|
||||
{
|
||||
result.sni = getQueryValue("sni");
|
||||
}
|
||||
else if (query.hasQueryItem("peer"))
|
||||
{
|
||||
// This is evil and may be removed in a future version.
|
||||
qWarning() << "use of 'peer' in trojan url is deprecated";
|
||||
result.sni = getQueryValue("peer");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the hostname
|
||||
result.sni = result.address;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
result.tcpFastOpen = trueList.contains(getQueryValue("tfo").toLower());
|
||||
result.sessionTicket = trueList.contains(getQueryValue("sessionTicket").toLower());
|
||||
//
|
||||
bool allowAllInsecure = trueList.contains(getQueryValue("allowInsecure").toLower());
|
||||
result.ignoreHostname = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureHostname").toLower());
|
||||
result.ignoreCertificate = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureCertificate").toLower());
|
||||
|
||||
QJsonObject stream;
|
||||
// handle type
|
||||
const auto hasType = query.hasQueryItem("type");
|
||||
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||
if (type != "tcp")
|
||||
QJsonIO::SetValue(stream, type, "network");
|
||||
|
||||
|
||||
// type-wise settings
|
||||
if (type == "kcp")
|
||||
{
|
||||
const auto hasSeed = query.hasQueryItem("seed");
|
||||
if (hasSeed)
|
||||
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||
|
||||
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||
if (headerType != "none")
|
||||
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||
}
|
||||
else if (type == "http")
|
||||
{
|
||||
const auto hasPath = query.hasQueryItem("path");
|
||||
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||
if (path != "/")
|
||||
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||
|
||||
const auto hasHost = query.hasQueryItem("host");
|
||||
if (hasHost)
|
||||
{
|
||||
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||
}
|
||||
}
|
||||
else if (type == "ws")
|
||||
{
|
||||
const auto hasPath = query.hasQueryItem("path");
|
||||
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||
if (path != "/")
|
||||
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||
|
||||
const auto hasHost = query.hasQueryItem("host");
|
||||
if (hasHost)
|
||||
{
|
||||
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||
}
|
||||
}
|
||||
else if (type == "quic")
|
||||
{
|
||||
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||
if (hasQuicSecurity)
|
||||
{
|
||||
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||
|
||||
if (quicSecurity != "none")
|
||||
{
|
||||
const auto key = query.queryItemValue("key");
|
||||
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||
}
|
||||
|
||||
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||
if (headerType != "none")
|
||||
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||
}
|
||||
}
|
||||
else if (type == "grpc")
|
||||
{
|
||||
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||
if (hasServiceName)
|
||||
{
|
||||
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||
}
|
||||
|
||||
const auto hasMode = query.hasQueryItem("mode");
|
||||
if (hasMode)
|
||||
{
|
||||
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||
}
|
||||
}
|
||||
|
||||
// tls-wise settings
|
||||
const auto hasSecurity = query.hasQueryItem("security");
|
||||
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||
if (security != "none")
|
||||
{
|
||||
QJsonIO::SetValue(stream, security, "security");
|
||||
}
|
||||
// sni
|
||||
const auto hasSNI = query.hasQueryItem("sni");
|
||||
if (hasSNI)
|
||||
{
|
||||
const auto sni = query.queryItemValue("sni");
|
||||
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||
}
|
||||
// alpn
|
||||
const auto hasALPN = query.hasQueryItem("alpn");
|
||||
if (hasALPN)
|
||||
{
|
||||
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||
QStringList aplnElems = alpnRaw.split(",");
|
||||
// h2 protocol is not supported by xray
|
||||
aplnElems.removeAll("h2");
|
||||
if (!aplnElems.isEmpty()) {
|
||||
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||
}
|
||||
}
|
||||
|
||||
if (security == "reality")
|
||||
{
|
||||
if (query.hasQueryItem("fp"))
|
||||
{
|
||||
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||
}
|
||||
if (query.hasQueryItem("pbk"))
|
||||
{
|
||||
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||
}
|
||||
if (query.hasQueryItem("spiderX"))
|
||||
{
|
||||
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||
}
|
||||
if (query.hasQueryItem("sid"))
|
||||
{
|
||||
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject root;
|
||||
QJsonArray outbounds;
|
||||
QJsonObject outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "trojan", outbounds::GenerateTrojanOUT({ result }), {});
|
||||
outbound["streamSettings"] = stream;
|
||||
outbounds.append(outbound);
|
||||
JADD(outbounds)
|
||||
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||
root["inbounds"] = QJsonArray { inbound };
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
} // namespace amnezia::serialization::trojan
|
||||
|
256
client/core/serialization/vless.cpp
Normal file
256
client/core/serialization/vless.cpp
Normal file
|
@ -0,0 +1,256 @@
|
|||
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||
// This file is part of the Qv2ray VPN client.
|
||||
//
|
||||
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Copyright (c) 2024 AmneziaVPN
|
||||
// This file has been modified for AmneziaVPN
|
||||
//
|
||||
// This file is based on the work of the Qv2ray VPN client.
|
||||
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||
//
|
||||
// The modified version of this file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include <QUrlQuery>
|
||||
#include "serialization.h"
|
||||
|
||||
namespace amnezia::serialization::vless
|
||||
{
|
||||
QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
|
||||
{
|
||||
// must start with vless://
|
||||
if (!str.startsWith("vless://"))
|
||||
{
|
||||
*errMessage = QObject::tr("VLESS link should start with vless://");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
// parse url
|
||||
QUrl url(str);
|
||||
if (!url.isValid())
|
||||
{
|
||||
*errMessage = QObject::tr("link parse failed: %1").arg(url.errorString());
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
// fetch host
|
||||
const auto hostRaw = url.host();
|
||||
if (hostRaw.isEmpty())
|
||||
{
|
||||
*errMessage = QObject::tr("empty host");
|
||||
return QJsonObject();
|
||||
}
|
||||
const auto host = (hostRaw.startsWith('[') && hostRaw.endsWith(']')) ? hostRaw.mid(1, hostRaw.length() - 2) : hostRaw;
|
||||
|
||||
// fetch port
|
||||
const auto port = url.port();
|
||||
if (port == -1)
|
||||
{
|
||||
*errMessage = QObject::tr("missing port");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
// fetch remarks
|
||||
const auto remarks = url.fragment();
|
||||
if (!remarks.isEmpty())
|
||||
{
|
||||
*alias = remarks;
|
||||
}
|
||||
|
||||
// fetch uuid
|
||||
const auto uuid = url.userInfo();
|
||||
if (uuid.isEmpty())
|
||||
{
|
||||
*errMessage = QObject::tr("missing uuid");
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
// initialize QJsonObject with basic info
|
||||
QJsonObject outbound;
|
||||
QJsonObject stream;
|
||||
|
||||
QJsonIO::SetValue(outbound, "vless", "protocol");
|
||||
QJsonIO::SetValue(outbound, host, { "settings", "vnext", 0, "address" });
|
||||
QJsonIO::SetValue(outbound, port, { "settings", "vnext", 0, "port" });
|
||||
QJsonIO::SetValue(outbound, uuid, { "settings", "vnext", 0, "users", 0, "id" });
|
||||
|
||||
// parse query
|
||||
QUrlQuery query(url.query());
|
||||
|
||||
// handle type
|
||||
const auto hasType = query.hasQueryItem("type");
|
||||
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||
if (type != "tcp")
|
||||
QJsonIO::SetValue(stream, type, "network");
|
||||
|
||||
// handle encryption
|
||||
const auto hasEncryption = query.hasQueryItem("encryption");
|
||||
const auto encryption = hasEncryption ? query.queryItemValue("encryption") : "none";
|
||||
QJsonIO::SetValue(outbound, encryption, { "settings", "vnext", 0, "users", 0, "encryption" });
|
||||
|
||||
// type-wise settings
|
||||
if (type == "kcp")
|
||||
{
|
||||
const auto hasSeed = query.hasQueryItem("seed");
|
||||
if (hasSeed)
|
||||
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||
|
||||
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||
if (headerType != "none")
|
||||
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||
}
|
||||
else if (type == "http")
|
||||
{
|
||||
const auto hasPath = query.hasQueryItem("path");
|
||||
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||
if (path != "/")
|
||||
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||
|
||||
const auto hasHost = query.hasQueryItem("host");
|
||||
if (hasHost)
|
||||
{
|
||||
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||
}
|
||||
}
|
||||
else if (type == "ws")
|
||||
{
|
||||
const auto hasPath = query.hasQueryItem("path");
|
||||
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||
if (path != "/")
|
||||
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||
|
||||
const auto hasHost = query.hasQueryItem("host");
|
||||
if (hasHost)
|
||||
{
|
||||
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||
}
|
||||
}
|
||||
else if (type == "quic")
|
||||
{
|
||||
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||
if (hasQuicSecurity)
|
||||
{
|
||||
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||
|
||||
if (quicSecurity != "none")
|
||||
{
|
||||
const auto key = query.queryItemValue("key");
|
||||
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||
}
|
||||
|
||||
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||
if (headerType != "none")
|
||||
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||
}
|
||||
}
|
||||
else if (type == "grpc")
|
||||
{
|
||||
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||
if (hasServiceName)
|
||||
{
|
||||
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||
}
|
||||
|
||||
const auto hasMode = query.hasQueryItem("mode");
|
||||
if (hasMode)
|
||||
{
|
||||
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||
}
|
||||
}
|
||||
|
||||
// tls-wise settings
|
||||
const auto hasSecurity = query.hasQueryItem("security");
|
||||
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||
if (security != "none")
|
||||
{
|
||||
QJsonIO::SetValue(stream, security, "security");
|
||||
}
|
||||
// sni
|
||||
const auto hasSNI = query.hasQueryItem("sni");
|
||||
if (hasSNI)
|
||||
{
|
||||
const auto sni = query.queryItemValue("sni");
|
||||
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||
}
|
||||
// alpn
|
||||
const auto hasALPN = query.hasQueryItem("alpn");
|
||||
if (hasALPN)
|
||||
{
|
||||
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||
QStringList aplnElems = alpnRaw.split(",");
|
||||
// h2 protocol is not supported by xray
|
||||
aplnElems.removeAll("h2");
|
||||
if (!aplnElems.isEmpty()) {
|
||||
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||
}
|
||||
}
|
||||
// xtls-specific
|
||||
if (security == "xtls" || security == "reality")
|
||||
{
|
||||
const auto flow = query.queryItemValue("flow");
|
||||
QJsonIO::SetValue(outbound, flow, { "settings", "vnext", 0, "users", 0, "flow" });
|
||||
}
|
||||
|
||||
if (security == "reality")
|
||||
{
|
||||
if (query.hasQueryItem("fp"))
|
||||
{
|
||||
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||
}
|
||||
if (query.hasQueryItem("pbk"))
|
||||
{
|
||||
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||
}
|
||||
if (query.hasQueryItem("spiderX"))
|
||||
{
|
||||
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||
}
|
||||
if (query.hasQueryItem("sid"))
|
||||
{
|
||||
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||
}
|
||||
}
|
||||
|
||||
// assembling config
|
||||
QJsonObject root;
|
||||
outbound["streamSettings"] = stream;
|
||||
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||
root["outbounds"] = QJsonArray{ outbound };
|
||||
root["inbounds"] = QJsonArray { inbound };
|
||||
return root;
|
||||
}
|
||||
} // namespace amnezia::serialization::vless
|
||||
|
344
client/core/serialization/vmess.cpp
Normal file
344
client/core/serialization/vmess.cpp
Normal file
|
@ -0,0 +1,344 @@
|
|||
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||
// This file is part of the Qv2ray VPN client.
|
||||
//
|
||||
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Copyright (c) 2024 AmneziaVPN
|
||||
// This file has been modified for AmneziaVPN
|
||||
//
|
||||
// This file is based on the work of the Qv2ray VPN client.
|
||||
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||
//
|
||||
// The modified version of this file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||
#include <QJsonDocument>
|
||||
#include "transfer.h"
|
||||
#include "utilities.h"
|
||||
#include "serialization.h"
|
||||
|
||||
#define nothing
|
||||
|
||||
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||
|
||||
namespace amnezia::serialization::vmess
|
||||
{
|
||||
// From https://github.com/2dust/v2rayN/wiki/分享链接格式说明(ver-2)
|
||||
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias)
|
||||
{
|
||||
QJsonObject vmessUriRoot;
|
||||
// Constant
|
||||
vmessUriRoot["v"] = 2;
|
||||
vmessUriRoot["ps"] = alias;
|
||||
vmessUriRoot["add"] = server.address;
|
||||
vmessUriRoot["port"] = server.port;
|
||||
vmessUriRoot["id"] = server.users.front().id;
|
||||
vmessUriRoot["aid"] = server.users.front().alterId;
|
||||
const auto scy = server.users.front().security;
|
||||
vmessUriRoot["scy"] = (scy == "aes-128-gcm" || scy == "chacha20-poly1305" || scy == "none" || scy == "zero") ? scy : "auto";
|
||||
vmessUriRoot["net"] = transfer.network == "http" ? "h2" : transfer.network;
|
||||
vmessUriRoot["tls"] = (transfer.security == "tls" || transfer.security == "xtls") ? "tls" : "none";
|
||||
if (transfer.security == "tls")
|
||||
{
|
||||
vmessUriRoot["sni"] = transfer.tlsSettings.serverName;
|
||||
}
|
||||
else if (transfer.security == "xtls")
|
||||
{
|
||||
vmessUriRoot["sni"] = transfer.xtlsSettings.serverName;
|
||||
}
|
||||
|
||||
if (transfer.network == "tcp")
|
||||
{
|
||||
vmessUriRoot["type"] = transfer.tcpSettings.header.type;
|
||||
}
|
||||
else if (transfer.network == "kcp")
|
||||
{
|
||||
vmessUriRoot["type"] = transfer.kcpSettings.header.type;
|
||||
}
|
||||
else if (transfer.network == "quic")
|
||||
{
|
||||
vmessUriRoot["type"] = transfer.quicSettings.header.type;
|
||||
vmessUriRoot["host"] = transfer.quicSettings.security;
|
||||
vmessUriRoot["path"] = transfer.quicSettings.key;
|
||||
}
|
||||
else if (transfer.network == "ws")
|
||||
{
|
||||
auto x = transfer.wsSettings.headers;
|
||||
auto host = x.contains("host");
|
||||
auto CapHost = x.contains("Host");
|
||||
auto realHost = host ? x["host"] : (CapHost ? x["Host"] : "");
|
||||
//
|
||||
vmessUriRoot["host"] = realHost;
|
||||
vmessUriRoot["path"] = transfer.wsSettings.path;
|
||||
}
|
||||
else if (transfer.network == "h2" || transfer.network == "http")
|
||||
{
|
||||
vmessUriRoot["host"] = transfer.httpSettings.host.join(",");
|
||||
vmessUriRoot["path"] = transfer.httpSettings.path;
|
||||
}
|
||||
else if (transfer.network == "grpc")
|
||||
{
|
||||
vmessUriRoot["path"] = transfer.grpcSettings.serviceName;
|
||||
}
|
||||
|
||||
if (!vmessUriRoot.contains("type") || vmessUriRoot["type"].toString().isEmpty())
|
||||
{
|
||||
vmessUriRoot["type"] = "none";
|
||||
}
|
||||
|
||||
//
|
||||
QString jString = Utils::JsonToString(vmessUriRoot, QJsonDocument::JsonFormat::Compact);
|
||||
auto vmessPart = jString.toUtf8().toBase64();
|
||||
return "vmess://" + vmessPart;
|
||||
}
|
||||
|
||||
// This generates global config containing only one outbound....
|
||||
QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMessage)
|
||||
{
|
||||
#define default QJsonObject()
|
||||
QString vmess = vmessStr;
|
||||
|
||||
if (vmess.trimmed() != vmess)
|
||||
{
|
||||
vmess = vmessStr.trimmed();
|
||||
}
|
||||
|
||||
// Reset errMessage
|
||||
*errMessage = "";
|
||||
|
||||
if (!vmess.toLower().startsWith("vmess://"))
|
||||
{
|
||||
*errMessage = QObject::tr("VMess string should start with 'vmess://'");
|
||||
return default;
|
||||
}
|
||||
|
||||
const auto b64Str = vmess.mid(8, vmess.length() - 8);
|
||||
if (b64Str.isEmpty())
|
||||
{
|
||||
*errMessage = QObject::tr("VMess string should be a valid base64 string");
|
||||
return default;
|
||||
}
|
||||
|
||||
auto vmessString = Utils::SafeBase64Decode(b64Str);
|
||||
auto jsonErr = Utils::VerifyJsonString(vmessString);
|
||||
|
||||
if (!jsonErr.isEmpty())
|
||||
{
|
||||
*errMessage = jsonErr;
|
||||
return default;
|
||||
}
|
||||
|
||||
auto vmessConf = Utils::JsonFromString(vmessString);
|
||||
|
||||
if (vmessConf.isEmpty())
|
||||
{
|
||||
*errMessage = QObject::tr("JSON should not be empty");
|
||||
return default;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
QJsonObject root;
|
||||
QString ps, add, id, net, type, host, path, tls, scy, sni;
|
||||
int port, aid;
|
||||
//
|
||||
// __vmess_checker__func(key, values)
|
||||
//
|
||||
// - Key = Key in JSON and the variable name.
|
||||
// - Values = Candidate variable list, if not match, the first one is used as default.
|
||||
//
|
||||
// - [[val.size() <= 1]] is used when only the default value exists.
|
||||
//
|
||||
// - It can be empty, if so, if the key is not in the JSON, or the value is empty, report an error.
|
||||
// - Else if it contains one thing. if the key is not in the JSON, or the value is empty, use that one.
|
||||
// - Else if it contains many things, when the key IS in the JSON but not within the THINGS, use the first in the THINGS
|
||||
// - Else -------------------------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> use the JSON value
|
||||
//
|
||||
#define __vmess_checker__func(key, values) \
|
||||
{ \
|
||||
auto val = QStringList() values; \
|
||||
if (vmessConf.contains(#key) && !vmessConf[#key].toVariant().toString().trimmed().isEmpty() && \
|
||||
(val.size() <= 1 || val.contains(vmessConf[#key].toVariant().toString()))) \
|
||||
{ \
|
||||
key = vmessConf[#key].toVariant().toString(); \
|
||||
} \
|
||||
else if (!val.isEmpty()) \
|
||||
{ \
|
||||
key = val.first(); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
*errMessage = QObject::tr(#key " does not exist."); \
|
||||
} \
|
||||
}
|
||||
|
||||
// vmess v1 upgrader
|
||||
if (!vmessConf.contains("v"))
|
||||
{
|
||||
qDebug() << "Detected deprecated vmess v1. Trying to upgrade...";
|
||||
if (const auto network = vmessConf["net"].toString(); network == "ws" || network == "h2")
|
||||
{
|
||||
const QStringList hostComponents = vmessConf["host"].toString().replace(" ", "").split(";");
|
||||
if (const auto nParts = hostComponents.length(); nParts == 1)
|
||||
vmessConf["path"] = hostComponents[0], vmessConf["host"] = "";
|
||||
else if (nParts == 2)
|
||||
vmessConf["path"] = hostComponents[0], vmessConf["host"] = hostComponents[1];
|
||||
else
|
||||
vmessConf["path"] = "/", vmessConf["host"] = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Strict check of VMess protocol, to check if the specified value
|
||||
// is in the correct range.
|
||||
//
|
||||
// Get Alias (AKA ps) from address and port.
|
||||
{
|
||||
// Some idiot vmess:// links are using alterId...
|
||||
aid = vmessConf.contains("aid") ? vmessConf.value("aid").toInt(VMESS_USER_ALTERID_DEFAULT) :
|
||||
vmessConf.value("alterId").toInt(VMESS_USER_ALTERID_DEFAULT);
|
||||
//
|
||||
//
|
||||
__vmess_checker__func(ps, << vmessConf["add"].toVariant().toString() + ":" + vmessConf["port"].toVariant().toString()); //
|
||||
__vmess_checker__func(add, nothing); //
|
||||
__vmess_checker__func(id, nothing); //
|
||||
__vmess_checker__func(scy, << "aes-128-gcm" //
|
||||
<< "chacha20-poly1305" //
|
||||
<< "auto" //
|
||||
<< "none" //
|
||||
<< "zero"); //
|
||||
//
|
||||
__vmess_checker__func(type, << "none" //
|
||||
<< "http" //
|
||||
<< "srtp" //
|
||||
<< "utp" //
|
||||
<< "wechat-video"); //
|
||||
//
|
||||
__vmess_checker__func(net, << "tcp" //
|
||||
<< "http" //
|
||||
<< "h2" //
|
||||
<< "ws" //
|
||||
<< "kcp" //
|
||||
<< "quic" //
|
||||
<< "grpc"); //
|
||||
//
|
||||
__vmess_checker__func(tls, << "none" //
|
||||
<< "tls"); //
|
||||
//
|
||||
path = vmessConf.contains("path") ? vmessConf["path"].toVariant().toString() : (net == "quic" ? "" : "/");
|
||||
host = vmessConf.contains("host") ? vmessConf["host"].toVariant().toString() : (net == "quic" ? "none" : "");
|
||||
}
|
||||
|
||||
// Respect connection type rather than obfs type
|
||||
if (QStringList{ "srtp", "utp", "wechat-video" }.contains(type)) //
|
||||
{ //
|
||||
if (net != "quic" && net != "kcp") //
|
||||
{ //
|
||||
type = "none"; //
|
||||
} //
|
||||
}
|
||||
|
||||
port = vmessConf["port"].toVariant().toInt();
|
||||
aid = vmessConf["aid"].toVariant().toInt();
|
||||
//
|
||||
// Apply the settings.
|
||||
// User
|
||||
VMessServerObject::UserObject user;
|
||||
user.id = id;
|
||||
user.alterId = aid;
|
||||
user.security = scy;
|
||||
//
|
||||
// Server
|
||||
VMessServerObject serv;
|
||||
serv.port = port;
|
||||
serv.address = add;
|
||||
serv.users.push_back(user);
|
||||
//
|
||||
//
|
||||
// Stream Settings
|
||||
StreamSettingsObject streaming;
|
||||
|
||||
if (net == "tcp")
|
||||
{
|
||||
streaming.tcpSettings.header.type = type;
|
||||
}
|
||||
else if (net == "http" || net == "h2")
|
||||
{
|
||||
// Fill hosts for HTTP
|
||||
for (const auto &_host : host.split(','))
|
||||
{
|
||||
if (!_host.isEmpty())
|
||||
{
|
||||
streaming.httpSettings.host << _host.trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
streaming.httpSettings.path = path;
|
||||
}
|
||||
else if (net == "ws")
|
||||
{
|
||||
if (!host.isEmpty())
|
||||
streaming.wsSettings.headers["Host"] = host;
|
||||
streaming.wsSettings.path = path;
|
||||
}
|
||||
else if (net == "kcp")
|
||||
{
|
||||
streaming.kcpSettings.header.type = type;
|
||||
}
|
||||
else if (net == "quic")
|
||||
{
|
||||
streaming.quicSettings.security = host;
|
||||
streaming.quicSettings.header.type = type;
|
||||
streaming.quicSettings.key = path;
|
||||
}
|
||||
else if (net == "grpc")
|
||||
{
|
||||
streaming.grpcSettings.serviceName = path;
|
||||
}
|
||||
|
||||
streaming.security = tls;
|
||||
if (tls == "tls")
|
||||
{
|
||||
if (sni.isEmpty() && !host.isEmpty())
|
||||
sni = host;
|
||||
streaming.tlsSettings.serverName = sni;
|
||||
streaming.tlsSettings.allowInsecure = false;
|
||||
}
|
||||
//
|
||||
// Network type
|
||||
// NOTE(DuckSoft): Damn vmess:// just don't write 'http' properly
|
||||
if (net == "h2")
|
||||
net = "http";
|
||||
streaming.network = net;
|
||||
//
|
||||
// VMess root config
|
||||
QJsonObject vConf;
|
||||
vConf["vnext"] = QJsonArray{ serv.toJson() };
|
||||
const auto outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "vmess", vConf, streaming.toJson());
|
||||
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||
root["outbounds"] = QJsonArray{ outbound };
|
||||
root["inbounds"] = QJsonArray{ inbound };
|
||||
// If previous alias is empty, just the PS is needed, else, append a "_"
|
||||
*alias = alias->trimmed().isEmpty() ? ps : *alias + "_" + ps;
|
||||
return root;
|
||||
#undef default
|
||||
}
|
||||
} // namespace amnezia::serialization::vmess
|
||||
|
172
client/core/serialization/vmess_new.cpp
Normal file
172
client/core/serialization/vmess_new.cpp
Normal file
|
@ -0,0 +1,172 @@
|
|||
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||
// This file is part of the Qv2ray VPN client.
|
||||
//
|
||||
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Copyright (c) 2024 AmneziaVPN
|
||||
// This file has been modified for AmneziaVPN
|
||||
//
|
||||
// This file is based on the work of the Qv2ray VPN client.
|
||||
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||
//
|
||||
// The modified version of this file is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||
#include "transfer.h"
|
||||
#include "serialization.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
|
||||
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||
|
||||
namespace amnezia::serialization::vmess_new
|
||||
{
|
||||
const static QStringList NetworkType{ "tcp", "http", "ws", "kcp", "quic", "grpc" };
|
||||
const static QStringList QuicSecurityTypes{ "none", "aes-128-gcm", "chacha20-poly1305" };
|
||||
const static QStringList QuicKcpHeaderTypes{ "none", "srtp", "utp", "wechat-video", "dtls", "wireguard" };
|
||||
const static QStringList FalseTypes{ "false", "False", "No", "Off", "0" };
|
||||
|
||||
QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMessage)
|
||||
{
|
||||
QUrl url{ vmessStr };
|
||||
QUrlQuery query{ url };
|
||||
//
|
||||
#define default QJsonObject()
|
||||
if (!url.isValid())
|
||||
{
|
||||
*errMessage = QObject::tr("vmess:// url is invalid");
|
||||
return default;
|
||||
}
|
||||
|
||||
// If previous alias is empty, just the PS is needed, else, append a "_"
|
||||
const auto name = url.fragment(QUrl::FullyDecoded).trimmed();
|
||||
*alias = alias->isEmpty() ? name : (*alias + "_" + name);
|
||||
|
||||
VMessServerObject server;
|
||||
server.users << VMessServerObject::UserObject{};
|
||||
|
||||
StreamSettingsObject stream;
|
||||
QString net;
|
||||
bool tls = false;
|
||||
// Check streamSettings
|
||||
{
|
||||
for (const auto &_protocol : url.userName().split("+"))
|
||||
{
|
||||
if (_protocol == "tls")
|
||||
tls = true;
|
||||
else
|
||||
net = _protocol;
|
||||
}
|
||||
if (!NetworkType.contains(net))
|
||||
{
|
||||
*errMessage = QObject::tr("Invalid streamSettings protocol: ") + net;
|
||||
return default;
|
||||
}
|
||||
stream.network = net;
|
||||
stream.security = tls ? "tls" : "";
|
||||
}
|
||||
// Host Port UUID AlterID
|
||||
{
|
||||
const auto host = url.host();
|
||||
int port = url.port();
|
||||
QString uuid;
|
||||
int aid;
|
||||
{
|
||||
const auto pswd = url.password();
|
||||
const auto index = pswd.lastIndexOf("-");
|
||||
uuid = pswd.mid(0, index);
|
||||
aid = pswd.right(pswd.length() - index - 1).toInt();
|
||||
}
|
||||
server.address = host;
|
||||
server.port = port;
|
||||
server.users.first().id = uuid;
|
||||
server.users.first().alterId = aid;
|
||||
server.users.first().security = "auto";
|
||||
}
|
||||
|
||||
const 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
|
|
@ -72,6 +72,8 @@ QMap<amnezia::Proto, QString> ProtocolProps::protocolHumanNames()
|
|||
{ Proto::Ikev2, "IKEv2" },
|
||||
{ Proto::L2tp, "L2TP" },
|
||||
{ Proto::Xray, "XRay" },
|
||||
{ Proto::SSXray, "ShadowSocks"},
|
||||
|
||||
|
||||
{ Proto::TorWebSite, "Website in Tor network" },
|
||||
{ Proto::Dns, "DNS Service" },
|
||||
|
@ -87,6 +89,8 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p)
|
|||
{
|
||||
switch (p) {
|
||||
case Proto::Any: return ServiceType::None;
|
||||
case Proto::SSXray: return ServiceType::None;
|
||||
|
||||
case Proto::OpenVpn: return ServiceType::Vpn;
|
||||
case Proto::Cloak: return ServiceType::Vpn;
|
||||
case Proto::ShadowSocks: return ServiceType::Vpn;
|
||||
|
@ -160,7 +164,7 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p)
|
|||
case Proto::Any: return TransportProto::Udp;
|
||||
case Proto::OpenVpn: return TransportProto::Udp;
|
||||
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::Awg: return TransportProto::Udp;
|
||||
case Proto::Ikev2: return TransportProto::Udp;
|
||||
|
|
|
@ -83,6 +83,7 @@ namespace amnezia
|
|||
constexpr char sftp[] = "sftp";
|
||||
constexpr char awg[] = "awg";
|
||||
constexpr char xray[] = "xray";
|
||||
constexpr char ssxray[] = "ssxray";
|
||||
|
||||
constexpr char configVersion[] = "config_version";
|
||||
|
||||
|
@ -223,7 +224,8 @@ namespace amnezia
|
|||
|
||||
enum TransportProto {
|
||||
Udp,
|
||||
Tcp
|
||||
Tcp,
|
||||
TcpAndUdp
|
||||
};
|
||||
Q_ENUM_NS(TransportProto)
|
||||
|
||||
|
@ -237,6 +239,7 @@ namespace amnezia
|
|||
Ikev2,
|
||||
L2tp,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
// non-vpn
|
||||
TorWebSite,
|
||||
|
|
|
@ -116,6 +116,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
|
|||
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Awg: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Xray: return new XrayProtocol(configuration);
|
||||
case DockerContainer::SSXray: return new XrayProtocol(configuration);
|
||||
#endif
|
||||
default: return nullptr;
|
||||
}
|
||||
|
|
|
@ -233,6 +233,9 @@ void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
|
|||
{
|
||||
m_configData = configuration;
|
||||
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_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
|
||||
m_remoteAddress = configuration.value(amnezia::config_key::hostName).toString();
|
||||
|
|
|
@ -40,6 +40,7 @@ cat > /opt/amnezia/shadowsocks/ss-config.json <<EOF
|
|||
"password": "$SHADOWSOCKS_PASSWORD",
|
||||
"server": "0.0.0.0",
|
||||
"server_port": $SHADOWSOCKS_SERVER_PORT,
|
||||
"timeout": 60
|
||||
"timeout": 60,
|
||||
"mode" : "tcp_and_udp"
|
||||
}
|
||||
EOF
|
||||
|
|
|
@ -5,6 +5,7 @@ sudo docker run -d \
|
|||
--restart always \
|
||||
--cap-add=NET_ADMIN \
|
||||
-p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp \
|
||||
-p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/udp \
|
||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
|
|
@ -4,8 +4,13 @@
|
|||
#include <QFileInfo>
|
||||
#include <QQuickItem>
|
||||
#include <QRandomGenerator>
|
||||
#include <QUrlQuery>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "utilities.h"
|
||||
#include "core/serialization/serialization.h"
|
||||
#include "core/errorstrings.h"
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
@ -86,6 +91,55 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
|
|||
bool ImportController::extractConfigFromData(QString 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);
|
||||
if (m_configType == ConfigTypes::Invalid) {
|
||||
data.replace("vpn://", "");
|
||||
|
@ -397,7 +451,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
|||
return config;
|
||||
}
|
||||
|
||||
QJsonObject ImportController::extractXrayConfig(const QString &data)
|
||||
QJsonObject ImportController::extractXrayConfig(const QString &data, const QString &description)
|
||||
{
|
||||
QJsonParseError parserErr;
|
||||
QJsonDocument jsonConf = QJsonDocument::fromJson(data.toLocal8Bit(), &parserErr);
|
||||
|
@ -409,8 +463,13 @@ QJsonObject ImportController::extractXrayConfig(const QString &data)
|
|||
lastConfig[config_key::isThirdPartyConfig] = true;
|
||||
|
||||
QJsonObject containers;
|
||||
containers.insert(config_key::container, QJsonValue("amnezia-xray"));
|
||||
containers.insert(config_key::xray, QJsonValue(lastConfig));
|
||||
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::xray, QJsonValue(lastConfig));
|
||||
}
|
||||
|
||||
QJsonArray arr;
|
||||
arr.push_back(containers);
|
||||
|
@ -425,9 +484,17 @@ QJsonObject ImportController::extractXrayConfig(const QString &data)
|
|||
|
||||
QJsonObject config;
|
||||
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;
|
||||
|
||||
return config;
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace
|
|||
WireGuard,
|
||||
Awg,
|
||||
Xray,
|
||||
ShadowSocks,
|
||||
Backup,
|
||||
Invalid
|
||||
};
|
||||
|
@ -63,7 +64,7 @@ signals:
|
|||
private:
|
||||
QJsonObject extractOpenVpnConfig(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);
|
||||
|
||||
|
|
|
@ -580,6 +580,9 @@ bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
|
|||
if (ContainerProps::containerService(container) == ServiceType::Vpn) {
|
||||
return true;
|
||||
}
|
||||
if (container == DockerContainer::SSXray) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -623,4 +626,4 @@ bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
|
|||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <QUrl>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "utilities.h"
|
||||
#include "version.h"
|
||||
|
@ -23,6 +25,50 @@ QString Utils::getRandomString(int len)
|
|||
return randomString;
|
||||
}
|
||||
|
||||
QString Utils::VerifyJsonString(const QString &source)
|
||||
{
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(source.toUtf8(), &error);
|
||||
Q_UNUSED(doc)
|
||||
|
||||
if (error.error == QJsonParseError::NoError) {
|
||||
return "";
|
||||
} else {
|
||||
qDebug() << "WARNING: Json parse returns: " + error.errorString();
|
||||
return error.errorString();
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject Utils::JsonFromString(const QString &string)
|
||||
{
|
||||
auto removeComment = string.trimmed();
|
||||
if (removeComment != string.trimmed()) {
|
||||
qDebug() << "Some comments have been removed from the json.";
|
||||
}
|
||||
QJsonDocument doc = QJsonDocument::fromJson(removeComment.toUtf8());
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
QString Utils::SafeBase64Decode(QString string)
|
||||
{
|
||||
QByteArray ba = string.replace(QChar('-'), QChar('+')).replace(QChar('_'), QChar('/')).toUtf8();
|
||||
return QByteArray::fromBase64(ba, QByteArray::Base64Option::OmitTrailingEquals);
|
||||
}
|
||||
|
||||
QString Utils::JsonToString(const QJsonObject &json, QJsonDocument::JsonFormat format)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
doc.setObject(json);
|
||||
return doc.toJson(format);
|
||||
}
|
||||
|
||||
QString Utils::JsonToString(const QJsonArray &array, QJsonDocument::JsonFormat format)
|
||||
{
|
||||
QJsonDocument doc;
|
||||
doc.setArray(array);
|
||||
return doc.toJson(format);
|
||||
}
|
||||
|
||||
QString Utils::systemLogPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
|
@ -15,7 +16,11 @@ class Utils : public QObject
|
|||
|
||||
public:
|
||||
static QString getRandomString(int len);
|
||||
|
||||
static QString SafeBase64Decode(QString string);
|
||||
static QString VerifyJsonString(const QString &source);
|
||||
static QString JsonToString(const QJsonObject &json, QJsonDocument::JsonFormat format);
|
||||
static QString JsonToString(const QJsonArray &array, QJsonDocument::JsonFormat format);
|
||||
static QJsonObject JsonFromString(const QString &string);
|
||||
static QString executable(const QString &baseName, bool absPath);
|
||||
static QString usrExecutable(const QString &baseName);
|
||||
static QString systemLogPath();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue