Connection string support for XRay protocol (#777)

* Connection string support for XRay protocol
This commit is contained in:
Mykola Baibuz 2024-05-27 16:15:55 +01:00 committed by GitHub
parent d8020878d5
commit e6ee9085a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 20709 additions and 11 deletions

2
client/3rd/QJsonStruct/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.user
build/

View 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()

View 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.

View 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 &current, 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 &current, 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();
}
};

View 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)

View 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;
}
};

View 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__)

View 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)

View 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(){};
};

View 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)
};

File diff suppressed because it is too large Load diff

View 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;
}

View 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);
}
}
}
}

View file

@ -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

View file

@ -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;

View file

@ -23,6 +23,7 @@ namespace amnezia
ShadowSocks,
Ipsec,
Xray,
SSXray,
// non-vpn
TorWebSite,

View file

@ -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") {

View file

@ -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>();
}
}

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -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;

View file

@ -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,

View file

@ -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;
}

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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

View file

@ -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();