diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b90abb..00b523a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,20 +36,30 @@ IF(USING_MALLOC_TRIM) ENDIF() ADD_EXECUTABLE(subconverter - src/interfaces.cpp - src/logger.cpp + src/generator/config/nodemanip.cpp + src/generator/config/ruleconvert.cpp + src/generator/config/subexport.cpp + src/generator/template/templates.cpp + src/handler/interfaces.cpp + src/handler/multithread.cpp + src/handler/upload.cpp + src/handler/webget.cpp src/main.cpp - src/md5.cpp - src/misc.cpp - src/multithread.cpp - src/nodemanip.cpp - src/script.cpp - src/speedtestutil.cpp - src/subexport.cpp - src/templates.cpp - src/upload.cpp - src/webget.cpp - src/webserver_libevent.cpp) + src/parser/infoparser.cpp + src/parser/subparser.cpp + src/script/cron.cpp + src/script/script_quickjs.cpp + src/server/webserver_libevent.cpp + src/utils/base64/base64.cpp + src/utils/codepage.cpp + src/utils/file.cpp + src/utils/logger.cpp + src/utils/md5/md5.cpp + src/utils/network.cpp + src/utils/regexp.cpp + src/utils/string.cpp + src/utils/system.cpp + src/utils/urlencode.cpp) INCLUDE_DIRECTORIES(src) LINK_DIRECTORIES(${CMAKE_SOURCE_DIR}) @@ -90,9 +100,13 @@ TARGET_LINK_LIBRARIES(subconverter ${YAML_CPP_LIBRARY}) ADD_DEFINITIONS(-DPCRE2_STATIC) #ENDIF() -FIND_PACKAGE(Duktape REQUIRED) -INCLUDE_DIRECTORIES(${DUKTAPE_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES(subconverter ${DUKTAPE_LIBRARIES}) +FIND_PACKAGE(QuickJS REQUIRED) +INCLUDE_DIRECTORIES(${QUICKJS_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(subconverter ${QUICKJS_LIBRARIES}) + +FIND_PACKAGE(libcron REQUIRED) +INCLUDE_DIRECTORIES(${LIBCRON_INCLUDE_DIRS}) +TARGET_LINK_LIBRARIES(subconverter ${LIBCRON_LIBRARIES}) IF(WIN32) TARGET_LINK_LIBRARIES(subconverter wsock32 ws2_32) diff --git a/base/pref.example.ini b/base/pref.example.ini index dd61446..31d27af 100644 --- a/base/pref.example.ini +++ b/base/pref.example.ini @@ -244,6 +244,11 @@ clash.log_level=info /v2ray=/sub?target=v2ray /trojan=/sub?target=trojan +[tasks] +;Tasks to be run regularly during server execution. +;Format: Name`Cron_Expression`JS_Path`Timeout_in_seconds +;task=tick`0/10 * * * * ?`tick.js`3 + [server] ;Address to bind on for Web Server listen=0.0.0.0 diff --git a/base/pref.example.yml b/base/pref.example.yml index e44f579..ede4999 100644 --- a/base/pref.example.yml +++ b/base/pref.example.yml @@ -123,6 +123,12 @@ aliases: - {uri: /v2ray, target: "/sub?target=v2ray"} - {uri: /trojan, target: "/sub?target=trojan"} +tasks: +# - name: tick +# cronexp: "0/10 * * * * ?" +# path: tick.js +# timeout: 3 + server: listen: 0.0.0.0 port: 25500 diff --git a/cmake/FindLibCron.cmake b/cmake/FindLibCron.cmake new file mode 100644 index 0000000..51651d1 --- /dev/null +++ b/cmake/FindLibCron.cmake @@ -0,0 +1,13 @@ +find_path(LIBCRON_INCLUDE_DIR libcron/Cron.h) +find_path(DATE_INCLUDE_DIR date/date.h) + +find_library(LIBCRON_LIBRARY liblibcron) + +set(LIBCRON_LIBRARIES "${LIBCRON_LIBRARY}") +set(LIBCRON_INCLUDE_DIRS "${LIBCRON_INCLUDE_DIR} ${DATE_INCLUDE_DIR}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libcron DEFAULT_MSG + LIBCRON_INCLUDE_DIRS LIBCRON_LIBRARY) + +mark_as_advanced(LIBCRON_INCLUDE_DIRS) diff --git a/cmake/FindQuickJS.cmake b/cmake/FindQuickJS.cmake new file mode 100644 index 0000000..70ed30c --- /dev/null +++ b/cmake/FindQuickJS.cmake @@ -0,0 +1,11 @@ +find_path(QUICKJS_INCLUDE_DIRS quickjs/quickjs.h) + +find_library(QUICKJS_LIBRARY libquickjs) + +set(QUICKJS_LIBRARIES "${QUICKJS_LIBRARY}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(QuickJS DEFAULT_MSG + QUICKJS_INCLUDE_DIRS QUICKJS_LIBRARY) + +mark_as_advanced(QUICKJS_INCLUDE_DIRS QUICKJS_LIBRARY) diff --git a/include/quickjspp.hpp b/include/quickjspp.hpp new file mode 100644 index 0000000..ce4225d --- /dev/null +++ b/include/quickjspp.hpp @@ -0,0 +1,1341 @@ +#pragma once + +#include "quickjs/quickjs.h" +#include "quickjs/quickjs-libc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qjs { + + +/** Exception type. + * Indicates that exception has occured in JS context. + */ +class exception {}; + +/** Javascript conversion traits. + * Describes how to convert type R to/from JSValue. Second template argument can be used for SFINAE/enable_if type filters. + */ +template +struct js_traits +{ + /** Create an object of C++ type R given JSValue v and JSContext. + * This function is intentionally not implemented. User should implement this function for their own type. + * @param v This value is passed as JSValueConst so it should be freed by the caller. + * @throws exception in case of conversion error + */ + static R unwrap(JSContext * ctx, JSValueConst v); + /** Create JSValue from an object of type R and JSContext. + * This function is intentionally not implemented. User should implement this function for their own type. + * @return Returns JSValue which should be freed by the caller or JS_EXCEPTION in case of error. + */ + static JSValue wrap(JSContext * ctx, R value); +}; + +/** Conversion traits for JSValue (identity). + */ +template <> +struct js_traits +{ + static JSValue unwrap(JSContext * ctx, JSValueConst v) noexcept + { + return JS_DupValue(ctx, v); + } + + static JSValue wrap(JSContext * ctx, JSValue v) noexcept + { + return v; + } +}; + +/** Conversion traits for integers. + */ +template +struct js_traits && sizeof(Int) <= sizeof(int64_t)>> +{ + + /// @throws exception + static Int unwrap(JSContext * ctx, JSValueConst v) + { + if constexpr (sizeof(Int) > sizeof(int32_t)) + { + int64_t r; + if(JS_ToInt64(ctx, &r, v)) + throw exception{}; + return static_cast(r); + } + else + { + int32_t r; + if(JS_ToInt32(ctx, &r, v)) + throw exception{}; + return static_cast(r); + } + } + + static JSValue wrap(JSContext * ctx, Int i) noexcept + { + if constexpr (std::is_same_v || sizeof(Int) > sizeof(int32_t)) + return JS_NewInt64(ctx, static_cast(i)); + else + return JS_NewInt32(ctx, static_cast(i)); + } +}; + +/** Conversion traits for boolean. + */ +template <> +struct js_traits +{ + static bool unwrap(JSContext * ctx, JSValueConst v) noexcept + { + return JS_ToBool(ctx, v); + } + + static JSValue wrap(JSContext * ctx, bool i) noexcept + { + return JS_NewBool(ctx, i); + } +}; + +/** Conversion trait for void. + */ +template <> +struct js_traits +{ + /// @throws exception if jsvalue is neither undefined nor null + static void unwrap(JSContext * ctx, JSValueConst value) + { + if(JS_IsException(value)) + throw exception{}; + } +}; + +/** Conversion traits for float64/double. + */ +template <> +struct js_traits +{ + /// @throws exception + static double unwrap(JSContext * ctx, JSValueConst v) + { + double r; + if(JS_ToFloat64(ctx, &r, v)) + throw exception{}; + return r; + } + + static JSValue wrap(JSContext * ctx, double i) noexcept + { + return JS_NewFloat64(ctx, i); + } +}; + +namespace detail { +/** Fake std::string_view which frees the string on destruction. +*/ +class js_string : public std::string_view +{ + using Base = std::string_view; + JSContext * ctx = nullptr; + + friend struct js_traits; + + js_string(JSContext * ctx, const char * ptr, std::size_t len) : Base(ptr, len), ctx(ctx) + {} + +public: + + template + js_string(Args&& ... args) : Base(std::forward(args)...), ctx(nullptr) + {} + + js_string(const js_string& other) = delete; + + operator const char * () const { + return this->data(); + } + + ~js_string() + { + if(ctx) + JS_FreeCString(ctx, this->data()); + } +}; +} // namespace detail + +/** Conversion traits from std::string_view and to detail::js_string. */ +template <> +struct js_traits +{ + static detail::js_string unwrap(JSContext * ctx, JSValueConst v) + { + size_t plen; + const char * ptr = JS_ToCStringLen(ctx, &plen, v); + if(!ptr) + throw exception{}; + return detail::js_string{ctx, ptr, plen}; + } + + static JSValue wrap(JSContext * ctx, std::string_view str) noexcept + { + return JS_NewStringLen(ctx, str.data(), str.size()); + } +}; + +/** Conversion traits for std::string */ +template <> // slower +struct js_traits +{ + static std::string unwrap(JSContext * ctx, JSValueConst v) + { + auto str_view = js_traits::unwrap(ctx, v); + return std::string{str_view.data(), str_view.size()}; + } + + static JSValue wrap(JSContext * ctx, const std::string& str) noexcept + { + return JS_NewStringLen(ctx, str.data(), str.size()); + } +}; + +/** Conversion from const char * */ +template <> +struct js_traits +{ + static JSValue wrap(JSContext * ctx, const char * str) noexcept + { + return JS_NewString(ctx, str); + } + static detail::js_string unwrap(JSContext * ctx, JSValueConst v) + { + return js_traits::unwrap(ctx, v); + } +}; + + +namespace detail { + +/** Helper function to convert and then free JSValue. */ +template +T unwrap_free(JSContext * ctx, JSValue val) +{ + if constexpr(std::is_same_v) + { + JS_FreeValue(ctx, val); + return js_traits::unwrap(ctx, val); + } else + { + try + { + T result = js_traits>::unwrap(ctx, val); + JS_FreeValue(ctx, val); + return result; + } + catch(...) + { + JS_FreeValue(ctx, val); + throw; + } + } +} + +template +Tuple unwrap_args_impl(JSContext * ctx, JSValueConst * argv, std::index_sequence) +{ + return Tuple{js_traits>>::unwrap(ctx, argv[I])...}; +} + +/** Helper function to convert an array of JSValues to a tuple. + * @tparam Args C++ types of the argv array + */ +template +std::tuple...> unwrap_args(JSContext * ctx, JSValueConst * argv) +{ + return unwrap_args_impl...>>(ctx, argv, std::make_index_sequence()); +} + +/** Helper function to call f with an array of JSValues. + * @tparam R return type of f + * @tparam Args argument types of f + * @tparam Callable type of f (inferred) + * @param ctx JSContext + * @param f callable object + * @param argv array of JSValue's + * @return converted return value of f or JS_NULL if f returns void + */ +template +JSValue wrap_call(JSContext * ctx, Callable&& f, JSValueConst * argv) noexcept +{ + try + { + if constexpr(std::is_same_v) + { + std::apply(std::forward(f), unwrap_args(ctx, argv)); + return JS_NULL; + } else + { + return js_traits>::wrap(ctx, + std::apply(std::forward(f), + unwrap_args(ctx, argv))); + } + } + catch(exception) + { + return JS_EXCEPTION; + } +} + +/** Same as wrap_call, but pass this_value as first argument. + * @tparam FirstArg type of this_value + */ +template +JSValue wrap_this_call(JSContext * ctx, Callable&& f, JSValueConst this_value, JSValueConst * argv) noexcept +{ + try + { + if constexpr(std::is_same_v) + { + std::apply(std::forward(f), std::tuple_cat(unwrap_args(ctx, &this_value), + unwrap_args(ctx, argv))); + return JS_NULL; + } else + { + return js_traits>::wrap(ctx, + std::apply(std::forward(f), + std::tuple_cat( + unwrap_args(ctx, &this_value), + unwrap_args(ctx, argv)))); + } + } + catch(exception) + { + return JS_EXCEPTION; + } +} + +template +void wrap_args_impl(JSContext * ctx, JSValue * argv, Tuple tuple, std::index_sequence) +{ + ((argv[I] = js_traits>>::wrap(ctx, std::get(tuple))), ...); +} + +/** Converts C++ args to JSValue array. + * @tparam Args argument types + * @param argv array of size at least sizeof...(Args) + */ +template +void wrap_args(JSContext * ctx, JSValue * argv, Args&& ... args) +{ + wrap_args_impl(ctx, argv, std::make_tuple(std::forward(args)...), + std::make_index_sequence()); +} +} // namespace detail + +/** A wrapper type for free and class member functions. + * Pointer to function F is a template argument. + * @tparam F either a pointer to free function or a pointer to class member function + * @tparam PassThis if true and F is a pointer to free function, passes Javascript "this" value as first argument: + */ +template +struct fwrapper +{ + /// "name" property of the JS function object (not defined if nullptr) + const char * name = nullptr; +}; + +/** Conversion to JSValue for free function in fwrapper. */ +template +struct js_traits> +{ + static JSValue wrap(JSContext * ctx, fwrapper fw) noexcept + { + return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc, + JSValueConst * argv) noexcept -> JSValue { + if constexpr(PassThis) + return detail::wrap_this_call(ctx, F, this_value, argv); + else + return detail::wrap_call(ctx, F, argv); + }, fw.name, sizeof...(Args)); + + } +}; + +/** Conversion to JSValue for class member function in fwrapper. PassThis is ignored and treated as true */ +template +struct js_traits> +{ + static JSValue wrap(JSContext * ctx, fwrapper fw) noexcept + { + return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc, + JSValueConst * argv) noexcept -> JSValue { + return detail::wrap_this_call, Args...>(ctx, F, this_value, argv); + }, fw.name, sizeof...(Args)); + + } +}; + +/** Conversion to JSValue for const class member function in fwrapper. PassThis is ignored and treated as true */ +template +struct js_traits> +{ + static JSValue wrap(JSContext * ctx, fwrapper fw) noexcept + { + return JS_NewCFunction(ctx, [](JSContext * ctx, JSValueConst this_value, int argc, + JSValueConst * argv) noexcept -> JSValue { + return detail::wrap_this_call, Args...>(ctx, F, this_value, argv); + }, fw.name, sizeof...(Args)); + + } +}; + +/** A wrapper type for constructor of type T with arguments Args. + * Compilation fails if no such constructor is defined. + * @tparam Args constructor arguments + */ +template +struct ctor_wrapper +{ + static_assert(std::is_constructible::value, "no such constructor!"); + /// "name" property of JS constructor object + const char * name = nullptr; +}; + +/** Conversion to JSValue for ctor_wrapper. */ +template +struct js_traits> +{ + static JSValue wrap(JSContext * ctx, ctor_wrapper cw) noexcept + { + return JS_NewCFunction2(ctx, [](JSContext * ctx, JSValueConst this_value, int argc, + JSValueConst * argv) noexcept -> JSValue { + + if(js_traits>::QJSClassId == 0) // not registered + { +#if defined(__cpp_rtti) + // automatically register class on first use (no prototype) + js_traits>::register_class(ctx, typeid(T).name()); +#else + JS_ThrowTypeError(ctx, "quickjspp ctor_wrapper::wrap: Class is not registered"); + return JS_EXCEPTION; +#endif + } + + auto proto = JS_GetPropertyStr(ctx, this_value, "prototype"); + if (JS_IsException(proto)) + return proto; + auto jsobj = JS_NewObjectProtoClass(ctx, proto, js_traits>::QJSClassId); + JS_FreeValue(ctx, proto); + if (JS_IsException(jsobj)) + return jsobj; + + std::shared_ptr ptr = std::apply(std::make_shared, detail::unwrap_args(ctx, argv)); + JS_SetOpaque(jsobj, new std::shared_ptr(std::move(ptr))); + return jsobj; + + // return detail::wrap_call, Args...>(ctx, std::make_shared, argv); + }, cw.name, sizeof...(Args), JS_CFUNC_constructor, 0); + } +}; + + +/** Conversions for std::shared_ptr. + * T should be registered to a context before conversions. + * @tparam T class type + */ +template +struct js_traits> +{ + /// Registered class id in QuickJS. + inline static JSClassID QJSClassId = 0; + + /** Register class in QuickJS context. + * + * @param ctx context + * @param name class name + * @param proto class prototype or JS_NULL + * @throws exception + */ + static void register_class(JSContext * ctx, const char * name, JSValue proto = JS_NULL) + { + if(QJSClassId == 0) + { + JS_NewClassID(&QJSClassId); + } + auto rt = JS_GetRuntime(ctx); + if(!JS_IsRegisteredClass(rt, QJSClassId)) + { + JSClassDef def{ + name, + // destructor + [](JSRuntime * rt, JSValue obj) noexcept { + auto pptr = reinterpret_cast *>(JS_GetOpaque(obj, QJSClassId)); + delete pptr; + }, + nullptr, + nullptr, + nullptr + }; + int e = JS_NewClass(rt, QJSClassId, &def); + if(e < 0) + { + JS_ThrowInternalError(ctx, "Cant register class %s", name); + throw exception{}; + } + } + JS_SetClassProto(ctx, QJSClassId, proto); + } + + /** Create a JSValue from std::shared_ptr. + * Creates an object with class if #QJSClassId and sets its opaque pointer to a new copy of #ptr. + */ + static JSValue wrap(JSContext * ctx, std::shared_ptr ptr) + { + if(QJSClassId == 0) // not registered + { +#if defined(__cpp_rtti) + // automatically register class on first use (no prototype) + register_class(ctx, typeid(T).name()); +#else + JS_ThrowTypeError(ctx, "quickjspp std::shared_ptr::wrap: Class is not registered"); + return JS_EXCEPTION; +#endif + } + auto jsobj = JS_NewObjectClass(ctx, QJSClassId); + if(JS_IsException(jsobj)) + return jsobj; + + auto pptr = new std::shared_ptr(std::move(ptr)); + JS_SetOpaque(jsobj, pptr); + return jsobj; + } + + /// @throws exception if #v doesn't have the correct class id + static const std::shared_ptr& unwrap(JSContext * ctx, JSValueConst v) + { + auto ptr = reinterpret_cast *>(JS_GetOpaque2(ctx, v, QJSClassId)); + if(!ptr) + throw exception{}; + return *ptr; + } +}; + +/** Conversions for non-owning pointers to class T. + * @tparam T class type + */ +template +struct js_traits>> +{ + static JSValue wrap(JSContext * ctx, T * ptr) + { + if(js_traits>::QJSClassId == 0) // not registered + { +#if defined(__cpp_rtti) + js_traits>::register_class(ctx, typeid(T).name()); +#else + JS_ThrowTypeError(ctx, "quickjspp js_traits::wrap: Class is not registered"); + return JS_EXCEPTION; +#endif + } + auto jsobj = JS_NewObjectClass(ctx, js_traits>::QJSClassId); + if(JS_IsException(jsobj)) + return jsobj; + + // shared_ptr with empty deleter since we don't own T* + auto pptr = new std::shared_ptr(ptr, [](T *){}); + JS_SetOpaque(jsobj, pptr); + return jsobj; + } + + static T * unwrap(JSContext * ctx, JSValueConst v) + { + auto ptr = reinterpret_cast *>(JS_GetOpaque2(ctx, v, + js_traits>::QJSClassId)); + if(!ptr) + throw exception{}; + return ptr->get(); + } +}; + +namespace detail { +/** A faster std::function-like object with type erasure. + * Used to convert any callable objects (including lambdas) to JSValue. + */ +struct function +{ + JSValue + (* invoker)(function * self, JSContext * ctx, JSValueConst this_value, int argc, JSValueConst * argv) = nullptr; + + void (* destroyer)(function * self) = nullptr; + + alignas(std::max_align_t) char functor[]; + + template + static function * create(JSRuntime * rt, Functor&& f) + { + auto fptr = reinterpret_cast(js_malloc_rt(rt, sizeof(function) + sizeof(Functor))); + if(!fptr) + throw std::bad_alloc{}; + new(fptr) function; + auto functorptr = reinterpret_cast(fptr->functor); + new(functorptr) Functor(std::forward(f)); + fptr->destroyer = nullptr; + if constexpr(!std::is_trivially_destructible_v) + { + fptr->destroyer = [](function * fptr) { + auto functorptr = reinterpret_cast(fptr->functor); + functorptr->~Functor(); + }; + } + return fptr; + } +}; + +static_assert(std::is_trivially_destructible_v); +} + +template <> +struct js_traits +{ + inline static JSClassID QJSClassId = 0; + + // TODO: replace ctx with rt + static void register_class(JSContext * ctx, const char * name) + { + if(QJSClassId == 0) + { + JS_NewClassID(&QJSClassId); + } + auto rt = JS_GetRuntime(ctx); + if(JS_IsRegisteredClass(rt, QJSClassId)) + return; + JSClassDef def{ + name, + // destructor + [](JSRuntime * rt, JSValue obj) noexcept { + auto fptr = reinterpret_cast(JS_GetOpaque(obj, QJSClassId)); + assert(fptr); + if(fptr->destroyer) + fptr->destroyer(fptr); + js_free_rt(rt, fptr); + }, + nullptr, // mark + // call + [](JSContext * ctx, JSValueConst func_obj, JSValueConst this_val, int argc, + JSValueConst * argv, int flags) -> JSValue { + auto ptr = reinterpret_cast(JS_GetOpaque2(ctx, func_obj, QJSClassId)); + if(!ptr) + return JS_EXCEPTION; + return ptr->invoker(ptr, ctx, this_val, argc, argv); + }, + nullptr + }; + int e = JS_NewClass(rt, QJSClassId, &def); + if(e < 0) + throw std::runtime_error{"Cannot register C++ function class"}; + } +}; + + +/** Traits for accessing object properties. + * @tparam Key property key type (uint32 and strings are supported) + */ +template +struct js_property_traits +{ + static void set_property(JSContext * ctx, JSValue this_obj, Key key, JSValue value); + static JSValue get_property(JSContext * ctx, JSValue this_obj, Key key); +}; + +template <> +struct js_property_traits +{ + static void set_property(JSContext * ctx, JSValue this_obj, const char * name, JSValue value) + { + int err = JS_SetPropertyStr(ctx, this_obj, name, value); + if(err < 0) + throw exception{}; + } + + static JSValue get_property(JSContext * ctx, JSValue this_obj, const char * name) noexcept + { + return JS_GetPropertyStr(ctx, this_obj, name); + } +}; + +template <> +struct js_property_traits +{ + static void set_property(JSContext * ctx, JSValue this_obj, uint32_t idx, JSValue value) + { + int err = JS_SetPropertyUint32(ctx, this_obj, idx, value); + if(err < 0) + throw exception{}; + } + + static JSValue get_property(JSContext * ctx, JSValue this_obj, uint32_t idx) noexcept + { + return JS_GetPropertyUint32(ctx, this_obj, idx); + } +}; + +class Value; + +namespace detail { +template +struct property_proxy +{ + JSContext * ctx; + JSValue this_obj; + Key key; + + /** Conversion helper function */ + template + T as() const + { + return unwrap_free(ctx, js_property_traits::get_property(ctx, this_obj, key)); + } + + /** Explicit conversion operator (to any type) */ + template + explicit operator T() const { return as(); } + + /** Implicit converion to qjs::Value */ + operator Value() const; // defined later due to Value being incomplete type + + template + property_proxy& operator =(Value value) + { + js_property_traits::set_property(ctx, this_obj, key, + js_traits::wrap(ctx, std::move(value))); + return *this; + } +}; + + +// class member variable getter/setter +template +struct get_set {}; + +template +struct get_set +{ + using is_const = std::is_const; + + static const R& get(const std::shared_ptr& ptr) + { + return *ptr.*M; + } + + static R& set(const std::shared_ptr& ptr, R value) + { + return *ptr.*M = std::move(value); + } + +}; + +} // namespace detail + +/** JSValue with RAAI semantics. + * A wrapper over (JSValue v, JSContext * ctx). + * Calls JS_FreeValue(ctx, v) on destruction. Can be copied and moved. + * A JSValue can be released by either JSValue x = std::move(value); or JSValue x = value.release(), then the Value becomes invalid and FreeValue won't be called + * Can be converted to C++ type, for example: auto string = value.as(); qjs::exception would be thrown on error + * Properties can be accessed (read/write): value["property1"] = 1; value[2] = "2"; + */ +class Value +{ +public: + JSValue v; + JSContext * ctx = nullptr; + +public: + /** Use context.newValue(val) instead */ + template + Value(JSContext * ctx, T&& val) : ctx(ctx) + { + v = js_traits>::wrap(ctx, std::forward(val)); + if(JS_IsException(v)) + throw exception{}; + } + + Value(const Value& rhs) + { + ctx = rhs.ctx; + v = JS_DupValue(ctx, rhs.v); + } + + Value(Value&& rhs) + { + std::swap(ctx, rhs.ctx); + v = rhs.v; + } + + Value& operator=(Value rhs) + { + std::swap(ctx, rhs.ctx); + std::swap(v, rhs.v); + return *this; + } + + bool operator==(JSValueConst other) const + { + return JS_VALUE_GET_TAG(v) == JS_VALUE_GET_TAG(other) && JS_VALUE_GET_PTR(v) == JS_VALUE_GET_PTR(other); + } + + bool operator!=(JSValueConst other) const { return !((*this) == other); } + + + /** Returns true if 2 values are the same (equality for arithmetic types or point to the same object) */ + bool operator==(const Value& rhs) const + { + return ctx == rhs.ctx && (*this == rhs.v); + } + + bool operator!=(const Value& rhs) const { return !((*this) == rhs); } + + + ~Value() + { + if(ctx) JS_FreeValue(ctx, v); + } + + bool isError() const { return JS_IsError(ctx, v); } + + /** Conversion helper function: value.as() + * @tparam T type to convert to + * @return type returned by js_traits>::unwrap that should be implicitly convertible to T + * */ + template + auto as() const { return js_traits>::unwrap(ctx, v); } + + /** Explicit conversion: static_cast(value) or (T)value */ + template + explicit operator T() const { return as(); } + + JSValue release() // dont call freevalue + { + ctx = nullptr; + return v; + } + + /** Implicit conversion to JSValue (rvalue only). Example: JSValue v = std::move(value); */ + operator JSValue() && { return release(); } + + + /** Access JS properties. Returns proxy type which is implicitly convertible to qjs::Value */ + template + detail::property_proxy operator [](Key key) + { + return {ctx, v, std::move(key)}; + } + + + // add("f", []() {...}); + template + Value& add(const char * name, Function&& f) + { + (*this)[name] = js_traits(f)})>::wrap(ctx, + std::forward(f)); + return *this; + } + + // add<&f>("f"); + // add<&T::f>("f"); + template + std::enable_if_t, Value&> + add(const char * name) + { + (*this)[name] = fwrapper{name}; + return *this; + } + + // add_getter_setter<&T::get_member, &T::set_member>("member"); + template + Value& add_getter_setter(const char * name) + { + auto prop = JS_NewAtom(ctx, name); + using fgetter = fwrapper; + using fsetter = fwrapper; + int ret = JS_DefinePropertyGetSet(ctx, v, prop, + js_traits::wrap(ctx, fgetter{name}), + js_traits::wrap(ctx, fsetter{name}), + JS_PROP_CONFIGURABLE | JS_PROP_WRITABLE | JS_PROP_ENUMERABLE + ); + JS_FreeAtom(ctx, prop); + if(ret < 0) + throw exception{}; + return *this; + } + + // add_getter<&T::get_member>("member"); + template + Value& add_getter(const char * name) + { + auto prop = JS_NewAtom(ctx, name); + using fgetter = fwrapper; + int ret = JS_DefinePropertyGetSet(ctx, v, prop, + js_traits::wrap(ctx, fgetter{name}), + JS_UNDEFINED, + JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE + ); + JS_FreeAtom(ctx, prop); + if(ret < 0) + throw exception{}; + return *this; + } + + // add<&T::member>("member"); + template + std::enable_if_t, Value&> + add(const char * name) + { + if constexpr (detail::get_set::is_const::value) + { + return add_getter::get>(name); + } + else + { + return add_getter_setter::get, detail::get_set::set>(name); + } + } + + std::string toJSON(const Value& replacer = Value{nullptr, JS_UNDEFINED}, const Value& space = Value{nullptr, JS_UNDEFINED}) + { + assert(ctx); + assert(!replacer.ctx || ctx == replacer.ctx); + assert(!space.ctx || ctx == space.ctx); + JSValue json = JS_JSONStringify(ctx, v, replacer.v, space.v); + return (std::string)Value{ctx, json}; + } + +}; + +/** Thin wrapper over JSRuntime * rt + * Calls JS_FreeRuntime on destruction. noncopyable. + */ +class Runtime +{ +public: + JSRuntime * rt; + + Runtime() + { + rt = JS_NewRuntime(); + if(!rt) + throw std::runtime_error{"qjs: Cannot create runtime"}; + } + + // noncopyable + Runtime(const Runtime&) = delete; + + ~Runtime() + { + JS_FreeRuntime(rt); + } +}; + +/** Wrapper over JSContext * ctx + * Calls JS_SetContextOpaque(ctx, this); on construction and JS_FreeContext on destruction + */ +class Context +{ +public: + JSContext * ctx; + + /** Module wrapper + * Workaround for lack of opaque pointer for module load function by keeping a list of modules in qjs::Context. + */ + class Module + { + friend class Context; + + JSModuleDef * m; + JSContext * ctx; + const char * name; + + using nvp = std::pair; + std::vector exports; + public: + Module(JSContext * ctx, const char * name) : ctx(ctx), name(name) + { + m = JS_NewCModule(ctx, name, [](JSContext * ctx, JSModuleDef * m) noexcept { + auto& context = Context::get(ctx); + auto it = std::find_if(context.modules.begin(), context.modules.end(), + [m](const Module& module) { return module.m == m; }); + if(it == context.modules.end()) + return -1; + for(const auto& e : it->exports) + { + if(JS_SetModuleExport(ctx, m, e.first, JS_DupValue(ctx, e.second.v)) != 0) + return -1; + } + return 0; + }); + if(!m) + throw exception{}; + } + + Module& add(const char * name, JSValue value) + { + exports.push_back({name, {ctx, value}}); + JS_AddModuleExport(ctx, m, name); + return *this; + } + + Module& add(const char * name, Value value) + { + assert(value.ctx == ctx); + exports.push_back({name, std::move(value)}); + JS_AddModuleExport(ctx, m, name); + return *this; + } + + template + Module& add(const char * name, T value) + { + return add(name, js_traits::wrap(ctx, std::move(value))); + } + + Module(const Module&) = delete; + + Module(Module&&) = default; + //Module& operator=(Module&&) = default; + + + // function wrappers + + /** Add free function F. + * Example: + * module.function(&::sin)>("sin"); + */ + template + Module& function(const char * name) + { + return add(name, qjs::fwrapper{name}); + } + + /** Add function object f. + * Slower than template version. + * Example: module.function("sin", [](double x) { return ::sin(x); }); + */ + template + Module& function(const char * name, F&& f) + { + return add(name, js_traits(f)})>::wrap(std::forward(f))); + } + + // class register wrapper + private: + /** Helper class to register class members and constructors. + * See fun, constructor. + * Actual registration occurs at object destruction. + */ + template + class class_registrar + { + const char * name; + qjs::Value prototype; + qjs::Context::Module& module; + qjs::Context& context; + public: + explicit class_registrar(const char * name, qjs::Context::Module& module, qjs::Context& context) : + name(name), + prototype(context.newObject()), + module(module), + context(context) + { + } + + class_registrar(const class_registrar&) = delete; + + /** Add functional object f + */ + template + class_registrar& fun(const char * name, F&& f) + { + prototype.add(name, std::forward(f)); + return *this; + } + + /** Add class member function or class member variable F + * Example: + * struct T { int var; int func(); } + * auto& module = context.addModule("module"); + * module.class_("T").fun<&T::var>("var").fun<&T::func>("func"); + */ + template + class_registrar& fun(const char * name) + { + prototype.add(name); + return *this; + } + + template + class_registrar& property(const char * name) + { + if /*constexpr*/ (FSet == nullptr) + prototype.add_getter(name); + else + prototype.add_getter_setter(name); + return *this; + } + + /** Add class constructor + * @tparam Args contructor arguments + * @param name constructor name (if not specified class name will be used) + */ + template + class_registrar& constructor(const char * name = nullptr) + { + if(!name) + name = this->name; + Value ctor = context.newValue(qjs::ctor_wrapper{name}); + JS_SetConstructor(context.ctx, ctor.v, prototype.v); + module.add(name, std::move(ctor)); + return *this; + } + + /* TODO: needs casting to base class + template + class_registrar& base() + { + assert(js_traits>::QJSClassId && "base class is not registered"); + auto base_proto = JS_GetClassProto(context.ctx, js_traits>::QJSClassId); + int err = JS_SetPrototype(context.ctx, prototype.v, base_proto); + JS_FreeValue(context.ctx, base_proto); + if(err < 0) + throw exception{}; + return *this; + } + */ + + ~class_registrar() + { + context.registerClass(name, std::move(prototype)); + } + }; + + public: + /** Add class to module. + * See \ref class_registrar. + */ + template + class_registrar class_(const char * name) + { + return class_registrar{name, *this, qjs::Context::get(ctx)}; + } + + }; + + std::vector modules; +private: + void init() + { + JS_SetContextOpaque(ctx, this); + js_traits::register_class(ctx, "C++ function"); + } + +public: + Context(Runtime& rt) : Context(rt.rt) + {} + + Context(JSRuntime * rt) + { + ctx = JS_NewContext(rt); + if(!ctx) + throw std::runtime_error{"qjs: Cannot create context"}; + init(); + } + + Context(JSContext * ctx) : ctx{ctx} + { + init(); + } + + // noncopyable + Context(const Context&) = delete; + + ~Context() + { + modules.clear(); + JS_FreeContext(ctx); + } + + /** Create module and return a reference to it */ + Module& addModule(const char * name) + { + modules.emplace_back(ctx, name); + return modules.back(); + } + + /** returns globalThis */ + Value global() { return Value{ctx, JS_GetGlobalObject(ctx)}; } + + /** returns new Object() */ + Value newObject() { return Value{ctx, JS_NewObject(ctx)}; } + + /** returns JS value converted from c++ object val */ + template + Value newValue(T&& val) { return Value{ctx, std::forward(val)}; } + + /** returns current exception associated with context, and resets it. Should be called when qjs::exception is caught */ + Value getException() { return Value{ctx, JS_GetException(ctx)}; } + + /** Register class T for conversions to/from std::shared_ptr to work. + * Wherever possible module.class_("T")... should be used instead. + * @tparam T class type + * @param name class name in JS engine + * @param proto JS class prototype or JS_UNDEFINED + */ + template + void registerClass(const char * name, JSValue proto = JS_NULL) + { + js_traits>::register_class(ctx, name, proto); + } + + Value eval(std::string_view buffer, const char * filename = "", unsigned eval_flags = 0) + { + assert(buffer.data()[buffer.size()] == '\0' && "eval buffer is not null-terminated"); // JS_Eval requirement + JSValue v = JS_Eval(ctx, buffer.data(), buffer.size(), filename, eval_flags); + return Value{ctx, v}; + } + + Value evalFile(const char * filename, unsigned eval_flags = 0) + { + size_t buf_len; + auto deleter = [this](void * p) { js_free(ctx, p); }; + auto buf = std::unique_ptr{js_load_file(ctx, &buf_len, filename), deleter}; + if(!buf) + throw std::runtime_error{std::string{"evalFile: can't read file: "} + filename}; + return eval({reinterpret_cast(buf.get()), buf_len}, filename, eval_flags); + } + + Value fromJSON(std::string_view buffer, const char * filename = "") + { + assert(buffer.data()[buffer.size()] == '\0' && "fromJSON buffer is not null-terminated"); // JS_ParseJSON requirement + JSValue v = JS_ParseJSON(ctx, buffer.data(), buffer.size(), filename); + return Value{ctx, v}; + } + + /** Get qjs::Context from JSContext opaque pointer */ + static Context& get(JSContext * ctx) + { + void * ptr = JS_GetContextOpaque(ctx); + assert(ptr); + return *reinterpret_cast(ptr); + } +}; + +/** Conversion traits for Value. + */ +template <> +struct js_traits +{ + static Value unwrap(JSContext * ctx, JSValueConst v) + { + return Value{ctx, JS_DupValue(ctx, v)}; + } + + static JSValue wrap(JSContext * ctx, Value v) noexcept + { + assert(ctx == v.ctx); + return v.release(); + } +}; + +/** Convert to/from std::function. Actually accepts/returns callable object that is compatible with function. + * @tparam R return type + * @tparam Args argument types + */ +template +struct js_traits> +{ + static auto unwrap(JSContext * ctx, JSValueConst fun_obj) + { + return [jsfun_obj = Value{ctx, JS_DupValue(ctx, fun_obj)}](Args&& ... args) -> R { + const int argc = sizeof...(Args); + JSValue argv[argc]; + detail::wrap_args(jsfun_obj.ctx, argv, std::forward(args)...); + JSValue result = JS_Call(jsfun_obj.ctx, jsfun_obj.v, JS_UNDEFINED, argc, const_cast(argv)); + for(int i = 0; i < argc; i++) JS_FreeValue(jsfun_obj.ctx, argv[i]); + return detail::unwrap_free(jsfun_obj.ctx, result); + }; + } + + /** Convert from function object functor to JSValue. + * Uses detail::function for type-erasure. + */ + template + static JSValue wrap(JSContext * ctx, Functor&& functor) + { + using detail::function; + assert(js_traits::QJSClassId); + auto obj = JS_NewObjectClass(ctx, js_traits::QJSClassId); + if(JS_IsException(obj)) + return JS_EXCEPTION; + auto fptr = function::create(JS_GetRuntime(ctx), std::forward(functor)); + fptr->invoker = [](function * self, JSContext * ctx, JSValueConst this_value, int argc, JSValueConst * argv) { + assert(self); + auto f = reinterpret_cast(&self->functor); + return detail::wrap_call(ctx, *f, argv); + }; + JS_SetOpaque(obj, fptr); + return obj; + } +}; + +/** Convert from std::vector to Array and vice-versa. If Array holds objects that are non-convertible to T throws qjs::exception */ +template +struct js_traits> +{ + static JSValue wrap(JSContext * ctx, const std::vector& arr) noexcept + { + try + { + auto jsarray = Value{ctx, JS_NewArray(ctx)}; + for(uint32_t i = 0; i < (uint32_t) arr.size(); i++) + jsarray[i] = arr[i]; + return jsarray.release(); + } + catch(exception) + { + return JS_EXCEPTION; + } + } + + static std::vector unwrap(JSContext * ctx, JSValueConst jsarr) + { + int e = JS_IsArray(ctx, jsarr); + if(e == 0) + JS_ThrowTypeError(ctx, "js_traits>::unwrap expects array"); + if(e <= 0) + throw exception{}; + Value jsarray{ctx, JS_DupValue(ctx, jsarr)}; + std::vector arr; + auto len = static_cast(jsarray["length"]); + arr.reserve((uint32_t) len); + for(uint32_t i = 0; i < (uint32_t) len; i++) + arr.push_back(static_cast(jsarray[i])); + return arr; + } +}; + +namespace detail { +template +property_proxy::operator Value() const +{ + return as(); +} +} + +} // namespace qjs \ No newline at end of file diff --git a/scripts/Dockerfile b/scripts/Dockerfile index 282036b..3ddd462 100644 --- a/scripts/Dockerfile +++ b/scripts/Dockerfile @@ -1,26 +1,28 @@ -FROM alpine:3.12 +FROM alpine:3.13 LABEL maintainer "tindy.it@gmail.com" # build minimized WORKDIR / -RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake python2 nodejs npm && \ +RUN apk add --no-cache --virtual .build-tools git g++ build-base linux-headers cmake && \ apk add --no-cache --virtual .build-deps curl-dev rapidjson-dev libevent-dev pcre2-dev yaml-cpp-dev && \ - git clone https://github.com/svaarala/duktape --depth=1 && \ - cd duktape && \ - make -C src-tools && \ - python2 -m ensurepip && \ - pip2 install PyYAML --no-cache-dir && \ - python2 util/dist.py --output-directory dist && \ - cd dist/src && \ - cc -c -O3 -o duktape.o duktape.c && \ - cc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c && \ - ar cr libduktape.a duktape.o && \ - ar cr libduktape_module.a duk_module_node.o && \ - install -m0644 ./*.a /usr/lib && \ - install -m0644 duk*.h /usr/include && \ - install -m0644 ../extras/module-node/duk_module_node.h /usr/include && \ - cd ../../.. && \ - rm -rf duktape /usr/lib/python2.7 && \ + git clone https://github.com/ftk/quickjspp --depth=1 && \ + cd quickjspp && \ + cmake -DCMAKE_BUILD_TYPE=Release . && \ + make -j4 && \ + install -m644 quickjs/libquickjs.a /usr/lib && \ + install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h /usr/include/quickjs && \ + install -m644 quickjspp.hpp /usr/include && \ + cd .. && \ + git clone https://github.com/PerMalmberg/libcron --depth=1 && \ + cd libcron && \ + cmake -DCMAKE_BUILD_TYPE=Release . && \ + make -j4 && \ + install -m644 libcron/out/Release/liblibcron.a /usr/lib && \ + install -d /usr/include/libcron/ && \ + install -m644 libcron/include/libcron/* /usr/include/libcron/ && \ + install -d /usr/include/date/ && \ + install -m644 libcron/externals/date/include/date/* /usr/include/date/ && \ + cd .. && \ git clone https://github.com/tindy2013/subconverter --depth=1 && \ cd subconverter && \ cmake -DCMAKE_BUILD_TYPE=Release . && \ diff --git a/scripts/build.alpine.release.sh b/scripts/build.alpine.release.sh index f9f1bcb..6088041 100644 --- a/scripts/build.alpine.release.sh +++ b/scripts/build.alpine.release.sh @@ -16,21 +16,25 @@ cmake -DCMAKE_BUILD_TYPE=Release -DYAML_CPP_BUILD_TESTS=OFF -DYAML_CPP_BUILD_TOO make install -j2 > /dev/null cd .. -git clone https://github.com/svaarala/duktape --depth=1 -cd duktape -make -C src-tools -python2 -m ensurepip -pip2 install PyYAML -python2 util/dist.py --output-directory dist -cd dist/src -cc -c -O3 -o duktape.o duktape.c -cc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c -ar cr libduktape.a duktape.o -ar cr libduktape_module.a duk_module_node.o -install -m0644 ./*.a /usr/lib -install -m0644 ./duk*.h /usr/include -install -m0644 ../extras/module-node/duk_module_node.h /usr/include -cd ../../.. +git clone https://github.com/ftk/quickjspp --depth=1 +cd quickjspp +cmake -DCMAKE_BUILD_TYPE=Release . +make -j4 +install -m644 quickjs/libquickjs.a /usr/lib +install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h /usr/include/quickjs +install -m644 quickjspp.hpp /usr/include +cd .. + +git clone https://github.com/PerMalmberg/libcron --depth=1 +cd libcron +cmake -DCMAKE_BUILD_TYPE=Release . +make -j4 +install -m644 libcron/out/Release/liblibcron.a /usr/lib +install -d /usr/include/libcron/ +install -m644 libcron/include/libcron/* /usr/include/libcron/ +install -d /usr/include/date/ +install -m644 libcron/externals/date/include/date/* /usr/include/date/ +cd .. export PKG_CONFIG_PATH=/usr/lib64/pkgconfig cmake -DCMAKE_BUILD_TYPE=Release . diff --git a/scripts/build.macos.release.sh b/scripts/build.macos.release.sh index be54036..182ba14 100644 --- a/scripts/build.macos.release.sh +++ b/scripts/build.macos.release.sh @@ -17,20 +17,23 @@ cmake -DCMAKE_BUILD_TYPE=Release -DYAML_CPP_BUILD_TESTS=OFF -DYAML_CPP_BUILD_TOO make install -j8 > /dev/null cd .. -git clone https://github.com/svaarala/duktape --depth=1 -cd duktape -make -C src-tools -pip2 install PyYAML -python2 util/dist.py --output-directory dist -cd dist/src -cc -c -O3 -o duktape.o duktape.c -cc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c -ar cr libduktape.a duktape.o -ar cr libduktape_module.a duk_module_node.o -install -m0644 ./*.a /usr/local/lib -install -m0644 ./duk*.h /usr/local/include -install -m0644 ../extras/module-node/duk_module_node.h /usr/local/include -cd ../../.. +git clone https://github.com/ftk/quickjspp --depth=1 +cd quickjspp +cmake -DCMAKE_BUILD_TYPE=Release . +make -j4 +install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h /usr/local/include/quickjs +install -m644 quickjspp.hpp /usr/local/include +cd .. + +git clone https://github.com/PerMalmberg/libcron --depth=1 +cd libcron +cmake -DCMAKE_BUILD_TYPE=Release . +make -j4 +install -d /usr/local/include/libcron/ +install -m644 libcron/include/libcron/* /usr/local/include/libcron/ +install -d /usr/local/include/date/ +install -m644 libcron/externals/date/include/date/* /usr/local/include/date/ +cd .. cp /usr/local/lib/libevent.a . cp /usr/local/opt/zlib/lib/libz.a . diff --git a/scripts/build.windows.release.sh b/scripts/build.windows.release.sh index 0c04af3..8853a93 100644 --- a/scripts/build.windows.release.sh +++ b/scripts/build.windows.release.sh @@ -13,19 +13,26 @@ cmake -DCMAKE_BUILD_TYPE=Release -DYAML_CPP_BUILD_TESTS=OFF -DYAML_CPP_BUILD_TOO make install -j4 cd .. -git clone https://github.com/svaarala/duktape --depth=1 -cd duktape -make -C src-tools -node src-tools/index.js dist --output-directory dist -cd dist/src -gcc -c -O3 -o duktape.o duktape.c -gcc -c -O3 -o duk_module_node.o -I. ../extras/module-node/duk_module_node.c -ar cr libduktape.a duktape.o -ar cr libduktape_module.a duk_module_node.o -install -m0644 ./*.a "$MINGW_PREFIX/lib" -install -m0644 ./duk*.h "$MINGW_PREFIX/include" -install -m0644 ../extras/module-node/duk_module_node.h "$MINGW_PREFIX/include" -cd ../../.. +git clone https://github.com/ftk/quickjspp --depth=1 +cd quickjspp +patch quickjs/quickjs-libc.c -i ../scripts/patches/0001-quickjs-libc-add-realpath-for-Windows.patch +cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release . +make -j4 +install -m644 quickjs/libquickjs.a "$MINGW_PREFIX/lib" +install -m644 quickjs/quickjs.h quickjs/quickjs-libc.h "$MINGW_PREFIX/include/quickjs" +install -m644 quickjspp.hpp "$MINGW_PREFIX/include" +cd .. + +git clone https://github.com/PerMalmberg/libcron --depth=1 +cd libcron +cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release . +make -j4 +install -m644 libcron/out/Release/liblibcron.a "$MINGW_PREFIX/lib" +install -d "$MINGW_PREFIX/include/libcron/" +install -m644 libcron/include/libcron/* "$MINGW_PREFIX/include/libcron/" +install -d "$MINGW_PREFIX/include/date/" +install -m644 libcron/externals/date/include/date/* "$MINGW_PREFIX/include/date/" +cd .. git clone https://github.com/Tencent/rapidjson --depth=1 cd rapidjson diff --git a/scripts/patches/0001-quickjs-libc-add-realpath-for-Windows.patch b/scripts/patches/0001-quickjs-libc-add-realpath-for-Windows.patch new file mode 100644 index 0000000..216c824 --- /dev/null +++ b/scripts/patches/0001-quickjs-libc-add-realpath-for-Windows.patch @@ -0,0 +1,93 @@ +--- quickjs-libc.c 2021-03-23 15:04:09.604314700 +0800 ++++ patched.c 2021-03-23 15:04:35.109289500 +0800 +@@ -132,6 +132,29 @@ + static uint64_t os_pending_signals; + static int (*os_poll_func)(JSContext *ctx); + ++static char *_realpath(const char *path, char *resolved_path) ++{ ++#if defined(_WIN32) ++ BOOL allocated = FALSE; ++ if(resolved_path == NULL) ++ { ++ resolved_path = (char*) malloc(PATH_MAX); ++ allocated = TRUE; ++ } ++ DWORD len = GetFullPathNameA(path, PATH_MAX, resolved_path, NULL); ++ if(!len || len >= PATH_MAX) ++ { ++ if(allocated) ++ free(resolved_path); ++ return NULL; ++ } ++ resolved_path[len] = '\0'; ++ return resolved_path; ++#else ++ return realpath(path, resolved_path); ++#endif // _WIN32 ++} ++ + static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) + { + dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); +@@ -530,19 +553,19 @@ + return -1; + if (!strchr(module_name, ':')) { + strcpy(buf, "file://"); +-#if !defined(_WIN32) ++//#if !defined(_WIN32) + /* realpath() cannot be used with modules compiled with qjsc + because the corresponding module source code is not + necessarily present */ + if (use_realpath) { +- char *res = realpath(module_name, buf + strlen(buf)); ++ char *res = _realpath(module_name, buf + strlen(buf)); + if (!res) { + JS_ThrowTypeError(ctx, "realpath failure"); + JS_FreeCString(ctx, module_name); + return -1; + } + } else +-#endif ++//#endif + { + pstrcat(buf, sizeof(buf), module_name); + } +@@ -2588,8 +2611,6 @@ + return JS_NewInt32(ctx, ret); + } + +-#if !defined(_WIN32) +- + /* return [path, errorcode] */ + static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +@@ -2601,7 +2622,7 @@ + path = JS_ToCString(ctx, argv[0]); + if (!path) + return JS_EXCEPTION; +- res = realpath(path, buf); ++ res = _realpath(path, buf); + JS_FreeCString(ctx, path); + if (!res) { + buf[0] = '\0'; +@@ -2612,6 +2633,8 @@ + return make_string_error(ctx, buf, err); + } + ++#if !defined(_WIN32) ++ + static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) + { +@@ -3598,9 +3621,9 @@ + #endif + JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), + JS_CFUNC_DEF("utimes", 3, js_os_utimes ), ++ JS_CFUNC_DEF("realpath", 1, js_os_realpath ), + #if !defined(_WIN32) + JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), +- JS_CFUNC_DEF("realpath", 1, js_os_realpath ), + JS_CFUNC_DEF("symlink", 2, js_os_symlink ), + JS_CFUNC_DEF("readlink", 1, js_os_readlink ), + JS_CFUNC_DEF("exec", 1, js_os_exec ), diff --git a/src/generator/config/nodemanip.cpp b/src/generator/config/nodemanip.cpp new file mode 100644 index 0000000..b3f32af --- /dev/null +++ b/src/generator/config/nodemanip.cpp @@ -0,0 +1,476 @@ +#include +#include +#include +#include + +#include "../../handler/webget.h" +#include "../../parser/config/proxy.h" +#include "../../parser/infoparser.h" +#include "../../parser/subparser.h" +#include "../../script/script_quickjs.h" +#include "../../utils/file_extra.h" +#include "../../utils/logger.h" +#include "../../utils/map_extra.h" +#include "../../utils/network.h" +#include "../../utils/regexp.h" +#include "../../utils/urlencode.h" +#include "nodemanip.h" +#include "subexport.h" + +std::string override_conf_port; +bool ss_libev, ssr_libev; +extern int gCacheSubscription; +extern bool gScriptCleanContext; + +int explodeConf(const std::string &filepath, std::vector &nodes) +{ + return explodeConfContent(fileGet(filepath), nodes); +} + +void copyNodes(std::vector &source, std::vector &dest) +{ + std::move(source.begin(), source.end(), std::back_inserter(dest)); +} + +int addNodes(std::string link, std::vector &allNodes, int groupID, parse_settings &parse_set) +{ + std::string &proxy = *parse_set.proxy, subInfo = *parse_set.sub_info; + string_array &exclude_remarks = *parse_set.exclude_remarks; + string_array &include_remarks = *parse_set.include_remarks; + string_array &stream_rules = *parse_set.stream_rules; + string_array &time_rules = *parse_set.time_rules; + string_icase_map &request_headers = *parse_set.request_header; + bool &authorized = parse_set.authorized; + + ConfType linkType = ConfType::Unknow; + std::vector nodes; + Proxy node; + std::string strSub, extra_headers, custom_group; + + // TODO: replace with startsWith if appropriate + link = replaceAllDistinct(link, "\"", ""); + + /// script:filepath,arg1,arg2,... + script_safe_runner(parse_set.js_runtime, parse_set.js_context, [&](qjs::Context &ctx) + { + if(startsWith(link, "script:")) /// process subscription with script + { + writeLog(0, "Found script link. Start running...", LOG_LEVEL_INFO); + string_array args = split(link.substr(7), ","); + if(args.size() >= 1) + { + std::string script = fileGet(args[0], false); + try + { + ctx.eval(script); + args.erase(args.begin()); /// remove script path + auto parse = (std::function) ctx.eval("parse"); + switch(args.size()) + { + case 0: + link = parse(std::string(), string_array()); + break; + case 1: + link = parse(args[0], string_array()); + break; + default: + { + std::string first = args[0]; + args.erase(args.begin()); + link = parse(first, args); + break; + } + } + } + catch(qjs::exception) + { + script_print_stack(ctx); + } + } + } + }, gScriptCleanContext); + /* + duk_context *ctx = duktape_init(); + defer(duk_destroy_heap(ctx);) + duktape_peval(ctx, script); + duk_get_global_string(ctx, "parse"); + for(size_t i = 1; i < args.size(); i++) + duk_push_string(ctx, trim(args[i]).c_str()); + if(duk_pcall(ctx, args.size() - 1) == 0) + link = duktape_get_res_str(ctx); + else + { + writeLog(0, "Error when trying to evaluate script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); + duk_pop(ctx); /// pop err + } + */ + + /// tag:group_name,link + if(startsWith(link, "tag:")) + { + string_size pos = link.find(","); + if(pos != link.npos) + { + custom_group = link.substr(4, pos - 4); + link.erase(0, pos + 1); + } + } + + if(link == "nullnode") + { + node.GroupId = 0; + writeLog(0, "Adding node placeholder..."); + allNodes.emplace_back(std::move(node)); + return 0; + } + + writeLog(LOG_TYPE_INFO, "Received Link."); + if(startsWith(link, "https://t.me/socks") || startsWith(link, "tg://socks")) + linkType = ConfType::SOCKS; + else if(startsWith(link, "https://t.me/http") || startsWith(link, "tg://http")) + linkType = ConfType::HTTP; + else if(isLink(link) || startsWith(link, "surge:///install-config")) + linkType = ConfType::SUB; + else if(startsWith(link, "Netch://")) + linkType = ConfType::Netch; + else if(fileExist(link)) + linkType = ConfType::Local; + + switch(linkType) + { + case ConfType::SUB: + writeLog(LOG_TYPE_INFO, "Downloading subscription data..."); + if(startsWith(link, "surge:///install-config")) //surge config link + link = urlDecode(getUrlArg(link, "url")); + strSub = webGet(link, proxy, gCacheSubscription, &extra_headers, &request_headers); + /* + if(strSub.size() == 0) + { + //try to get it again with system proxy + writeLog(LOG_TYPE_WARN, "Cannot download subscription directly. Using system proxy."); + strProxy = getSystemProxy(); + if(strProxy != "") + { + strSub = webGet(link, strProxy); + } + else + writeLog(LOG_TYPE_WARN, "No system proxy is set. Skipping."); + } + */ + if(strSub.size()) + { + writeLog(LOG_TYPE_INFO, "Parsing subscription data..."); + if(explodeConfContent(strSub, nodes) == 0) + { + writeLog(LOG_TYPE_ERROR, "Invalid subscription!"); + return -1; + } + if(startsWith(strSub, "ssd://")) + { + getSubInfoFromSSD(strSub, subInfo); + } + else + { + if(!getSubInfoFromHeader(extra_headers, subInfo)) + getSubInfoFromNodes(nodes, stream_rules, time_rules, subInfo); + } + filterNodes(nodes, exclude_remarks, include_remarks, groupID); + for(Proxy &x : nodes) + { + x.GroupId = groupID; + if(custom_group.size()) + x.Group = custom_group; + } + copyNodes(nodes, allNodes); + } + else + { + writeLog(LOG_TYPE_ERROR, "Cannot download subscription data."); + return -1; + } + break; + case ConfType::Local: + if(!authorized) + return -1; + writeLog(LOG_TYPE_INFO, "Parsing configuration file data..."); + if(explodeConf(link, nodes) == 0) + { + writeLog(LOG_TYPE_ERROR, "Invalid configuration file!"); + return -1; + } + if(startsWith(strSub, "ssd://")) + { + getSubInfoFromSSD(strSub, subInfo); + } + else + { + getSubInfoFromNodes(nodes, stream_rules, time_rules, subInfo); + } + filterNodes(nodes, exclude_remarks, include_remarks, groupID); + for(Proxy &x : nodes) + { + x.GroupId = groupID; + if(custom_group.size()) + x.Group = custom_group; + } + copyNodes(nodes, allNodes); + break; + default: + explode(link, node); + if(node.Type == -1) + { + writeLog(LOG_TYPE_ERROR, "No valid link found."); + return -1; + } + node.GroupId = groupID; + if(custom_group.size()) + node.Group = custom_group; + allNodes.emplace_back(std::move(node)); + } + return 0; +} + +bool chkIgnore(const Proxy &node, string_array &exclude_remarks, string_array &include_remarks) +{ + bool excluded = false, included = false; + //std::string remarks = UTF8ToACP(node.remarks); + //std::string remarks = node.remarks; + //writeLog(LOG_TYPE_INFO, "Comparing exclude remarks..."); + excluded = std::any_of(exclude_remarks.cbegin(), exclude_remarks.cend(), [&node](const auto &x) + { + std::string real_rule; + if(applyMatcher(x, real_rule, node)) + { + if(real_rule.empty()) return true; + return regFind(node.Remark, real_rule); + } + else + return false; + }); + if(include_remarks.size() != 0) + { + //writeLog(LOG_TYPE_INFO, "Comparing include remarks..."); + included = std::any_of(include_remarks.cbegin(), include_remarks.cend(), [&node](const auto &x) + { + std::string real_rule; + if(applyMatcher(x, real_rule, node)) + { + if(real_rule.empty()) return true; + return regFind(node.Remark, real_rule); + } + else + return false; + }); + } + else + { + included = true; + } + + return excluded || !included; +} + +void filterNodes(std::vector &nodes, string_array &exclude_remarks, string_array &include_remarks, int groupID) +{ + int node_index = 0; + std::vector::iterator iter = nodes.begin(); + while(iter != nodes.end()) + { + if(chkIgnore(*iter, exclude_remarks, include_remarks)) + { + writeLog(LOG_TYPE_INFO, "Node " + iter->Group + " - " + iter->Remark + " has been ignored and will not be added."); + nodes.erase(iter); + } + else + { + writeLog(LOG_TYPE_INFO, "Node " + iter->Group + " - " + iter->Remark + " has been added."); + iter->Id = node_index; + iter->GroupId = groupID; + ++node_index; + ++iter; + } + } + /* + std::vector> exclude_patterns, include_patterns; + std::vector> exclude_match_data, include_match_data; + unsigned int i = 0; + PCRE2_SIZE erroroffset; + int errornumber, rc; + + for(i = 0; i < exclude_remarks.size(); i++) + { + std::unique_ptr pattern(pcre2_compile(reinterpret_cast(exclude_remarks[i].c_str()), exclude_remarks[i].size(), PCRE2_UTF | PCRE2_MULTILINE | PCRE2_ALT_BSUX, &errornumber, &erroroffset, NULL), &pcre2_code_free); + if(!pattern) + return; + exclude_patterns.emplace_back(std::move(pattern)); + pcre2_jit_compile(exclude_patterns[i].get(), 0); + std::unique_ptr match_data(pcre2_match_data_create_from_pattern(exclude_patterns[i].get(), NULL), &pcre2_match_data_free); + exclude_match_data.emplace_back(std::move(match_data)); + } + for(i = 0; i < include_remarks.size(); i++) + { + std::unique_ptr pattern(pcre2_compile(reinterpret_cast(include_remarks[i].c_str()), include_remarks[i].size(), PCRE2_UTF | PCRE2_MULTILINE | PCRE2_ALT_BSUX, &errornumber, &erroroffset, NULL), &pcre2_code_free); + if(!pattern) + return; + include_patterns.emplace_back(std::move(pattern)); + pcre2_jit_compile(include_patterns[i].get(), 0); + std::unique_ptr match_data(pcre2_match_data_create_from_pattern(include_patterns[i].get(), NULL), &pcre2_match_data_free); + include_match_data.emplace_back(std::move(match_data)); + } + writeLog(LOG_TYPE_INFO, "Filter started."); + while(iter != nodes.end()) + { + bool excluded = false, included = false; + for(i = 0; i < exclude_patterns.size(); i++) + { + rc = pcre2_match(exclude_patterns[i].get(), reinterpret_cast(iter->remarks.c_str()), iter->remarks.size(), 0, 0, exclude_match_data[i].get(), NULL); + if (rc < 0) + { + switch(rc) + { + case PCRE2_ERROR_NOMATCH: + break; + default: + return; + } + } + else + excluded = true; + } + if(include_patterns.size() > 0) + for(i = 0; i < include_patterns.size(); i++) + { + rc = pcre2_match(include_patterns[i].get(), reinterpret_cast(iter->remarks.c_str()), iter->remarks.size(), 0, 0, include_match_data[i].get(), NULL); + if (rc < 0) + { + switch(rc) + { + case PCRE2_ERROR_NOMATCH: + break; + default: + return; + } + } + else + included = true; + } + else + included = true; + if(excluded || !included) + { + writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been ignored and will not be added."); + nodes.erase(iter); + } + else + { + writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been added."); + iter->id = node_index; + iter->groupID = groupID; + ++node_index; + ++iter; + } + } + */ + writeLog(LOG_TYPE_INFO, "Filter done."); +} + +bool matchRange(const std::string &range, int target) +{ + string_array vArray = split(range, ","); + bool match = false; + std::string range_begin_str, range_end_str; + int range_begin, range_end; + static const std::string reg_num = "-?\\d+", reg_range = "(\\d+)-(\\d+)", reg_not = "\\!-?(\\d+)", reg_not_range = "\\!(\\d+)-(\\d+)", reg_less = "(\\d+)-", reg_more = "(\\d+)\\+"; + for(std::string &x : vArray) + { + if(regMatch(x, reg_num)) + { + if(to_int(x, INT_MAX) == target) + match = true; + } + else if(regMatch(x, reg_range)) + { + /* + range_begin = to_int(regReplace(x, reg_range, "$1"), INT_MAX); + range_end = to_int(regReplace(x, reg_range, "$2"), INT_MIN); + */ + regGetMatch(x, reg_range, 3, 0, &range_begin_str, &range_end_str); + range_begin = to_int(range_begin_str, INT_MAX); + range_end = to_int(range_end_str, INT_MIN); + if(target >= range_begin && target <= range_end) + match = true; + } + else if(regMatch(x, reg_not)) + { + if(to_int(regReplace(x, reg_not, "$1"), INT_MAX) == target) + match = false; + } + else if(regMatch(x, reg_not_range)) + { + /* + range_begin = to_int(regReplace(x, reg_range, "$1"), INT_MAX); + range_end = to_int(regReplace(x, reg_range, "$2"), INT_MIN); + */ + regGetMatch(x, reg_range, 3, 0, &range_begin_str, &range_end_str); + range_begin = to_int(range_begin_str, INT_MAX); + range_end = to_int(range_end_str, INT_MIN); + if(target >= range_begin && target <= range_end) + match = false; + } + else if(regMatch(x, reg_less)) + { + if(to_int(regReplace(x, reg_less, "$1"), INT_MAX) >= target) + match = true; + } + else if(regMatch(x, reg_more)) + { + if(to_int(regReplace(x, reg_more, "$1"), INT_MIN) <= target) + match = true; + } + } + return match; +} + +bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy &node) +{ + std::string target, ret_real_rule; + static const std::string groupid_regex = R"(^!!(?:GROUPID|INSERT)=([\d\-+!,]+)(?:!!(.*))?$)", group_regex = R"(^!!(?:GROUP)=(.+?)(?:!!(.*))?$)"; + static const std::string type_regex = R"(^!!(?:TYPE)=(.+?)(?:!!(.*))?$)", port_regex = R"(^!!(?:PORT)=(.+?)(?:!!(.*))?$)", server_regex = R"(^!!(?:SERVER)=(.+?)(?:!!(.*))?$)"; + static const string_array types = {"", "SS", "SSR", "VMESS", "TROJAN", "SNELL", "HTTP", "HTTPS", "SOCKS5"}; + if(startsWith(rule, "!!GROUP=")) + { + regGetMatch(rule, group_regex, 3, 0, &target, &ret_real_rule); + real_rule = ret_real_rule; + return regFind(node.Group, target); + } + else if(startsWith(rule, "!!GROUPID=") || startsWith(rule, "!!INSERT=")) + { + int dir = startsWith(rule, "!!INSERT=") ? -1 : 1; + regGetMatch(rule, groupid_regex, 3, 0, &target, &ret_real_rule); + real_rule = ret_real_rule; + return matchRange(target, dir * node.GroupId); + } + else if(startsWith(rule, "!!TYPE=")) + { + regGetMatch(rule, type_regex, 3, 0, &target, &ret_real_rule); + real_rule = ret_real_rule; + if(node.Type == ProxyType::Unknow) + return false; + return regMatch(types[node.Type], target); + } + else if(startsWith(rule, "!!PORT=")) + { + regGetMatch(rule, port_regex, 3, 0, &target, &ret_real_rule); + real_rule = ret_real_rule; + return matchRange(target, node.Port); + } + else if(startsWith(rule, "!!SERVER=")) + { + regGetMatch(rule, server_regex, 3, 0, &target, &ret_real_rule); + real_rule = ret_real_rule; + return regFind(node.Hostname, target); + } + else + real_rule = rule; + return true; +} diff --git a/src/generator/config/nodemanip.h b/src/generator/config/nodemanip.h new file mode 100644 index 0000000..cbce202 --- /dev/null +++ b/src/generator/config/nodemanip.h @@ -0,0 +1,30 @@ +#ifndef NODEMANIP_H_INCLUDED +#define NODEMANIP_H_INCLUDED + +#include +#include +#include + +#include "../../parser/config/proxy.h" +#include "../../utils/map_extra.h" +#include "../../utils/string.h" + +struct parse_settings +{ + std::string *proxy = nullptr; + string_array *exclude_remarks = nullptr; + string_array *include_remarks = nullptr; + string_array *stream_rules = nullptr; + string_array *time_rules = nullptr; + std::string *sub_info = nullptr; + bool authorized = false; + string_icase_map *request_header = nullptr; + qjs::Runtime *js_runtime = nullptr; + qjs::Context *js_context = nullptr; +}; + +int addNodes(std::string link, std::vector &allNodes, int groupID, parse_settings &parse_set); +void filterNodes(std::vector &nodes, string_array &exclude_remarks, string_array &include_remarks, int groupID); +bool applyMatcher(const std::string &rule, std::string &real_rule, const Proxy &node); + +#endif // NODEMANIP_H_INCLUDED diff --git a/src/generator/config/ruleconvert.cpp b/src/generator/config/ruleconvert.cpp new file mode 100644 index 0000000..c73b5bd --- /dev/null +++ b/src/generator/config/ruleconvert.cpp @@ -0,0 +1,456 @@ +#include + +#include "../../utils/logger.h" +#include "../../utils/network.h" +#include "../../utils/regexp.h" +#include "../../utils/string.h" +#include "subexport.h" + +extern size_t gMaxAllowedRules; + +/// rule type lists +#define basic_types "DOMAIN", "DOMAIN-SUFFIX", "DOMAIN-KEYWORD", "IP-CIDR", "SRC-IP-CIDR", "GEOIP", "MATCH", "FINAL" +string_array ClashRuleTypes = {basic_types, "IP-CIDR6", "SRC-PORT", "DST-PORT", "PROCESS-NAME"}; +string_array Surge2RuleTypes = {basic_types, "IP-CIDR6", "USER-AGENT", "URL-REGEX", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; +string_array SurgeRuleTypes = {basic_types, "IP-CIDR6", "USER-AGENT", "URL-REGEX", "AND", "OR", "NOT", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; +string_array QuanXRuleTypes = {basic_types, "USER-AGENT", "HOST", "HOST-SUFFIX", "HOST-KEYWORD"}; +string_array SurfRuleTypes = {basic_types, "IP-CIDR6", "PROCESS-NAME", "IN-PORT", "DEST-PORT", "SRC-IP"}; + +std::string convertRuleset(const std::string &content, int type) +{ + /// Target: Surge type,pattern[,flag] + /// Source: QuanX type,pattern[,group] + /// Clash payload:\n - 'ipcidr/domain/classic(Surge-like)' + + std::string output, strLine; + + if(type == RULESET_SURGE) + return content; + + if(regFind(content, "^payload:\\r?\\n")) /// Clash + { + output = regReplace(regReplace(content, "payload:\\r?\\n", "", true), "\\s?^\\s*-\\s+('|\"?)(.*)\\1$", "\n$2", true); + if(type == RULESET_CLASH_CLASSICAL) /// classical type + return output; + std::stringstream ss; + ss << output; + char delimiter = getLineBreak(output); + output.clear(); + string_size pos, lineSize; + while(getline(ss, strLine, delimiter)) + { + strLine = trim(strLine); + lineSize = strLine.size(); + if(lineSize && strLine[lineSize - 1] == '\r') //remove line break + strLine.erase(--lineSize); + + if(strFind(strLine, "//")) + { + strLine.erase(strLine.find("//")); + strLine = trimWhitespace(strLine); + } + + if(!strLine.empty() && (strLine[0] != ';' && strLine[0] != '#' && !(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/'))) + { + pos = strLine.find("/"); + if(pos != strLine.npos) /// ipcidr + { + if(isIPv4(strLine.substr(0, pos))) + output += "IP-CIDR,"; + else + output += "IP-CIDR6,"; + } + else + { + if(strLine[0] == '.' || (lineSize >= 2 && strLine[0] == '+' && strLine[1] == '.')) /// suffix + { + bool keyword_flag = false; + while(endsWith(strLine, ".*")) + { + keyword_flag = true; + strLine.erase(strLine.size() - 2); + } + output += "DOMAIN-"; + if(keyword_flag) + output += "KEYWORD,"; + else + output += "SUFFIX,"; + strLine.erase(0, 2 - (strLine[0] == '.')); + } + else + output += "DOMAIN,"; + } + } + output += strLine; + output += '\n'; + } + return output; + } + else /// QuanX + { + output = regReplace(regReplace(content, "^(?i:host)", "DOMAIN", true), "^(?i:ip6-cidr)", "IP-CIDR6", true); //translate type + output = regReplace(output, "^((?i:DOMAIN(?:-(?:SUFFIX|KEYWORD))?|IP-CIDR6?|USER-AGENT),)\\s*?(\\S*?)(?:,(?!no-resolve).*?)(,no-resolve)?$", "\\U$1\\E$2${3:-}", true); //remove group + return output; + } +} + + +void rulesetToClash(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name) +{ + string_array allRules; + std::string rule_group, retrieved_rules, strLine; + std::stringstream strStrm; + const std::string field_name = new_field_name ? "rules" : "Rule"; + YAML::Node Rules; + size_t total_rules = 0; + + if(!overwrite_original_rules && base_rule[field_name].IsDefined()) + Rules = base_rule[field_name]; + + for(ruleset_content &x : ruleset_content_array) + { + if(gMaxAllowedRules && total_rules > gMaxAllowedRules) + break; + rule_group = x.rule_group; + retrieved_rules = x.rule_content.get(); + if(retrieved_rules.empty()) + { + writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING); + continue; + } + if(startsWith(retrieved_rules, "[]")) + { + strLine = retrieved_rules.substr(2); + if(startsWith(strLine, "FINAL")) + strLine.replace(0, 5, "MATCH"); + strLine += "," + rule_group; + if(count_least(strLine, ',', 3)) + strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); + allRules.emplace_back(std::move(strLine)); + total_rules++; + continue; + } + retrieved_rules = convertRuleset(retrieved_rules, x.rule_type); + char delimiter = getLineBreak(retrieved_rules); + + strStrm.clear(); + strStrm< gMaxAllowedRules) + break; + strLine = trimWhitespace(strLine, true, true); //remove whitespaces + lineSize = strLine.size(); + if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored + continue; + if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + if(strFind(strLine, "//")) + { + strLine.erase(strLine.find("//")); + strLine = trimWhitespace(strLine); + } + strLine += "," + rule_group; + if(count_least(strLine, ',', 3)) + strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); + allRules.emplace_back(std::move(strLine)); + //Rules.push_back(strLine); + } + } + + for(std::string &x : allRules) + { + Rules.push_back(x); + } + + base_rule[field_name] = Rules; +} + +std::string rulesetToClashStr(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name) +{ + std::string rule_group, retrieved_rules, strLine; + std::stringstream strStrm; + const std::string field_name = new_field_name ? "rules" : "Rule"; + std::string output_content = "\n" + field_name + ":\n"; + size_t total_rules = 0; + + if(!overwrite_original_rules && base_rule[field_name].IsDefined()) + { + for(size_t i = 0; i < base_rule[field_name].size(); i++) + output_content += " - " + safe_as(base_rule[field_name][i]) + "\n"; + } + base_rule.remove(field_name); + + for(ruleset_content &x : ruleset_content_array) + { + if(gMaxAllowedRules && total_rules > gMaxAllowedRules) + break; + rule_group = x.rule_group; + retrieved_rules = x.rule_content.get(); + if(retrieved_rules.empty()) + { + writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING); + continue; + } + if(startsWith(retrieved_rules, "[]")) + { + strLine = retrieved_rules.substr(2); + if(startsWith(strLine, "FINAL")) + strLine.replace(0, 5, "MATCH"); + strLine += "," + rule_group; + if(count_least(strLine, ',', 3)) + strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); + output_content += " - " + strLine + "\n"; + total_rules++; + continue; + } + retrieved_rules = convertRuleset(retrieved_rules, x.rule_type); + char delimiter = getLineBreak(retrieved_rules); + + strStrm.clear(); + strStrm< gMaxAllowedRules) + break; + strLine = trimWhitespace(strLine, true, true); //remove whitespaces + lineSize = strLine.size(); + if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored + continue; + if(std::none_of(ClashRuleTypes.begin(), ClashRuleTypes.end(), [strLine](std::string type){ return startsWith(strLine, type); })) + continue; + if(strFind(strLine, "//")) + { + strLine.erase(strLine.find("//")); + strLine = trimWhitespace(strLine); + } + strLine += "," + rule_group; + if(count_least(strLine, ',', 3)) + strLine = regReplace(strLine, "^(.*?,.*?)(,.*)(,.*)$", "$1$3$2"); + output_content += " - " + strLine + "\n"; + total_rules++; + } + } + return output_content; +} + +void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix) +{ + string_array allRules; + std::string rule_group, rule_path, rule_path_typed, retrieved_rules, strLine; + std::stringstream strStrm; + size_t total_rules = 0; + + switch(surge_ver) //other version: -3 for Surfboard, -4 for Loon + { + case 0: + base_rule.SetCurrentSection("RoutingRule"); //Mellow + break; + case -1: + base_rule.SetCurrentSection("filter_local"); //Quantumult X + break; + case -2: + base_rule.SetCurrentSection("TCP"); //Quantumult + break; + default: + base_rule.SetCurrentSection("Rule"); + } + + if(overwrite_original_rules) + { + base_rule.EraseSection(); + switch(surge_ver) + { + case -1: + base_rule.EraseSection("filter_remote"); + break; + case -4: + base_rule.EraseSection("Remote Rule"); + break; + } + } + + const std::string rule_match_regex = "^(.*?,.*?)(,.*)(,.*)$"; + + for(ruleset_content &x : ruleset_content_array) + { + if(gMaxAllowedRules && total_rules > gMaxAllowedRules) + break; + rule_group = x.rule_group; + rule_path = x.rule_path; + rule_path_typed = x.rule_path_typed; + if(rule_path.empty()) + { + strLine = x.rule_content.get().substr(2); + if(strLine == "MATCH") + strLine = "FINAL"; + strLine += "," + rule_group; + if(surge_ver == -1 || surge_ver == -2) + { + if(count_least(strLine, ',', 3) && regReplace(strLine, rule_match_regex, "$2") == ",no-resolve") + strLine = regReplace(strLine, rule_match_regex, "$1$3$2"); + else + strLine = regReplace(strLine, rule_match_regex, "$1$3"); + } + else + { + if(!startsWith(strLine, "AND") && !startsWith(strLine, "OR") && !startsWith(strLine, "NOT") && count_least(strLine, ',', 3)) + strLine = regReplace(strLine, rule_match_regex, "$1$3$2"); + } + strLine = replaceAllDistinct(strLine, ",,", ","); + allRules.emplace_back(std::move(strLine)); + total_rules++; + continue; + } + else + { + if(surge_ver == -1 && x.rule_type == RULESET_QUANX && isLink(rule_path)) + { + strLine = rule_path + ", tag=" + rule_group + ", force-policy=" + rule_group + ", enabled=true"; + base_rule.Set("filter_remote", "{NONAME}", strLine); + continue; + } + if(fileExist(rule_path)) + { + if(surge_ver > 2 && remote_path_prefix.size()) + { + strLine = "RULE-SET," + remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group; + if(x.update_interval) + strLine += ",update-interval=" + std::to_string(x.update_interval); + allRules.emplace_back(std::move(strLine)); + continue; + } + else if(surge_ver == -1 && remote_path_prefix.size()) + { + strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group); + strLine += ", tag=" + rule_group + ", enabled=true"; + base_rule.Set("filter_remote", "{NONAME}", strLine); + continue; + } + else if(surge_ver == -4 && remote_path_prefix.size()) + { + strLine = remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group; + base_rule.Set("Remote Rule", "{NONAME}", strLine); + continue; + } + } + else if(isLink(rule_path)) + { + if(surge_ver > 2) + { + if(x.rule_type != RULESET_SURGE) + { + if(remote_path_prefix.size()) + strLine = "RULE-SET," + remote_path_prefix + "/getruleset?type=1&url=" + urlSafeBase64Encode(rule_path_typed) + "," + rule_group; + else + continue; + } + else + strLine = "RULE-SET," + rule_path + "," + rule_group; + + if(x.update_interval) + strLine += ",update-interval=" + std::to_string(x.update_interval); + + allRules.emplace_back(std::move(strLine)); + continue; + } + else if(surge_ver == -1 && remote_path_prefix.size()) + { + strLine = remote_path_prefix + "/getruleset?type=2&url=" + urlSafeBase64Encode(rule_path_typed) + "&group=" + urlSafeBase64Encode(rule_group); + strLine += ", tag=" + rule_group + ", enabled=true"; + base_rule.Set("filter_remote", "{NONAME}", strLine); + continue; + } + else if(surge_ver == -4) + { + strLine = rule_path + "," + rule_group; + base_rule.Set("Remote Rule", "{NONAME}", strLine); + continue; + } + } + else + continue; + retrieved_rules = x.rule_content.get(); + if(retrieved_rules.empty()) + { + writeLog(0, "Failed to fetch ruleset or ruleset is empty: '" + x.rule_path + "'!", LOG_LEVEL_WARNING); + continue; + } + + retrieved_rules = convertRuleset(retrieved_rules, x.rule_type); + char delimiter = getLineBreak(retrieved_rules); + + strStrm.clear(); + strStrm< gMaxAllowedRules) + break; + strLine = trimWhitespace(strLine, true, true); + lineSize = strLine.size(); + if(!lineSize || strLine[0] == ';' || strLine[0] == '#' || (lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/')) //empty lines and comments are ignored + continue; + + /// remove unsupported types + switch(surge_ver) + { + case -2: + if(startsWith(strLine, "IP-CIDR6")) + continue; + [[fallthrough]]; + case -1: + if(!std::any_of(QuanXRuleTypes.begin(), QuanXRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + break; + case -3: + if(!std::any_of(SurfRuleTypes.begin(), SurfRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + break; + default: + if(surge_ver > 2) + { + if(!std::any_of(SurgeRuleTypes.begin(), SurgeRuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + } + else + { + if(!std::any_of(Surge2RuleTypes.begin(), Surge2RuleTypes.end(), [strLine](std::string type){return startsWith(strLine, type);})) + continue; + } + } + + if(strFind(strLine, "//")) + { + strLine.erase(strLine.find("//")); + strLine = trimWhitespace(strLine); + } + + strLine += "," + rule_group; + if(surge_ver == -1 || surge_ver == -2) + { + if(startsWith(strLine, "IP-CIDR6")) + strLine.replace(0, 8, "IP6-CIDR"); + if(count_least(strLine, ',', 3) && regReplace(strLine, rule_match_regex, "$2") == ",no-resolve") + strLine = regReplace(strLine, rule_match_regex, "$1$3$2"); + else + strLine = regReplace(strLine, rule_match_regex, "$1$3"); + } + else + { + if(!startsWith(strLine, "AND") && !startsWith(strLine, "OR") && !startsWith(strLine, "NOT") && count_least(strLine, ',', 3)) + strLine = regReplace(strLine, rule_match_regex, "$1$3$2"); + } + allRules.emplace_back(std::move(strLine)); + total_rules++; + } + } + } + + for(std::string &x : allRules) + { + base_rule.Set("{NONAME}", x); + } +} diff --git a/src/generator/config/ruleconvert.h b/src/generator/config/ruleconvert.h new file mode 100644 index 0000000..99076a6 --- /dev/null +++ b/src/generator/config/ruleconvert.h @@ -0,0 +1,36 @@ +#ifndef RULECONVERT_H_INCLUDED +#define RULECONVERT_H_INCLUDED + +#include +#include +#include + +#include + +#include "../../utils/ini_reader/ini_reader.h" + +enum ruleset_type +{ + RULESET_SURGE, + RULESET_QUANX, + RULESET_CLASH_DOMAIN, + RULESET_CLASH_IPCIDR, + RULESET_CLASH_CLASSICAL +}; + +struct ruleset_content +{ + std::string rule_group; + std::string rule_path; + std::string rule_path_typed; + int rule_type = RULESET_SURGE; + std::shared_future rule_content; + int update_interval = 0; +}; + +std::string convertRuleset(const std::string &content, int type); +void rulesetToClash(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name); +std::string rulesetToClashStr(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name); +void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix); + +#endif // RULECONVERT_H_INCLUDED diff --git a/src/generator/config/subexport.cpp b/src/generator/config/subexport.cpp new file mode 100644 index 0000000..303d496 --- /dev/null +++ b/src/generator/config/subexport.cpp @@ -0,0 +1,2087 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../generator/config/subexport.h" +#include "../../generator/template/templates.h" +#include "../../parser/config/proxy.h" +#include "../../script/script_quickjs.h" +#include "../../utils/bitwise.h" +#include "../../utils/file_extra.h" +#include "../../utils/ini_reader/ini_reader.h" +#include "../../utils/logger.h" +#include "../../utils/network.h" +#include "../../utils/rapidjson_extra.h" +#include "../../utils/regexp.h" +#include "../../utils/stl_extra.h" +#include "../../utils/urlencode.h" +#include "../../utils/yamlcpp_extra.h" +#include "nodemanip.h" +#include "ruleconvert.h" + +extern bool gAPIMode, gSurgeResolveHostname, gScriptCleanContext; +extern string_array ss_ciphers, ssr_ciphers; + +const string_array clashr_protocols = {"origin", "auth_sha1_v4", "auth_aes128_md5", "auth_aes128_sha1", "auth_chain_a", "auth_chain_b"}; +const string_array clashr_obfs = {"plain", "http_simple", "http_post", "random_head", "tls1.2_ticket_auth", "tls1.2_ticket_fastauth"}; +const string_array clash_ssr_ciphers = {"rc4-md5", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "chacha20-ietf", "xchacha20", "none"}; + +std::string vmessLinkConstruct(const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &id, const std::string &aid, const std::string &net, const std::string &path, const std::string &host, const std::string &tls) +{ + rapidjson::StringBuffer sb; + rapidjson::Writer writer(sb); + writer.StartObject(); + writer.Key("v"); + writer.String("2"); + writer.Key("ps"); + writer.String(remarks.data()); + writer.Key("add"); + writer.String(add.data()); + writer.Key("port"); + writer.Int(to_int(port)); + writer.Key("type"); + writer.String(type.empty() ? "none" : type.data()); + writer.Key("id"); + writer.String(id.data()); + writer.Key("aid"); + writer.Int(to_int(aid)); + writer.Key("net"); + writer.String(net.empty() ? "tcp" : net.data()); + writer.Key("path"); + writer.String(path.data()); + writer.Key("host"); + writer.String(host.data()); + writer.Key("tls"); + writer.String(tls.data()); + writer.EndObject(); + return sb.GetString(); +} + +void nodeRename(Proxy &node, const string_array &rename_array, extra_settings &ext) +{ + string_size pos; + std::string match, rep; + std::string &remark = node.Remark, original_remark = node.Remark, returned_remark, real_rule; + + for(const std::string &x : rename_array) + { + if(startsWith(x, "!!script:")) + { + script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx) + { + std::string script = x.substr(9); + if(startsWith(script, "path:")) + script = fileGet(script.substr(5), true); + try + { + ctx.eval(script); + auto rename = (std::function) ctx.eval("rename"); + returned_remark = rename(node); + if(!returned_remark.empty()) + remark = returned_remark; + } + catch (qjs::exception) + { + script_print_stack(ctx); + } + }, gScriptCleanContext); + continue; + } + pos = x.rfind("@"); + match = x.substr(0, pos); + if(pos != x.npos && pos < x.size()) + rep = x.substr(pos + 1); + else + rep.clear(); + if(applyMatcher(match, real_rule, node) && real_rule.size()) + remark = regReplace(remark, real_rule, rep); + } + if(remark.empty()) + remark = original_remark; + return; +} + +std::string removeEmoji(const std::string &orig_remark) +{ + char emoji_id[2] = {(char)-16, (char)-97}; + std::string remark = orig_remark; + while(true) + { + if(remark[0] == emoji_id[0] && remark[1] == emoji_id[1]) + remark.erase(0, 4); + else + break; + } + if(remark.empty()) + return orig_remark; + return remark; +} + +std::string addEmoji(const Proxy &node, const string_array &emoji_array, extra_settings &ext) +{ + std::string real_rule, ret; + string_size pos; + + for(const std::string &x : emoji_array) + { + if(startsWith(x, "!!script:")) + { + std::string result; + script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx) + { + std::string script = x.substr(9); + if(startsWith(script, "path:")) + script = fileGet(script.substr(5), true); + try + { + ctx.eval(script); + auto getEmoji = (std::function) ctx.eval("getEmoji"); + ret = getEmoji(node); + if(!ret.empty()) + result = ret + " " + node.Remark; + } + catch (qjs::exception) + { + script_print_stack(ctx); + } + }, gScriptCleanContext); + if(!result.empty()) + return result; + continue; + } + pos = x.rfind(","); + if(pos == x.npos) + continue; + if(applyMatcher(x.substr(0, pos), real_rule, node) && real_rule.size() && regFind(node.Remark, real_rule)) + return x.substr(pos + 1) + " " + node.Remark; + } + return node.Remark; +} + +void processRemark(std::string &oldremark, std::string &newremark, string_array &remarks_list, bool proc_comma = true) +{ + if(proc_comma) + { + if(oldremark.find(',') != oldremark.npos) + { + oldremark.insert(0, "\""); + oldremark.append("\""); + } + } + newremark = oldremark; + int cnt = 2; + while(std::find(remarks_list.begin(), remarks_list.end(), newremark) != remarks_list.end()) + { + newremark = oldremark + " " + std::to_string(cnt); + cnt++; + } + oldremark = newremark; +} + +void parseGroupTimes(const std::string &src, int *interval, int *tolerance, int *timeout) +{ + std::vector ptrs; + ptrs.push_back(interval); + ptrs.push_back(timeout); + ptrs.push_back(tolerance); + string_size bpos = 0, epos = src.find(","); + for(int *x : ptrs) + { + if(x != NULL) + *x = to_int(src.substr(bpos, epos - bpos), 0); + if(epos != src.npos) + { + bpos = epos + 1; + epos = src.find(",", bpos); + } + else + return; + } + return; +} + +void groupGenerate(std::string &rule, std::vector &nodelist, string_array &filtered_nodelist, bool add_direct, extra_settings &ext) +{ + std::string real_rule; + if(startsWith(rule, "[]") && add_direct) + { + filtered_nodelist.emplace_back(rule.substr(2)); + } + else if(startsWith(rule, "script:")) + { + script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx){ + std::string script = fileGet(rule.substr(7), true); + try + { + ctx.eval(script); + auto filter = (std::function&)>) ctx.eval("filter"); + std::string result_list = filter(nodelist); + filtered_nodelist = split(regTrim(result_list), "\n"); + } + catch (qjs::exception) + { + script_print_stack(ctx); + } + }, gScriptCleanContext); + } + else + { + for(Proxy &x : nodelist) + { + if(applyMatcher(rule, real_rule, x) && (real_rule.empty() || regFind(x.Remark, real_rule)) && std::find(filtered_nodelist.begin(), filtered_nodelist.end(), x.Remark) == filtered_nodelist.end()) + filtered_nodelist.emplace_back(x.Remark); + } + } +} + +void preprocessNodes(std::vector &nodes, extra_settings &ext) +{ + std::for_each(nodes.begin(), nodes.end(), [&ext](Proxy &x) + { + if(ext.remove_emoji) + x.Remark = trim(removeEmoji(x.Remark)); + + nodeRename(x, ext.rename_array, ext); + + if(ext.add_emoji) + x.Remark = addEmoji(x, ext.emoji_array, ext); + }); + + if(ext.sort_flag) + { + bool failed = true; + if(ext.sort_script.size()) + { + std::string script = ext.sort_script; + if(startsWith(script, "path:")) + script = fileGet(script.substr(5), false); + script_safe_runner(ext.js_runtime, ext.js_context, [&](qjs::Context &ctx) + { + ctx.eval(script); + auto compare = (std::function) ctx.eval("compare"); + auto comparer = [&](const Proxy &a, const Proxy &b) + { + if(a.Type == ProxyType::Unknow) + return 1; + if(b.Type == ProxyType::Unknow) + return 0; + return compare(a, b); + }; + std::sort(nodes.begin(), nodes.end(), comparer); + failed = false; + }, gScriptCleanContext); + } + if(failed) std::stable_sort(nodes.begin(), nodes.end(), [](const Proxy &a, const Proxy &b) + { + return a.Remark < b.Remark; + }); + } +} + +void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const string_array &extra_proxy_group, bool clashR, extra_settings &ext) +{ + YAML::Node proxies, singleproxy, singlegroup, original_groups; + std::vector nodelist; + string_array vArray, remarks_list, filtered_nodelist; + /// proxies style + bool block = false, compact = false; + switch(hash_(ext.clash_proxies_style)) + { + case "block"_hash: + block = true; + break; + default: + case "flow"_hash: + break; + case "compact"_hash: + compact = true; + break; + } + + for(Proxy &x : nodes) + { + singleproxy.reset(); + + std::string type = getProxyTypeName(x.Type); + std::string remark; + if(ext.append_proxy_type) + x.Remark = "[" + type + "] " + x.Remark; + + processRemark(x.Remark, remark, remarks_list, false); + + tribool udp = ext.udp; + tribool scv = ext.skip_cert_verify; + udp.define(x.UDP); + scv.define(x.AllowInsecure); + + singleproxy["name"] = remark; + singleproxy["server"] = x.Hostname; + singleproxy["port"] = x.Port; + + switch(x.Type) + { + case ProxyType::Shadowsocks: + //latest clash core removed support for chacha20 encryption + if(ext.filter_deprecated && x.EncryptMethod == "chacha20") + continue; + singleproxy["type"] = "ss"; + singleproxy["cipher"] = x.EncryptMethod; + singleproxy["password"] = x.Password; + if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + switch(hash_(x.Plugin)) + { + case "simple-obfs"_hash: + case "obfs-local"_hash: + singleproxy["plugin"] = "obfs"; + singleproxy["plugin-opts"]["mode"] = urlDecode(getUrlArg(x.PluginOption, "obfs")); + singleproxy["plugin-opts"]["host"] = urlDecode(getUrlArg(x.PluginOption, "obfs-host")); + break; + case "v2ray-plugin"_hash: + singleproxy["plugin"] = "v2ray-plugin"; + singleproxy["plugin-opts"]["mode"] = getUrlArg(x.PluginOption, "mode"); + singleproxy["plugin-opts"]["host"] = getUrlArg(x.PluginOption, "host"); + singleproxy["plugin-opts"]["path"] = getUrlArg(x.PluginOption, "path"); + singleproxy["plugin-opts"]["tls"] = x.PluginOption.find("tls") != std::string::npos; + singleproxy["plugin-opts"]["mux"] = x.PluginOption.find("mux") != std::string::npos; + if(!scv.is_undef()) + singleproxy["plugin-opts"]["skip-cert-verify"] = scv.get(); + break; + } + break; + case ProxyType::VMess: + singleproxy["type"] = "vmess"; + singleproxy["uuid"] = x.UserId; + singleproxy["alterId"] = x.AlterId; + singleproxy["cipher"] = x.EncryptMethod; + singleproxy["tls"] = x.TLSSecure; + if(!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + switch(hash_(x.TransferProtocol)) + { + case "tcp"_hash: + break; + case "ws"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["ws-path"] = x.Path; + if(x.Host.size()) + singleproxy["ws-headers"]["Host"] = x.Host; + if(x.Edge.size()) + singleproxy["ws-headers"]["Edge"] = x.Edge; + break; + case "http"_hash: + singleproxy["network"] = x.TransferProtocol; + singleproxy["http-opts"]["method"] = "GET"; + singleproxy["http-opts"]["path"].push_back(x.Path); + if(x.Host.size()) + singleproxy["http-opts"]["headers"]["Host"].push_back(x.Host); + if(x.Edge.size()) + singleproxy["http-opts"]["headers"]["Edge"].push_back(x.Edge); + break; + default: + continue; + } + break; + case ProxyType::ShadowsocksR: + //ignoring all nodes with unsupported obfs, protocols and encryption + if(ext.filter_deprecated) + { + if(!clashR && std::find(clash_ssr_ciphers.cbegin(), clash_ssr_ciphers.cend(), x.EncryptMethod) == clash_ssr_ciphers.cend()) + continue; + if(std::find(clashr_protocols.cbegin(), clashr_protocols.cend(), x.Protocol) == clashr_protocols.cend()) + continue; + if(std::find(clashr_obfs.cbegin(), clashr_obfs.cend(), x.OBFS) == clashr_obfs.cend()) + continue; + } + + singleproxy["type"] = "ssr"; + singleproxy["cipher"] = x.EncryptMethod == "none" ? "dummy" : x.EncryptMethod; + singleproxy["password"] = x.Password; + if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + singleproxy["protocol"] = x.Protocol; + singleproxy["obfs"] = x.OBFS; + if(clashR) + { + singleproxy["protocolparam"] = x.ProtocolParam; + singleproxy["obfsparam"] = x.OBFSParam; + } + else + { + singleproxy["protocol-param"] = x.ProtocolParam; + singleproxy["obfs-param"] = x.OBFSParam; + } + break; + case ProxyType::SOCKS5: + singleproxy["type"] = "socks5"; + if(!x.Username.empty()) + singleproxy["username"] = x.Username; + if(!x.Password.empty()) + { + singleproxy["password"] = x.Password; + if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit)) + singleproxy["password"].SetTag("str"); + } + if(!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + break; + case ProxyType::HTTP: + singleproxy["type"] = "http"; + if(!x.Username.empty()) + singleproxy["username"] = x.Username; + if(!x.Password.empty()) + { + singleproxy["password"] = x.Password; + if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit)) + singleproxy["password"].SetTag("str"); + } + singleproxy["tls"] = type == "HTTPS"; + if(!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + break; + case ProxyType::Trojan: + singleproxy["type"] = "trojan"; + singleproxy["password"] = x.Password; + if(x.Host.size()) + singleproxy["sni"] = x.Host; + if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + if(!scv.is_undef()) + singleproxy["skip-cert-verify"] = scv.get(); + break; + case ProxyType::Snell: + singleproxy["type"] = "snell"; + singleproxy["psk"] = x.Password; + if(x.OBFS.size()) + { + singleproxy["obfs-opts"]["mode"] = x.OBFS; + if(x.Host.size()) + singleproxy["obfs-opts"]["host"] = x.Host; + } + if(std::all_of(x.Password.begin(), x.Password.end(), ::isdigit) && !x.Password.empty()) + singleproxy["password"].SetTag("str"); + break; + default: + continue; + } + + if(udp) + singleproxy["udp"] = true; + if(block) + singleproxy.SetStyle(YAML::EmitterStyle::Block); + else + singleproxy.SetStyle(YAML::EmitterStyle::Flow); + proxies.push_back(singleproxy); + remarks_list.emplace_back(std::move(remark)); + nodelist.emplace_back(x); + } + + if(compact) + proxies.SetStyle(YAML::EmitterStyle::Flow); + + if(ext.nodelist) + { + YAML::Node provider; + provider["proxies"] = proxies; + yamlnode.reset(provider); + return; + } + + if(ext.clash_new_field_name) + yamlnode["proxies"] = proxies; + else + yamlnode["Proxy"] = proxies; + + string_array providers; + + for(const std::string &x : extra_proxy_group) + { + singlegroup.reset(); + eraseElements(filtered_nodelist); + eraseElements(providers); + unsigned int rules_upper_bound = 0; + + vArray = split(x, "`"); + if(vArray.size() < 3) + continue; + + singlegroup["name"] = vArray[0]; + singlegroup["type"] = vArray[1]; + + int interval = 0, tolerance = 0; + rules_upper_bound = vArray.size(); + switch(hash_(vArray[1])) + { + case "select"_hash: + case "relay"_hash: + break; + case "url-test"_hash: + case "fallback"_hash: + case "load-balance"_hash: + if(rules_upper_bound < 5) + continue; + rules_upper_bound -= 2; + singlegroup["url"] = vArray[rules_upper_bound]; + parseGroupTimes(vArray[rules_upper_bound + 1], &interval, &tolerance, NULL); + if(interval) + singlegroup["interval"] = interval; + if(tolerance) + singlegroup["tolerance"] = tolerance; + break; + default: + continue; + } + + for(unsigned int i = 2; i < rules_upper_bound; i++) + { + if(startsWith(vArray[i], "!!PROVIDER=")) + { + string_array list = split(vArray[i].substr(11), ","); + providers.reserve(providers.size() + list.size()); + std::move(list.begin(), list.end(), std::back_inserter(providers)); + } + else + groupGenerate(vArray[i], nodelist, filtered_nodelist, true, ext); + } + + if(providers.size()) + singlegroup["use"] = providers; + else + { + if(filtered_nodelist.empty()) + filtered_nodelist.emplace_back("DIRECT"); + } + if(!filtered_nodelist.empty()) + singlegroup["proxies"] = filtered_nodelist; + //singlegroup.SetStyle(YAML::EmitterStyle::Flow); + + bool replace_flag = false; + for(unsigned int i = 0; i < original_groups.size(); i++) + { + if(original_groups[i]["name"].as() == vArray[0]) + { + original_groups[i] = singlegroup; + replace_flag = true; + break; + } + } + if(!replace_flag) + original_groups.push_back(singlegroup); + } + + if(ext.clash_new_field_name) + yamlnode["proxy-groups"] = original_groups; + else + yamlnode["Proxy Group"] = original_groups; +} + +std::string proxyToClash(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, bool clashR, extra_settings &ext) +{ + YAML::Node yamlnode; + + try + { + yamlnode = YAML::Load(base_conf); + } + catch (std::exception &e) + { + writeLog(0, std::string("Clash base loader failed with error: ") + e.what(), LOG_LEVEL_ERROR); + return std::string(); + } + + proxyToClash(nodes, yamlnode, extra_proxy_group, clashR, ext); + + if(ext.nodelist) + return YAML::Dump(yamlnode); + + /* + if(ext.enable_rule_generator) + rulesetToClash(yamlnode, ruleset_content_array, ext.overwrite_original_rules, ext.clash_new_field_name); + + return YAML::Dump(yamlnode); + */ + if(!ext.enable_rule_generator) + return YAML::Dump(yamlnode); + + if(ext.managed_config_prefix.size() || ext.clash_script) + { + if(yamlnode["mode"].IsDefined()) + { + if(ext.clash_new_field_name) + yamlnode["mode"] = ext.clash_script ? "script" : "rule"; + else + yamlnode["mode"] = ext.clash_script ? "Script" : "Rule"; + } + + renderClashScript(yamlnode, ruleset_content_array, ext.managed_config_prefix, ext.clash_script, ext.overwrite_original_rules, ext.clash_classical_ruleset); + return YAML::Dump(yamlnode); + } + + std::string output_content = rulesetToClashStr(yamlnode, ruleset_content_array, ext.overwrite_original_rules, ext.clash_new_field_name); + output_content.insert(0, YAML::Dump(yamlnode)); + + return output_content; +} + +std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, int surge_ver, extra_settings &ext) +{ + INIReader ini; + std::string proxy; + std::string output_nodelist; + tribool udp, tfo, scv, tls13; + std::vector nodelist; + unsigned short local_port = 1080; + + string_array vArray, remarks_list, filtered_nodelist, args; + + ini.store_any_line = true; + // filter out sections that requires direct-save + ini.AddDirectSaveSection("General"); + ini.AddDirectSaveSection("Replica"); + ini.AddDirectSaveSection("Rule"); + ini.AddDirectSaveSection("MITM"); + ini.AddDirectSaveSection("Script"); + ini.AddDirectSaveSection("Host"); + ini.AddDirectSaveSection("URL Rewrite"); + ini.AddDirectSaveSection("Header Rewrite"); + if(ini.Parse(base_conf) != 0 && !ext.nodelist) + { + writeLog(0, "Surge base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); + return std::string(); + } + + ini.SetCurrentSection("Proxy"); + ini.EraseSection(); + ini.Set("{NONAME}", "DIRECT = direct"); + + for(Proxy &x : nodes) + { + std::string remark; + if(ext.append_proxy_type) + { + std::string type = getProxyTypeName(x.Type); + x.Remark = "[" + type + "] " + x.Remark; + } + + processRemark(x.Remark, remark, remarks_list); + + std::string &hostname = x.Hostname, &username = x.Username, &password = x.Password, &method = x.EncryptMethod, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &edge = x.Edge, &path = x.Path, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &plugin = x.Plugin, &pluginopts = x.PluginOption; + std::string port = std::to_string(x.Port); + bool &tlssecure = x.TLSSecure; + + udp = ext.udp; + tfo = ext.tfo; + scv = ext.skip_cert_verify; + tls13 = ext.tls13; + udp.define(x.UDP); + tfo.define(x.TCPFastOpen); + scv.define(x.AllowInsecure); + tls13.define(x.TLS13); + + proxy.clear(); + + switch(x.Type) + { + case ProxyType::Shadowsocks: + if(surge_ver >= 3) + { + proxy = "ss, " + hostname + ", " + port + ", encrypt-method=" + method + ", password=" + password; + } + else + { + proxy = "custom, " + hostname + ", " + port + ", " + method + ", " + password + ", https://github.com/ConnersHua/SSEncrypt/raw/master/SSEncrypt.module"; + } + if(plugin.size()) + { + switch(hash_(plugin)) + { + case "simple-obfs"_hash: + case "obfs-local"_hash: + if(pluginopts.size()) + proxy += "," + replaceAllDistinct(pluginopts, ";", ","); + break; + default: + continue; + } + } + break; + case ProxyType::VMess: + if(surge_ver < 4 && surge_ver != -3) + continue; + proxy = "vmess, " + hostname + ", " + port + ", username=" + id + ", tls=" + (tlssecure ? "true" : "false"); + if(tlssecure && !tls13.is_undef()) + proxy += ", tls13=" + std::string(tls13 ? "true" : "false"); + switch(hash_(transproto)) + { + case "tcp"_hash: + break; + case "ws"_hash: + proxy += ", ws=true, ws-path=" + path + ", sni=" + host + ", ws-headers=Host:" + host; + if(edge.size()) + proxy += "|Edge:" + edge; + break; + default: + continue; + } + if(!scv.is_undef()) + proxy += ", skip-cert-verify=" + std::string(scv.get() ? "1" : "0"); + break; + case ProxyType::ShadowsocksR: + if(ext.surge_ssr_path.empty() || surge_ver < 2) + continue; + proxy = "external, exec=\"" + ext.surge_ssr_path + "\", args=\""; + args = {"-l", std::to_string(local_port), "-s", hostname, "-p", port, "-m", method, "-k", password, "-o", obfs, "-O", protocol}; + if(obfsparam.size()) + { + args.emplace_back("-g"); + args.emplace_back(std::move(obfsparam)); + } + if(protoparam.size()) + { + args.emplace_back("-G"); + args.emplace_back(std::move(protoparam)); + } + proxy += std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, std::string b) + { + return std::move(a) + "\", args=\"" + std::move(b); + }); + proxy += "\", local-port=" + std::to_string(local_port); + if(isIPv4(hostname) || isIPv6(hostname)) + proxy += ", addresses=" + hostname; + else if(gSurgeResolveHostname) + proxy += ", addresses=" + hostnameToIPAddr(hostname); + local_port++; + break; + case ProxyType::SOCKS5: + proxy = "socks5, " + hostname + ", " + port; + if(username.size()) + proxy += ", username=" + username; + if(password.size()) + proxy += ", password=" + password; + if(!scv.is_undef()) + proxy += ", skip-cert-verify=" + std::string(scv.get() ? "1" : "0"); + break; + case ProxyType::HTTP: + proxy = "http, " + hostname + ", " + port; + if(username.size()) + proxy += ", username=" + username; + if(password.size()) + proxy += ", password=" + password; + proxy += std::string(", tls=") + (x.TLSSecure ? "true" : "false"); + if(!scv.is_undef()) + proxy += ", skip-cert-verify=" + std::string(scv.get() ? "1" : "0"); + break; + case ProxyType::Trojan: + if(surge_ver < 4) + continue; + proxy = "trojan, " + hostname + ", " + port + ", password=" + password; + if(host.size()) + proxy += ", sni=" + host; + if(!scv.is_undef()) + proxy += ", skip-cert-verify=" + std::string(scv.get() ? "1" : "0"); + break; + case ProxyType::Snell: + proxy = "snell, " + hostname + ", " + port + ", psk=" + password; + if(obfs.size()) + proxy += ", obfs=" + obfs + ", obfs-host=" + host; + break; + default: + continue; + } + + if(!tfo.is_undef()) + proxy += ", tfo=" + tfo.get_str(); + if(!udp.is_undef()) + proxy += ", udp-relay=" + udp.get_str(); + + if(ext.nodelist) + output_nodelist += remark + " = " + proxy + "\n"; + else + { + ini.Set("{NONAME}", remark + " = " + proxy); + nodelist.emplace_back(x); + } + remarks_list.emplace_back(std::move(remark)); + } + + if(ext.nodelist) + return output_nodelist; + + ini.SetCurrentSection("Proxy Group"); + ini.EraseSection(); + for(const std::string &x : extra_proxy_group) + { + //group pref + std::string url; + int interval = 0, tolerance = 0, timeout = 0; + eraseElements(filtered_nodelist); + unsigned int rules_upper_bound = 0; + url.clear(); + proxy.clear(); + + vArray = split(x, "`"); + if(vArray.size() < 3) + continue; + + rules_upper_bound = vArray.size(); + switch(hash_(vArray[1])) + { + case "select"_hash: + break; + case "load-balance"_hash: + if(surge_ver < 1) + continue; + [[fallthrough]]; + case "url-test"_hash: + case "fallback"_hash: + if(rules_upper_bound < 5) + continue; + rules_upper_bound -= 2; + url = vArray[rules_upper_bound]; + parseGroupTimes(vArray[rules_upper_bound + 1], &interval, &tolerance, &timeout); + break; + case "ssid"_hash: + if(rules_upper_bound < 4) + continue; + proxy = vArray[1] + ",default=" + vArray[2] + ","; + proxy += std::accumulate(vArray.begin() + 4, vArray.end(), vArray[3], [](std::string a, std::string b) + { + return std::move(a) + "," + std::move(b); + }); + ini.Set("{NONAME}", vArray[0] + " = " + proxy); //insert order + continue; + default: + continue; + } + + for(unsigned int i = 2; i < rules_upper_bound; i++) + groupGenerate(vArray[i], nodelist, filtered_nodelist, true, ext); + + if(!filtered_nodelist.size()) + filtered_nodelist.emplace_back("DIRECT"); + + if(filtered_nodelist.size() == 1) + { + proxy = toLower(filtered_nodelist[0]); + switch(hash_(proxy)) + { + case "direct"_hash: + case "reject"_hash: + case "reject-tinygif"_hash: + ini.Set("Proxy", "{NONAME}", vArray[0] + " = " + proxy); + continue; + } + } + + proxy = vArray[1] + ","; + proxy += std::accumulate(std::next(filtered_nodelist.cbegin()), filtered_nodelist.cend(), filtered_nodelist[0], [](std::string a, std::string b) + { + return std::move(a) + "," + std::move(b); + }); + if(vArray[1] == "url-test" || vArray[1] == "fallback") + { + proxy += ",url=" + url + ",interval=" + std::to_string(interval); + if(tolerance > 0) + proxy += ",tolerance=" + std::to_string(tolerance); + if(timeout > 0) + proxy += ",timeout=" + std::to_string(timeout); + } + else if(vArray[1] == "load-balance") + proxy += ",url=" + url; + + ini.Set("{NONAME}", vArray[0] + " = " + proxy); //insert order + } + + if(ext.enable_rule_generator) + rulesetToSurge(ini, ruleset_content_array, surge_ver, ext.overwrite_original_rules, ext.managed_config_prefix); + + return ini.ToString(); +} + +std::string proxyToSingle(std::vector &nodes, int types, extra_settings &ext) +{ + /// types: SS=1 SSR=2 VMess=4 Trojan=8 + rapidjson::Document json; + std::string remark, hostname, port, password, method; + std::string plugin, pluginopts; + std::string protocol, protoparam, obfs, obfsparam; + std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret; + std::string proxyStr, allLinks; + bool ss = GETBIT(types, 1), ssr = GETBIT(types, 2), vmess = GETBIT(types, 3), trojan = GETBIT(types, 4); + + for(Proxy &x : nodes) + { + remark = x.Remark; + std::string &hostname = x.Hostname, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &faketype = x.FakeType; + bool &tlssecure = x.TLSSecure; + std::string port = std::to_string(x.Port); + std::string aid = std::to_string(x.AlterId); + + switch(x.Type) + { + case ProxyType::Shadowsocks: + if(ss) + { + proxyStr = "ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port; + if(plugin.size() && pluginopts.size()) + { + proxyStr += "/?plugin=" + urlEncode(plugin + ";" + pluginopts); + } + proxyStr += "#" + urlEncode(remark); + } + else if(ssr) + { + if(std::count(ssr_ciphers.begin(), ssr_ciphers.end(), method) > 0 && !GetMember(json, "Plugin").size() && !GetMember(json, "Plugin").size()) + proxyStr = "ssr://" + urlSafeBase64Encode(hostname + ":" + port + ":origin:" + method + ":plain:" + urlSafeBase64Encode(password) \ + + "/?group=" + urlSafeBase64Encode(x.Group) + "&remarks=" + urlSafeBase64Encode(remark)); + } + else + continue; + break; + case ProxyType::ShadowsocksR: + if(ssr) + { + proxyStr = "ssr://" + urlSafeBase64Encode(hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + urlSafeBase64Encode(password) \ + + "/?group=" + urlSafeBase64Encode(x.Group) + "&remarks=" + urlSafeBase64Encode(remark) \ + + "&obfsparam=" + urlSafeBase64Encode(obfsparam) + "&protoparam=" + urlSafeBase64Encode(protoparam)); + } + else if(ss) + { + if(std::count(ss_ciphers.begin(), ss_ciphers.end(), method) > 0 && protocol == "origin" && obfs == "plain") + proxyStr = "ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port + "#" + urlEncode(remark); + } + else + continue; + break; + case ProxyType::VMess: + if(!vmess) + continue; + proxyStr = "vmess://" + base64Encode(vmessLinkConstruct(remark, hostname, port, faketype, id, aid, transproto, path, host, tlssecure ? "tls" : "")); + break; + case ProxyType::Trojan: + if(!trojan) + continue; + proxyStr = "trojan://" + password + "@" + hostname + ":" + port + "?allowInsecure=" + (x.AllowInsecure.get() ? "1" : "0"); + if(!host.empty()) + proxyStr += "&sni=" + host; + proxyStr += "#" + urlEncode(remark); + break; + default: + continue; + } + allLinks += proxyStr + "\n"; + } + + if(ext.nodelist) + return allLinks; + else + return base64Encode(allLinks); +} + +std::string proxyToSSSub(std::string &base_conf, std::vector &nodes, extra_settings &ext) +{ + rapidjson::Document json, base; + std::string remark, hostname, password, method; + std::string plugin, pluginopts; + std::string protocol, obfs; + std::string output_content; + + rapidjson::Document::AllocatorType &alloc = json.GetAllocator(); + json.SetObject(); + json.AddMember("remarks", "", alloc); + json.AddMember("server", "", alloc); + json.AddMember("server_port", 0, alloc); + json.AddMember("method", "", alloc); + json.AddMember("password", "", alloc); + json.AddMember("plugin", "", alloc); + json.AddMember("plugin_opts", "", alloc); + + rapidjson::ParseResult result = base.Parse(base_conf.data()); + if(result) + { + for(auto iter = base.MemberBegin(); iter != base.MemberEnd(); iter++) + json.AddMember(iter->name, iter->value, alloc); + } + else + writeLog(0, std::string("SIP008 base loader failed with error: ") + rapidjson::GetParseError_En(result.Code()) + " (" + std::to_string(result.Offset()) + ")", LOG_LEVEL_ERROR); + + rapidjson::Value jsondata; + jsondata = json.Move(); + + output_content = "["; + for(Proxy &x : nodes) + { + remark = x.Remark; + hostname = x.Hostname; + int port = (unsigned short)to_int(GetMember(json, "Port")); + std::string &password = x.Password; + std::string &method = x.EncryptMethod; + std::string &plugin = x.Plugin; + std::string &pluginopts = x.PluginOption; + std::string &protocol = x.Protocol; + std::string &obfs = x.OBFS; + + switch(x.Type) + { + case ProxyType::Shadowsocks: + if(plugin == "simple-obfs") + plugin = "obfs-local"; + break; + case ProxyType::ShadowsocksR: + if(std::count(ss_ciphers.begin(), ss_ciphers.end(), method) > 0 && protocol == "origin" && obfs == "plain") + continue; + break; + default: + continue; + } + jsondata["remarks"].SetString(rapidjson::StringRef(remark.c_str(), remark.size())); + jsondata["server"].SetString(rapidjson::StringRef(hostname.c_str(), hostname.size())); + jsondata["server_port"] = port; + jsondata["password"].SetString(rapidjson::StringRef(password.c_str(), password.size())); + jsondata["method"].SetString(rapidjson::StringRef(method.c_str(), method.size())); + jsondata["plugin"].SetString(rapidjson::StringRef(plugin.c_str(), plugin.size())); + jsondata["plugin_opts"].SetString(rapidjson::StringRef(pluginopts.c_str(), pluginopts.size())); + output_content += SerializeObject(jsondata) + ","; + } + if(output_content.size() > 1) + output_content.erase(output_content.size() - 1); + output_content += "]"; + return output_content; +} + +std::string proxyToQuan(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext) +{ + INIReader ini; + ini.store_any_line = true; + if(!ext.nodelist && ini.Parse(base_conf) != 0) + { + writeLog(0, "Quantumult base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); + return std::string(); + } + + proxyToQuan(nodes, ini, ruleset_content_array, extra_proxy_group, ext); + + if(ext.nodelist) + { + string_array allnodes; + std::string allLinks; + ini.GetAll("SERVER", "{NONAME}", allnodes); + if(allnodes.size()) + allLinks = std::accumulate(std::next(allnodes.begin()), allnodes.end(), allnodes[0], [](std::string a, std::string b) + { + return std::move(a) + "\n" + std::move(b); + }); + return base64Encode(allLinks); + } + return ini.ToString(); +} + +void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext) +{ + std::string type, proxyStr; + tribool scv; + std::vector nodelist; + string_array remarks_list; + + ini.SetCurrentSection("SERVER"); + ini.EraseSection(); + for(Proxy &x : nodes) + { + std::string remark = x.Remark; + + if(ext.append_proxy_type) + { + std::string type = getProxyTypeName(x.Type); + x.Remark = "[" + type + "] " + x.Remark; + } + + processRemark(x.Remark, remark, remarks_list); + + std::string &hostname = x.Hostname, &method = x.EncryptMethod, &password = x.Password, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &edge = x.Edge, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &plugin = x.Plugin, &pluginopts = x.PluginOption, &username = x.Username; + std::string port = std::to_string(x.Port); + bool &tlssecure = x.TLSSecure; + + switch(x.Type) + { + case ProxyType::VMess: + scv = ext.skip_cert_verify; + scv.define(x.AllowInsecure); + + if(method == "auto") + method = "chacha20-ietf-poly1305"; + proxyStr = remark + " = vmess, " + hostname + ", " + port + ", " + method + ", \"" + id + "\", group=" + x.Group; + if(tlssecure) + { + proxyStr += ", over-tls=true, tls-host=" + host; + if(!scv.is_undef()) + proxyStr += ", certificate=" + std::string(scv.get() ? "0" : "1"); + } + if(transproto == "ws") + { + proxyStr += ", obfs=ws, obfs-path=\"" + path + "\", obfs-header=\"Host: " + host; + if(edge.size()) + proxyStr += "[Rr][Nn]Edge: " + edge; + proxyStr += "\""; + } + + if(ext.nodelist) + proxyStr = "vmess://" + urlSafeBase64Encode(proxyStr); + break; + case ProxyType::ShadowsocksR: + if(ext.nodelist) + { + proxyStr = "ssr://" + urlSafeBase64Encode(hostname + ":" + port + ":" + protocol + ":" + method + ":" + obfs + ":" + urlSafeBase64Encode(password) \ + + "/?group=" + urlSafeBase64Encode(x.Group) + "&remarks=" + urlSafeBase64Encode(remark) \ + + "&obfsparam=" + urlSafeBase64Encode(obfsparam) + "&protoparam=" + urlSafeBase64Encode(protoparam)); + } + else + { + proxyStr = remark + " = shadowsocksr, " + hostname + ", " + port + ", " + method + ", \"" + password + "\", group=" + x.Group + ", protocol=" + protocol + ", obfs=" + obfs; + if(protoparam.size()) + proxyStr += ", protocol_param=" + protoparam; + if(obfsparam.size()) + proxyStr += ", obfs_param=" + obfsparam; + } + break; + case ProxyType::Shadowsocks: + if(ext.nodelist) + { + proxyStr = "ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port; + if(plugin.size() && pluginopts.size()) + { + proxyStr += "/?plugin=" + urlEncode(plugin + ";" + pluginopts); + } + proxyStr += "&group=" + urlSafeBase64Encode(x.Group) + "#" + urlEncode(remark); + } + else + { + proxyStr = remark + " = shadowsocks, " + hostname + ", " + port + ", " + method + ", \"" + password + "\", group=" + x.Group; + if(plugin == "simple-obfs" && pluginopts.size()) + { + proxyStr += ", " + replaceAllDistinct(pluginopts, ";", ", "); + } + } + break; + case ProxyType::HTTP: + proxyStr = remark + " = http, upstream-proxy-address=" + hostname + ", upstream-proxy-port=" + port + ", group=" + x.Group; + if(username.size() && password.size()) + proxyStr += ", upstream-proxy-auth=true, upstream-proxy-username=" + username + ", upstream-proxy-password=" + password; + else + proxyStr += ", upstream-proxy-auth=false"; + + if(tlssecure) + { + proxyStr += ", over-tls=true"; + if(host.size()) + proxyStr += ", tls-host=" + host; + if(!scv.is_undef()) + proxyStr += ", certificate=" + std::string(scv.get() ? "0" : "1"); + } + + if(ext.nodelist) + proxyStr = "http://" + urlSafeBase64Encode(proxyStr); + break; + case ProxyType::SOCKS5: + proxyStr = remark + " = socks, upstream-proxy-address=" + hostname + ", upstream-proxy-port=" + port + ", group=" + x.Group; + if(username.size() && password.size()) + proxyStr += ", upstream-proxy-auth=true, upstream-proxy-username=" + username + ", upstream-proxy-password=" + password; + else + proxyStr += ", upstream-proxy-auth=false"; + + if(tlssecure) + { + proxyStr += ", over-tls=true"; + if(host.size()) + proxyStr += ", tls-host=" + host; + if(!scv.is_undef()) + proxyStr += ", certificate=" + std::string(scv.get() ? "0" : "1"); + } + + if(ext.nodelist) + proxyStr = "socks://" + urlSafeBase64Encode(proxyStr); + break; + default: + continue; + } + + ini.Set("{NONAME}", proxyStr); + remarks_list.emplace_back(std::move(remark)); + nodelist.emplace_back(x); + } + + if(ext.nodelist) + return; + + string_array filtered_nodelist; + ini.SetCurrentSection("POLICY"); + ini.EraseSection(); + + std::string singlegroup; + std::string name, proxies; + string_array vArray; + for(const std::string &x : extra_proxy_group) + { + eraseElements(filtered_nodelist); + unsigned int rules_upper_bound = 0; + + vArray = split(x, "`"); + if(vArray.size() < 3) + continue; + + rules_upper_bound = vArray.size(); + switch(hash_(vArray[1])) + { + case "select"_hash: + type = "static"; + break; + case "fallback"_hash: + type = "static"; + if(vArray.size() < 5) + continue; + rules_upper_bound -= 2; + break; + case "url-test"_hash: + type = "auto"; + if(vArray.size() < 5) + continue; + rules_upper_bound -= 2; + break; + case "load-balance"_hash: + type = "balance, round-robin"; + if(vArray.size() < 5) + continue; + rules_upper_bound -= 2; + break; + case "ssid"_hash: + { + if(rules_upper_bound < 4) + continue; + singlegroup = vArray[0] + " : wifi = " + vArray[2]; + std::string content, celluar, celluar_matcher = R"(^(.*?),?celluar\s?=\s?(.*?)(,.*)$)", rem_a, rem_b; + for(auto iter = vArray.begin() + 3; iter != vArray.end(); iter++) + { + if(regGetMatch(*iter, celluar_matcher, 4, 0, &rem_a, &celluar, &rem_b)) + { + content += *iter + "\n"; + continue; + } + content += rem_a + rem_b + "\n"; + } + if(celluar.size()) + singlegroup += ", celluar = " + celluar; + singlegroup += "\n" + replaceAllDistinct(trimOf(content, ','), ",", "\n"); + ini.Set("{NONAME}", base64Encode(singlegroup)); //insert order + } + continue; + default: + continue; + } + + name = vArray[0]; + + for(unsigned int i = 2; i < rules_upper_bound; i++) + groupGenerate(vArray[i], nodelist, filtered_nodelist, true, ext); + + if(!filtered_nodelist.size()) + filtered_nodelist.emplace_back("direct"); + + if(filtered_nodelist.size() < 2) // force groups with 1 node to be static + type = "static"; + + proxies = std::accumulate(std::next(filtered_nodelist.begin()), filtered_nodelist.end(), filtered_nodelist[0], [](std::string a, std::string b) + { + return std::move(a) + "\n" + std::move(b); + }); + + singlegroup = name + " : " + type; + if(type == "static") + singlegroup += ", " + filtered_nodelist[0]; + singlegroup += "\n" + proxies + "\n"; + ini.Set("{NONAME}", base64Encode(singlegroup)); + } + + if(ext.enable_rule_generator) + rulesetToSurge(ini, ruleset_content_array, -2, ext.overwrite_original_rules, std::string()); +} + +std::string proxyToQuanX(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext) +{ + INIReader ini; + ini.store_any_line = true; + ini.AddDirectSaveSection("general"); + ini.AddDirectSaveSection("dns"); + ini.AddDirectSaveSection("rewrite_remote"); + ini.AddDirectSaveSection("rewrite_local"); + ini.AddDirectSaveSection("task_local"); + ini.AddDirectSaveSection("mitm"); + ini.AddDirectSaveSection("server_remote"); + if(!ext.nodelist && ini.Parse(base_conf) != 0) + { + writeLog(0, "QuantumultX base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); + return std::string(); + } + + proxyToQuanX(nodes, ini, ruleset_content_array, extra_proxy_group, ext); + + if(ext.nodelist) + { + string_array allnodes; + std::string allLinks; + ini.GetAll("server_local", "{NONAME}", allnodes); + if(allnodes.size()) + allLinks = std::accumulate(std::next(allnodes.begin()), allnodes.end(), allnodes[0], [](std::string a, std::string b) + { + return std::move(a) + "\n" + std::move(b); + }); + return allLinks; + } + return ini.ToString(); +} + +void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext) +{ + std::string type; + std::string remark, hostname, port, method; + std::string password, plugin, pluginopts; + std::string id, transproto, host, path; + std::string protocol, protoparam, obfs, obfsparam; + std::string proxyStr; + tribool udp, tfo, scv, tls13; + std::vector nodelist; + string_array remarks_list; + + ini.SetCurrentSection("server_local"); + ini.EraseSection(); + for(Proxy &x : nodes) + { + if(ext.append_proxy_type) + x.Remark = "[" + type + "] " + x.Remark; + + processRemark(x.Remark, remark, remarks_list); + + std::string &hostname = x.Hostname, &method = x.EncryptMethod, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &password = x.Password, &plugin = x.Plugin, &pluginopts = x.PluginOption, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam, &username = x.Username; + std::string port = std::to_string(x.Port); + bool &tlssecure = x.TLSSecure; + + udp = ext.udp; + tfo = ext.tfo; + scv = ext.skip_cert_verify; + tls13 = ext.tls13; + udp.define(x.UDP); + tfo.define(x.TCPFastOpen); + scv.define(x.AllowInsecure); + tls13.define(x.TLS13); + + switch(x.Type) + { + case ProxyType::VMess: + if(method == "auto") + method = "chacha20-ietf-poly1305"; + proxyStr = "vmess = " + hostname + ":" + port + ", method=" + method + ", password=" + id; + if(tlssecure && !tls13.is_undef()) + proxyStr += ", tls13=" + std::string(tls13 ? "true" : "false"); + if(transproto == "ws") + { + if(tlssecure) + proxyStr += ", obfs=wss"; + else + proxyStr += ", obfs=ws"; + proxyStr += ", obfs-host=" + host + ", obfs-uri=" + path; + } + else if(tlssecure) + proxyStr += ", obfs=over-tls, obfs-host=" + host; + break; + case ProxyType::Shadowsocks: + proxyStr = "shadowsocks = " + hostname + ":" + port + ", method=" + method + ", password=" + password; + if(plugin.size()) + { + switch(hash_(plugin)) + { + case "simple-obfs"_hash: + case "obfs-local"_hash: + if(pluginopts.size()) + proxyStr += ", " + replaceAllDistinct(pluginopts, ";", ", "); + break; + case "v2ray-plugin"_hash: + pluginopts = replaceAllDistinct(pluginopts, ";", "&"); + plugin = getUrlArg(pluginopts, "mode") == "websocket" ? "ws" : ""; + host = getUrlArg(pluginopts, "host"); + path = getUrlArg(pluginopts, "path"); + tlssecure = pluginopts.find("tls") != pluginopts.npos; + if(tlssecure && plugin == "ws") + { + plugin += 's'; + if(!tls13.is_undef()) + proxyStr += ", tls13=" + std::string(tls13 ? "true" : "false"); + } + proxyStr += ", obfs=" + plugin; + if(host.size()) + proxyStr += ", obfs-host=" + host; + if(path.size()) + proxyStr += ", obfs-uri=" + path; + break; + default: continue; + } + } + + break; + case ProxyType::ShadowsocksR: + proxyStr = "shadowsocks = " + hostname + ":" + port + ", method=" + method + ", password=" + password + ", ssr-protocol=" + protocol; + if(protoparam.size()) + proxyStr += ", ssr-protocol-param=" + protoparam; + proxyStr += ", obfs=" + obfs; + if(obfsparam.size()) + proxyStr += ", obfs-host=" + obfsparam; + break; + case ProxyType::HTTP: + proxyStr = "http = " + hostname + ":" + port + ", username=" + (username.size() ? username : "none") + ", password=" + (password.size() ? password : "none"); + if(tlssecure) + { + proxyStr += ", over-tls=true"; + if(!tls13.is_undef()) + proxyStr += ", tls13=" + std::string(tls13 ? "true" : "false"); + } + break; + case ProxyType::Trojan: + proxyStr = "trojan = " + hostname + ":" + port + ", password=" + password; + if(tlssecure) + { + proxyStr += ", over-tls=true, tls-host=" + host; + if(!tls13.is_undef()) + proxyStr += ", tls13=" + std::string(tls13 ? "true" : "false"); + } + break; + default: + continue; + } + if(!tfo.is_undef()) + proxyStr += ", fast-open=" + tfo.get_str(); + if(!udp.is_undef()) + proxyStr += ", udp-relay=" + udp.get_str(); + if(!scv.is_undef() && (x.Type == ProxyType::HTTP || x.Type == ProxyType::Trojan)) + proxyStr += ", tls-verification=" + scv.reverse().get_str(); + proxyStr += ", tag=" + remark; + + ini.Set("{NONAME}", proxyStr); + remarks_list.emplace_back(std::move(remark)); + nodelist.emplace_back(x); + } + + if(ext.nodelist) + return; + + string_multimap original_groups; + string_array filtered_nodelist; + ini.SetCurrentSection("policy"); + ini.GetItems(original_groups); + ini.EraseSection(); + + std::string singlegroup; + std::string name, proxies; + string_array vArray; + for(const std::string &x : extra_proxy_group) + { + eraseElements(filtered_nodelist); + unsigned int rules_upper_bound = 0; + + vArray = split(x, "`"); + if(vArray.size() < 3) + continue; + + rules_upper_bound = vArray.size(); + switch(hash_(vArray[1])) + { + case "select"_hash: + type = "static"; + break; + case "url-test"_hash: + case "fallback"_hash: + type = "available"; + if(rules_upper_bound < 5) + continue; + rules_upper_bound -= 2; + break; + case "load-balance"_hash: + type = "round-robin"; + if(rules_upper_bound < 5) + continue; + rules_upper_bound -= 2; + break; + case "ssid"_hash: + if(rules_upper_bound < 4) + continue; + type = "ssid"; + for(auto iter = vArray.begin() + 2; iter != vArray.end(); iter++) + filtered_nodelist.emplace_back(replaceAllDistinct(*iter, "=", ":")); + break; + default: + continue; + } + + name = vArray[0]; + + if(hash_(vArray[1]) != "ssid"_hash) + { + for(unsigned int i = 2; i < rules_upper_bound; i++) + groupGenerate(vArray[i], nodelist, filtered_nodelist, true, ext); + + if(!filtered_nodelist.size()) + filtered_nodelist.emplace_back("direct"); + + if(filtered_nodelist.size() < 2) // force groups with 1 node to be static + type = "static"; + } + + auto iter = std::find_if(original_groups.begin(), original_groups.end(), [name](const string_multimap::value_type &n) + { + std::string groupdata = n.second; + std::string::size_type cpos = groupdata.find(","); + if(cpos != groupdata.npos) + return trim(groupdata.substr(0, cpos)) == name; + else + return false; + }); + if(iter != original_groups.end()) + { + vArray = split(iter->second, ","); + if(vArray.size() > 1) + { + if(trim(vArray[vArray.size() - 1]).find("img-url") == 0) + filtered_nodelist.emplace_back(trim(vArray[vArray.size() - 1])); + } + } + + proxies = std::accumulate(std::next(filtered_nodelist.begin()), filtered_nodelist.end(), filtered_nodelist[0], [](std::string a, std::string b) + { + return std::move(a) + ", " + std::move(b); + }); + + singlegroup = type + "=" + name + ", " + proxies; + ini.Set("{NONAME}", singlegroup); + } + + if(ext.enable_rule_generator) + rulesetToSurge(ini, ruleset_content_array, -1, ext.overwrite_original_rules, ext.managed_config_prefix); + + //process scripts + string_multimap scripts; + std::string content, title, url; + const std::string pattern = "^(.*? url script-.*? )(.*?)$"; + if(ini.SectionExist("rewrite_local") && ext.quanx_dev_id.size()) + { + ini.GetItems("rewrite_local", scripts); + ini.EraseSection("rewrite_local"); + ini.SetCurrentSection("rewrite_local"); + for(auto &x : scripts) + { + title = x.first; + if(title != "{NONAME}") + content = title + "=" + x.second; + else + content = x.second; + + if(regMatch(content, pattern)) + { + url = regReplace(content, pattern, "$2"); + if(isLink(url)) + { + url = ext.managed_config_prefix + "/qx-script?id=" + ext.quanx_dev_id + "&url=" + urlSafeBase64Encode(url); + content = regReplace(content, pattern, "$1") + url; + } + } + ini.Set("{NONAME}", content); + } + } + eraseElements(scripts); + string_size pos; + if(ini.SectionExist("rewrite_remote") && ext.quanx_dev_id.size()) + { + ini.GetItems("rewrite_remote", scripts); + ini.EraseSection("rewrite_remote"); + ini.SetCurrentSection("rewrite_remote"); + for(auto &x : scripts) + { + title = x.first; + if(title != "{NONAME}") + content = title + "=" + x.second; + else + content = x.second; + + if(isLink(content)) + { + pos = content.find(","); + url = ext.managed_config_prefix + "/qx-rewrite?id=" + ext.quanx_dev_id + "&url=" + urlSafeBase64Encode(content.substr(0, pos)); + if(pos != content.npos) + url += content.substr(pos); + content = url; + } + ini.Set("{NONAME}", content); + } + } +} + +std::string proxyToSSD(std::vector &nodes, std::string &group, std::string &userinfo, extra_settings &ext) +{ + rapidjson::Document json; + rapidjson::StringBuffer sb; + rapidjson::Writer writer(sb); + int port, index = 0; + + if(!group.size()) + group = "SSD"; + + writer.StartObject(); + writer.Key("airport"); + writer.String(group.data()); + writer.Key("port"); + writer.Int(1); + writer.Key("encryption"); + writer.String("aes-128-gcm"); + writer.Key("password"); + writer.String("password"); + if(userinfo.size()) + { + std::string data = replaceAllDistinct(userinfo, "; ", "&"); + std::string upload = getUrlArg(data, "upload"), download = getUrlArg(data, "download"), total = getUrlArg(data, "total"), expiry = getUrlArg(data, "expire"); + double used = (to_number(upload, 0.0) + to_number(download, 0.0)) / std::pow(1024, 3) * 1.0, tot = to_number(total, 0.0) / std::pow(1024, 3) * 1.0; + writer.Key("traffic_used"); + writer.Double(used); + writer.Key("traffic_total"); + writer.Double(tot); + if(expiry.size()) + { + const time_t rawtime = to_int(expiry); + char buffer[30]; + struct tm *dt = localtime(&rawtime); + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M", dt); + writer.Key("expiry"); + writer.String(buffer); + } + } + writer.Key("servers"); + writer.StartArray(); + + for(Proxy &x : nodes) + { + std::string &hostname = x.Hostname, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &protocol = x.Protocol, &obfs = x.OBFS; + port = (unsigned short)to_int(GetMember(json, "Port")); + + switch(x.Type) + { + case ProxyType::Shadowsocks: + if(plugin == "obfs-local") + plugin = "simple-obfs"; + writer.StartObject(); + writer.Key("server"); + writer.String(hostname.data()); + writer.Key("port"); + writer.Int(port); + writer.Key("encryption"); + writer.String(method.data()); + writer.Key("password"); + writer.String(password.data()); + writer.Key("plugin"); + writer.String(plugin.data()); + writer.Key("plugin_options"); + writer.String(pluginopts.data()); + writer.Key("remarks"); + writer.String(x.Remark.data()); + writer.Key("id"); + writer.Int(index); + writer.EndObject(); + break; + case ProxyType::ShadowsocksR: + if(std::count(ss_ciphers.begin(), ss_ciphers.end(), method) > 0 && protocol == "origin" && obfs == "plain") + { + writer.StartObject(); + writer.Key("server"); + writer.String(hostname.data()); + writer.Key("port"); + writer.Int(port); + writer.Key("encryption"); + writer.String(method.data()); + writer.Key("password"); + writer.String(password.data()); + writer.Key("remarks"); + writer.String(x.Remark.data()); + writer.Key("id"); + writer.Int(index); + writer.EndObject(); + break; + } + else + continue; + default: + continue; + } + index++; + } + writer.EndArray(); + writer.EndObject(); + return "ssd://" + base64Encode(sb.GetString()); +} + +std::string proxyToMellow(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext) +{ + INIReader ini; + ini.store_any_line = true; + if(ini.Parse(base_conf) != 0) + { + writeLog(0, "Mellow base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); + return std::string(); + } + + proxyToMellow(nodes, ini, ruleset_content_array, extra_proxy_group, ext); + + return ini.ToString(); +} + +void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext) +{ + std::string proxy; + std::string type, remark, hostname, port, username, password, method; + std::string plugin, pluginopts; + std::string id, aid, transproto, faketype, host, path, quicsecure, quicsecret, tlssecure; + std::string url; + tribool tfo, scv; + std::vector nodelist; + string_array vArray, remarks_list, filtered_nodelist; + + ini.SetCurrentSection("Endpoint"); + + for(Proxy &x : nodes) + { + if(ext.append_proxy_type) + { + std::string type = getProxyTypeName(x.Type); + x.Remark = "[" + type + "] " + x.Remark; + } + + processRemark(x.Remark, remark, remarks_list); + + std::string &hostname = x.Hostname; + port = std::to_string(x.Port); + + tfo = ext.tfo; + scv = ext.skip_cert_verify; + tfo.define(x.TCPFastOpen); + scv.define(x.AllowInsecure); + + switch(x.Type) + { + case ProxyType::Shadowsocks: + if(x.Plugin.size()) + continue; + proxy = remark + ", ss, ss://" + urlSafeBase64Encode(method + ":" + password) + "@" + hostname + ":" + port; + break; + case ProxyType::VMess: + proxy = remark + ", vmess1, vmess1://" + id + "@" + hostname + ":" + port; + if(path.size()) + proxy += path; + proxy += "?network=" + transproto; + switch(hash_(transproto)) + { + case "ws"_hash: + proxy += "&ws.host=" + urlEncode(host); + break; + case "http"_hash: + if(!host.empty()) + proxy += "&http.host=" + urlEncode(host); + break; + case "quic"_hash: + if(!quicsecure.empty()) + proxy += "&quic.security=" + quicsecure + "&quic.key=" + quicsecret; + break; + case "kcp"_hash: + break; + case "tcp"_hash: + break; + } + proxy += "&tls=" + tlssecure; + if(tlssecure == "true") + { + if(!host.empty()) + proxy += "&tls.servername=" + urlEncode(host); + } + if(!scv.is_undef()) + proxy += "&tls.allowinsecure=" + scv.get_str(); + if(!tfo.is_undef()) + proxy += "&sockopt.tcpfastopen=" + tfo.get_str(); + break; + case ProxyType::SOCKS5: + proxy = remark + ", builtin, socks, address=" + hostname + ", port=" + port + ", user=" + username + ", pass=" + password; + break; + case ProxyType::HTTP: + proxy = remark + ", builtin, http, address=" + hostname + ", port=" + port + ", user=" + username + ", pass=" + password; + break; + default: + continue; + } + + ini.Set("{NONAME}", proxy); + remarks_list.emplace_back(std::move(remark)); + nodelist.emplace_back(x); + } + + ini.SetCurrentSection("EndpointGroup"); + + for(const std::string &x : extra_proxy_group) + { + eraseElements(filtered_nodelist); + unsigned int rules_upper_bound = 0; + url.clear(); + proxy.clear(); + + vArray = split(x, "`"); + if(vArray.size() < 3) + continue; + + rules_upper_bound = vArray.size(); + switch(hash_(vArray[1])) + { + case "select"_hash: + break; + case "url-test"_hash: + case "fallback"_hash: + case "load-balance"_hash: + if(vArray.size() < 5) + continue; + rules_upper_bound -= 2; + url = vArray[vArray.size() - 2]; + break; + default: + continue; + } + + for(unsigned int i = 2; i < rules_upper_bound; i++) + groupGenerate(vArray[i], nodelist, filtered_nodelist, false, ext); + + if(!filtered_nodelist.size()) + { + if(!remarks_list.size()) + filtered_nodelist.emplace_back("DIRECT"); + else + filtered_nodelist = remarks_list; + } + + //don't process these for now + /* + proxy = vArray[1]; + for(std::string &x : filtered_nodelist) + proxy += "," + x; + if(vArray[1] == "url-test" || vArray[1] == "fallback" || vArray[1] == "load-balance") + proxy += ",url=" + url; + */ + + proxy = vArray[0] + ", "; + /* + for(std::string &y : filtered_nodelist) + proxy += y + ":"; + proxy = proxy.substr(0, proxy.size() - 1); + */ + proxy += std::accumulate(std::next(filtered_nodelist.begin()), filtered_nodelist.end(), filtered_nodelist[0], [](std::string a, std::string b) + { + return std::move(a) + ":" + std::move(b); + }); + proxy += ", latency, interval=300, timeout=6"; //use hard-coded values for now + + ini.Set("{NONAME}", proxy); //insert order + } + + if(ext.enable_rule_generator) + rulesetToSurge(ini, ruleset_content_array, 0, ext.overwrite_original_rules, std::string()); +} + +std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext) +{ + rapidjson::Document json; + INIReader ini; + std::string proxy; + std::string output_nodelist; + tribool scv; + std::vector nodelist; + //group pref + std::string url; + int interval = 0; + + string_array vArray, remarks_list, filtered_nodelist; + + ini.store_any_line = true; + if(ini.Parse(base_conf) != INIREADER_EXCEPTION_NONE && !ext.nodelist) + { + writeLog(0, "Loon base loader failed with error: " + ini.GetLastError(), LOG_LEVEL_ERROR); + return std::string(); + } + + + ini.SetCurrentSection("Proxy"); + ini.EraseSection(); + + for(Proxy &x : nodes) + { + if(ext.append_proxy_type) + { + std::string type = getProxyTypeName(x.Type); + x.Remark = "[" + type + "] " + x.Remark; + } + std::string remark = x.Remark; + processRemark(x.Remark, remark, remarks_list); + + std::string &hostname = x.Hostname, &username = x.Username, &password = x.Password, &method = x.EncryptMethod, &plugin = x.Plugin, &pluginopts = x.PluginOption, &id = x.UserId, &transproto = x.TransferProtocol, &host = x.Host, &path = x.Path, &protocol = x.Protocol, &protoparam = x.ProtocolParam, &obfs = x.OBFS, &obfsparam = x.OBFSParam; + std::string port = std::to_string(x.Port), aid = std::to_string(x.AlterId); + bool &tlssecure = x.TLSSecure; + + tribool scv = ext.skip_cert_verify; + scv.define(x.AllowInsecure); + + proxy.clear(); + + switch(x.Type) + { + case ProxyType::Shadowsocks: + proxy = "Shadowsocks," + hostname + "," + port + "," + method + ",\"" + password + "\""; + if(plugin == "simple-obfs" || plugin == "obfs-local") + { + if(pluginopts.size()) + proxy += "," + replaceAllDistinct(replaceAllDistinct(pluginopts, ";obfs-host=", ","), "obfs=", ""); + } + else if(plugin.size()) + continue; + break; + case ProxyType::VMess: + if(method == "auto") + method = "chacha20-ietf-poly1305"; + + proxy = "vmess," + hostname + "," + port + "," + method + ",\"" + id + "\",over-tls:" + (tlssecure ? "true" : "false"); + if(tlssecure) + proxy += ",tls-name:" + host; + switch(hash_(transproto)) + { + case "tcp"_hash: + proxy += ",transport:tcp"; + break; + case "ws"_hash: + proxy += ",transport:ws,path:" + path + ",host:" + host; + break; + default: + continue; + } + if(!scv.is_undef()) + proxy += ",skip-cert-verify:" + std::string(scv.get() ? "1" : "0"); + break; + case ProxyType::ShadowsocksR: + proxy = "ShadowsocksR," + hostname + "," + port + "," + method + ",\"" + password + "\"," + protocol + ",{" + protoparam + "}," + obfs + ",{" + obfsparam + "}"; + break; + /* + case ProxyType::SOCKS5: + proxy = "socks5, " + hostname + ", " + port + ", " + username + ", " + password; + if(ext.skip_cert_verify) + proxy += ", skip-cert-verify:1"; + break; + */ + case ProxyType::HTTP: + proxy = "http," + hostname + "," + port + "," + username + "," + password; + break; + case ProxyType::Trojan: + proxy = "trojan," + hostname + "," + port + "," + password; + if(host.size()) + proxy += ",tls-name:" + host; + if(!scv.is_undef()) + proxy += ",skip-cert-verify:" + std::string(scv.get() ? "1" : "0"); + break; + default: + continue; + } + + /* + if(ext.tfo) + proxy += ", tfo=true"; + if(ext.udp) + proxy += ", udp-relay=true"; + */ + + if(ext.nodelist) + output_nodelist += remark + " = " + proxy + "\n"; + else + { + ini.Set("{NONAME}", remark + " = " + proxy); + nodelist.emplace_back(x); + remarks_list.emplace_back(std::move(remark)); + } + } + + if(ext.nodelist) + return output_nodelist; + + ini.SetCurrentSection("Proxy Group"); + ini.EraseSection(); + for(const std::string &x : extra_proxy_group) + { + eraseElements(filtered_nodelist); + unsigned int rules_upper_bound = 0; + url.clear(); + proxy.clear(); + + vArray = split(x, "`"); + if(vArray.size() < 3) + continue; + + rules_upper_bound = vArray.size(); + switch(hash_(vArray[1])) + { + case "select"_hash: + break; + case "url-test"_hash: + case "fallback"_hash: + if(vArray.size() < 5) + continue; + rules_upper_bound -= 2; + url = vArray[rules_upper_bound]; + parseGroupTimes(vArray[rules_upper_bound + 1], &interval, NULL, NULL); + break; + case "ssid"_hash: + if(vArray.size() < 4) + continue; + proxy = vArray[1] + ",default=" + vArray[2] + ","; + proxy += std::accumulate(vArray.begin() + 4, vArray.end(), vArray[3], [](std::string a, std::string b) + { + return std::move(a) + "," + std::move(b); + }); + ini.Set("{NONAME}", vArray[0] + " = " + proxy); //insert order + continue; + default: + continue; + } + + for(unsigned int i = 2; i < rules_upper_bound; i++) + groupGenerate(vArray[i], nodelist, filtered_nodelist, true, ext); + + if(!filtered_nodelist.size()) + filtered_nodelist.emplace_back("DIRECT"); + + proxy = vArray[1] + ","; + /* + for(std::string &y : filtered_nodelist) + proxy += "," + y; + */ + proxy += std::accumulate(std::next(filtered_nodelist.cbegin()), filtered_nodelist.cend(), filtered_nodelist[0], [](std::string a, std::string b) + { + return std::move(a) + "," + std::move(b); + }); + if(vArray[1] == "url-test" || vArray[1] == "fallback") + proxy += ",url=" + url + ",interval=" + std::to_string(interval); + + ini.Set("{NONAME}", vArray[0] + " = " + proxy); //insert order + } + + if(ext.enable_rule_generator) + rulesetToSurge(ini, ruleset_content_array, -4, ext.overwrite_original_rules, ext.managed_config_prefix); + + return ini.ToString(); +} diff --git a/src/generator/config/subexport.h b/src/generator/config/subexport.h new file mode 100644 index 0000000..0d04f84 --- /dev/null +++ b/src/generator/config/subexport.h @@ -0,0 +1,69 @@ +#ifndef SUBEXPORT_H_INCLUDED +#define SUBEXPORT_H_INCLUDED + +#include + +#include + +#include "../../parser/config/proxy.h" +#include "../../utils/ini_reader/ini_reader.h" +#include "../../utils/string.h" +#include "../../utils/yamlcpp_extra.h" +#include "ruleconvert.h" + +struct extra_settings +{ + bool enable_rule_generator = true; + bool overwrite_original_rules = true; + string_array rename_array; + string_array emoji_array; + bool add_emoji = false; + bool remove_emoji = false; + bool append_proxy_type = false; + bool nodelist = false; + bool sort_flag = false; + bool filter_deprecated = false; + bool clash_new_field_name = false; + bool clash_script = false; + std::string surge_ssr_path; + std::string managed_config_prefix; + std::string quanx_dev_id; + tribool udp = tribool(); + tribool tfo = tribool(); + tribool skip_cert_verify = tribool(); + tribool tls13 = tribool(); + bool clash_classical_ruleset = false; + std::string sort_script = ""; + std::string clash_proxies_style = "flow"; + + qjs::Runtime *js_runtime = nullptr; + qjs::Context *js_context = nullptr; + extra_settings() {}; + extra_settings(const extra_settings&) = delete; + extra_settings(extra_settings&&) = delete; + ~extra_settings() + { + delete js_context; + delete js_runtime; + } +}; + +void rulesetToClash(YAML::Node &base_rule, std::vector &ruleset_content_array, bool overwrite_original_rules, bool new_field_name); +void rulesetToSurge(INIReader &base_rule, std::vector &ruleset_content_array, int surge_ver, bool overwrite_original_rules, std::string remote_path_prefix); +void preprocessNodes(std::vector &nodes, extra_settings &ext); + +std::string proxyToClash(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, bool clashR, extra_settings &ext); +void proxyToClash(std::vector &nodes, YAML::Node &yamlnode, const string_array &extra_proxy_group, bool clashR, extra_settings &ext); +std::string proxyToSurge(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, int surge_ver, extra_settings &ext); +std::string proxyToMellow(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext); +void proxyToMellow(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext); +std::string proxyToLoon(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext); +std::string proxyToSSSub(std::string &base_conf, std::vector &nodes, extra_settings &ext); +std::string proxyToSingle(std::vector &nodes, int types, extra_settings &ext); +std::string proxyToQuanX(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext); +void proxyToQuanX(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext); +std::string proxyToQuan(std::vector &nodes, const std::string &base_conf, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext); +void proxyToQuan(std::vector &nodes, INIReader &ini, std::vector &ruleset_content_array, const string_array &extra_proxy_group, extra_settings &ext); +std::string proxyToSSD(std::vector &nodes, std::string &group, std::string &userinfo, extra_settings &ext); + +#endif // SUBEXPORT_H_INCLUDED diff --git a/src/template_jinja2.cpp b/src/generator/template/template_jinja2.cpp similarity index 92% rename from src/template_jinja2.cpp rename to src/generator/template/template_jinja2.cpp index a5000fd..4a4e959 100644 --- a/src/template_jinja2.cpp +++ b/src/generator/template/template_jinja2.cpp @@ -5,14 +5,13 @@ #include #include -#include "interfaces.h" +#include "../../handler/interfaces.h" +#include "../../utils/regexp.h" #include "templates.h" -#include "webget.h" -#include "misc.h" static inline void parse_json_pointer(nlohmann::json &json, const std::string &path, const std::string &value) { - std::string pointer = "/" + replace_all_distinct(path, ".", "/"); + std::string pointer = "/" + replaceAllDistinct(path, ".", "/"); json[nlohmann::json::json_pointer(pointer)] = value; } diff --git a/src/templates.cpp b/src/generator/template/templates.cpp similarity index 97% rename from src/templates.cpp rename to src/generator/template/templates.cpp index 0deae85..84d64c9 100644 --- a/src/templates.cpp +++ b/src/generator/template/templates.cpp @@ -4,12 +4,13 @@ #include #include -#include "yamlcpp_extra.h" -#include "interfaces.h" +#include "../../utils/yamlcpp_extra.h" +#include "../../utils/urlencode.h" +#include "../../utils/regexp.h" +#include "../../handler/interfaces.h" +#include "../../utils/logger.h" +#include "../../utils/network.h" #include "templates.h" -#include "logger.h" -#include "misc.h" -#include "webget.h" extern std::string gManagedConfigPrefix; @@ -68,19 +69,19 @@ int render_template(const std::string &content, const template_args &vars, std:: m_callbacks.add_callback("UrlEncode", 1, [](inja::Arguments &args) { std::string data = args.at(0)->get(); - return UrlEncode(data); + return urlEncode(data); }); m_callbacks.add_callback("UrlDecode", 1, [](inja::Arguments &args) { std::string data = args.at(0)->get(); - return UrlDecode(data); + return urlDecode(data); }); m_callbacks.add_callback("trim_of", 2, [](inja::Arguments &args) { std::string data = args.at(0)->get(), target = args.at(1)->get(); if(target.empty()) return data; - return trim_of(data, target[0]); + return trimOf(data, target[0]); }); m_callbacks.add_callback("trim", 1, [](inja::Arguments &args) { @@ -421,7 +422,7 @@ int renderClashScript(YAML::Node &base_rule, std::vector &rules if(url[0] == '*') base_rule["rule-providers"][yaml_key]["url"] = url.substr(1); else - base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=3&url=" + urlsafe_base64_encode(url); + base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=3&url=" + urlSafeBase64Encode(url); base_rule["rule-providers"][yaml_key]["path"] = "./providers/rule-provider_" + yaml_key + ".yaml"; if(interval) base_rule["rule-providers"][yaml_key]["interval"] = interval; @@ -436,7 +437,7 @@ int renderClashScript(YAML::Node &base_rule, std::vector &rules if(url[0] == '*') base_rule["rule-providers"][yaml_key]["url"] = url.substr(1); else - base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=4&url=" + urlsafe_base64_encode(url); + base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=4&url=" + urlSafeBase64Encode(url); base_rule["rule-providers"][yaml_key]["path"] = "./providers/rule-provider_" + yaml_key + ".yaml"; if(interval) base_rule["rule-providers"][yaml_key]["interval"] = interval; @@ -449,7 +450,7 @@ int renderClashScript(YAML::Node &base_rule, std::vector &rules if(url[0] == '*') base_rule["rule-providers"][yaml_key]["url"] = url.substr(1); else - base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=6&url=" + urlsafe_base64_encode(url); + base_rule["rule-providers"][yaml_key]["url"] = remote_path_prefix + "/getruleset?type=6&url=" + urlSafeBase64Encode(url); base_rule["rule-providers"][yaml_key]["path"] = "./providers/rule-provider_" + yaml_key + ".yaml"; if(interval) base_rule["rule-providers"][yaml_key]["interval"] = interval; diff --git a/src/templates.h b/src/generator/template/templates.h similarity index 84% rename from src/templates.h rename to src/generator/template/templates.h index c1d3e9c..90f6069 100644 --- a/src/templates.h +++ b/src/generator/template/templates.h @@ -4,15 +4,15 @@ #include #include -#include "subexport.h" - -typedef std::map string_map; +#include "../../generator/config/subexport.h" +#include "../../utils/string.h" struct template_args { string_map global_vars; string_map request_params; string_map local_vars; + string_map node_list; }; int render_template(const std::string &content, const template_args &vars, std::string &output, const std::string &include_scope = "template"); diff --git a/src/interfaces.cpp b/src/handler/interfaces.cpp similarity index 90% rename from src/interfaces.cpp rename to src/handler/interfaces.cpp index 3112823..fc96829 100644 --- a/src/interfaces.cpp +++ b/src/handler/interfaces.cpp @@ -6,21 +6,30 @@ #include #include -#include "yamlcpp_extra.h" -#include "misc.h" -#include "nodeinfo.h" -#include "speedtestutil.h" -#include "nodemanip.h" -#include "ini_reader.h" -#include "webget.h" -#include "webserver.h" -#include "subexport.h" +#include "../generator/config/ruleconvert.h" +#include "../generator/config/nodemanip.h" +#include "../generator/config/ruleconvert.h" +#include "../generator/config/subexport.h" +#include "../generator/template/templates.h" +#include "../script/cron.h" +#include "../script/script_quickjs.h" +#include "../server/webserver.h" +#include "../utils/base64/base64.h" +#include "../utils/file_extra.h" +#include "../utils/ini_reader/ini_reader.h" +#include "../utils/logger.h" +#include "../utils/network.h" +#include "../utils/regexp.h" +#include "../utils/stl_extra.h" +#include "../utils/string.h" +#include "../utils/string_hash.h" +#include "../utils/system.h" +#include "../utils/system.h" +#include "../utils/urlencode.h" +#include "../utils/yamlcpp_extra.h" #include "multithread.h" -#include "logger.h" -#include "string_hash.h" -#include "templates.h" #include "upload.h" -#include "script_duktape.h" +#include "webget.h" //common settings std::string gPrefPath = "pref.ini", gDefaultExtConfig; @@ -73,6 +82,11 @@ int gCacheSubscription = 60, gCacheConfig = 300, gCacheRuleset = 21600; //limits size_t gMaxAllowedRulesets = 64, gMaxAllowedRules = 32768; +bool gScriptCleanContext = false; + +//cron system +bool gEnableCron = false; +string_array gCronTasks; string_array gRegexBlacklist = {"(.*)*"}; @@ -182,81 +196,9 @@ void matchUserAgent(const std::string &user_agent, std::string &target, tribool return; } -std::string convertRuleset(const std::string &content, int type) -{ - /// Target: Surge type,pattern[,flag] - /// Source: QuanX type,pattern[,group] - /// Clash payload:\n - 'ipcidr/domain/classic(Surge-like)' - - std::string output, strLine; - - if(type == RULESET_SURGE) - return content; - - if(startsWith(content, "payload:")) /// Clash - { - output = regReplace(regReplace(content, "payload:\\r?\\n", "", true), "\\s?^\\s*-\\s+('?)(.*)\\1$", "\n$2", true); - if(type == RULESET_CLASH_CLASSICAL) /// classical type - return output; - std::stringstream ss; - ss << output; - char delimiter = getLineBreak(output); - output.clear(); - string_size pos, lineSize; - while(getline(ss, strLine, delimiter)) - { - strLine = trim(strLine); - lineSize = strLine.size(); - if(lineSize && strLine[lineSize - 1] == '\r') //remove line break - strLine.erase(--lineSize); - - if(!strLine.empty() && (strLine[0] != ';' && strLine[0] != '#' && !(lineSize >= 2 && strLine[0] == '/' && strLine[1] == '/'))) - { - pos = strLine.find("/"); - if(pos != strLine.npos) /// ipcidr - { - if(isIPv4(strLine.substr(0, pos))) - output += "IP-CIDR,"; - else - output += "IP-CIDR6,"; - } - else - { - if(strLine[0] == '.' || (lineSize >= 2 && strLine[0] == '+' && strLine[1] == '.')) /// suffix - { - bool keyword_flag = false; - while(endsWith(strLine, ".*")) - { - keyword_flag = true; - strLine.erase(strLine.size() - 2); - } - output += "DOMAIN-"; - if(keyword_flag) - output += "KEYWORD,"; - else - output += "SUFFIX,"; - strLine.erase(0, 2 - (strLine[0] == '.')); - } - else - output += "DOMAIN,"; - } - } - output += strLine; - output += '\n'; - } - return output; - } - else /// QuanX - { - output = regReplace(regReplace(content, "^(?i:host)", "DOMAIN", true), "^(?i:ip6-cidr)", "IP-CIDR6", true); //translate type - output = regReplace(output, "^((?i:DOMAIN(?:-(?:SUFFIX|KEYWORD))?|IP-CIDR6?|USER-AGENT),)\\s*?(\\S*?)(?:,(?!no-resolve).*?)(,no-resolve)?$", "\\U$1\\E$2${3:-}", true); //remove group - return output; - } -} - std::string getConvertedRuleset(RESPONSE_CALLBACK_ARGS) { - std::string url = UrlDecode(getUrlArg(request.argument, "url")), type = getUrlArg(request.argument, "type"); + std::string url = urlDecode(getUrlArg(request.argument, "url")), type = getUrlArg(request.argument, "type"); return convertRuleset(fetchFile(url, parseProxy(gProxyRuleset), gCacheRuleset), to_int(type)); } @@ -265,7 +207,7 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS) std::string &argument = request.argument; int *status_code = &response.status_code; /// type: 1 for Surge, 2 for Quantumult X, 3 for Clash domain rule-provider, 4 for Clash ipcidr rule-provider, 5 for Surge DOMAIN-SET, 6 for Clash classical ruleset - std::string url = urlsafe_base64_decode(getUrlArg(argument, "url")), type = getUrlArg(argument, "type"), group = urlsafe_base64_decode(getUrlArg(argument, "group")); + std::string url = urlSafeBase64Decode(getUrlArg(argument, "url")), type = getUrlArg(argument, "type"), group = urlSafeBase64Decode(getUrlArg(argument, "group")); std::string output_content, dummy; int type_int = to_int(type, 0); @@ -328,6 +270,11 @@ std::string getRuleset(RESPONSE_CALLBACK_ARGS) while(getline(ss, strLine, delimiter)) { + if(strFind(strLine, "//")) + { + strLine.erase(strLine.find("//")); + strLine = trimWhitespace(strLine); + } switch(type_int) { case 2: @@ -541,8 +488,8 @@ void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) std::string url = "http://www.gstatic.com/generate_204", interval = "300", tolerance, timeout; object["name"] >>= name; object["type"] >>= type; - tempArray.emplace_back(std::move(name)); - tempArray.emplace_back(type); + tempArray.emplace_back("name=" + name); + tempArray.emplace_back("type=" + type); object["url"] >>= url; object["interval"] >>= interval; object["tolerance"] >>= tolerance; @@ -562,13 +509,13 @@ void readGroup(YAML::Node node, string_array &dest, bool scope_limit = true) default: if(tempArray.size() < 3) continue; - tempArray.emplace_back(std::move(url)); - tempArray.emplace_back(interval + "," + timeout + "," + tolerance); + tempArray.emplace_back("url=" + url); + tempArray.emplace_back("interval=" + interval + ",timeout=" + timeout + ",tolerance=" + tolerance); } strLine = std::accumulate(std::next(tempArray.begin()), tempArray.end(), tempArray[0], [](std::string a, std::string b) -> std::string { - return std::move(a) + "`" + std::move(b); + return std::move(a) + "," + std::move(b); }); dest.emplace_back(std::move(strLine)); } @@ -693,8 +640,6 @@ void readYAMLConf(YAML::Node &node) if(section["include_remarks"].IsSequence()) section["include_remarks"] >> gIncludeRemarks; gFilterScript = safe_as(section["enable_filter"]) ? safe_as(section["filter_script"]) : ""; - if(startsWith(gFilterScript, "path:")) - gFilterScript = fileGet(gFilterScript.substr(5), false); section["base_path"] >> gBasePath; section["clash_rule_base"] >> gClashBase; section["surge_rule_base"] >> gSurgeBase; @@ -836,6 +781,30 @@ void readYAMLConf(YAML::Node &node) } } + if(node["tasks"].IsSequence()) + { + gCronTasks.clear(); + for(size_t i = 0; i < node["tasks"].size(); i++) + { + std::string name, exp, path, timeout; + node["tasks"][i]["import"] >> name; + if(name.size()) + { + gCronTasks.emplace_back("!!import:" + name); + continue; + } + node["tasks"][i]["name"] >> name; + node["tasks"][i]["cronexp"] >> exp; + node["tasks"][i]["path"] >> path; + node["tasks"][i]["timeout"] >> timeout; + strLine = name + "`" + exp + "`" + path + "`" + timeout; + gCronTasks.push_back(std::move(strLine)); + } + importItems(gCronTasks, false); + gEnableCron = !gCronTasks.empty(); + refresh_schedule(); + } + if(node["server"].IsDefined()) { node["server"]["listen"] >> gListenAddress; @@ -891,6 +860,7 @@ void readYAMLConf(YAML::Node &node) else gCacheSubscription = gCacheConfig = gCacheRuleset = 0; //disable cache } + node["advanced"]["script_clean_context"] >> gScriptCleanContext; node["advanced"]["async_fetch_ruleset"] >> gAsyncFetchRuleset; node["advanced"]["skip_failed_links"] >> gSkipFailedLinks; } @@ -1090,6 +1060,16 @@ void readConf() append_redirect(x.first, x.second); } + if(ini.SectionExist("tasks")) + { + gCronTasks.clear(); + ini.EnterSection("tasks"); + ini.GetAll("task", gCronTasks); + importItems(gCronTasks, false); + gEnableCron = !gCronTasks.empty(); + refresh_schedule(); + } + ini.EnterSection("server"); ini.GetIfExist("listen", gListenAddress); ini.GetIntIfExist("port", gListenPort); @@ -1145,6 +1125,7 @@ void readConf() gServeCacheOnFetchFail = false; } } + ini.GetBoolIfExist("script_clean_context", gScriptCleanContext); ini.GetBoolIfExist("async_fetch_ruleset", gAsyncFetchRuleset); ini.GetBoolIfExist("skip_failed_links", gSkipFailedLinks); @@ -1357,12 +1338,12 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) readConf(); /// string values - std::string argUrl = UrlDecode(getUrlArg(argument, "url")); - std::string argGroupName = UrlDecode(getUrlArg(argument, "group")), argUploadPath = getUrlArg(argument, "upload_path"); - std::string argIncludeRemark = UrlDecode(getUrlArg(argument, "include")), argExcludeRemark = UrlDecode(getUrlArg(argument, "exclude")); - std::string argCustomGroups = urlsafe_base64_decode(getUrlArg(argument, "groups")), argCustomRulesets = urlsafe_base64_decode(getUrlArg(argument, "ruleset")), argExternalConfig = UrlDecode(getUrlArg(argument, "config")); + std::string argUrl = urlDecode(getUrlArg(argument, "url")); + std::string argGroupName = urlDecode(getUrlArg(argument, "group")), argUploadPath = getUrlArg(argument, "upload_path"); + std::string argIncludeRemark = urlDecode(getUrlArg(argument, "include")), argExcludeRemark = urlDecode(getUrlArg(argument, "exclude")); + std::string argCustomGroups = urlSafeBase64Decode(getUrlArg(argument, "groups")), argCustomRulesets = urlSafeBase64Decode(getUrlArg(argument, "ruleset")), argExternalConfig = urlDecode(getUrlArg(argument, "config")); std::string argDeviceID = getUrlArg(argument, "dev_id"), argFilename = getUrlArg(argument, "filename"), argUpdateInterval = getUrlArg(argument, "interval"), argUpdateStrict = getUrlArg(argument, "strict"); - std::string argRenames = UrlDecode(getUrlArg(argument, "rename")), argFilterScript = UrlDecode(getUrlArg(argument, "filter_script")); + std::string argRenames = urlDecode(getUrlArg(argument, "rename")), argFilterScript = urlDecode(getUrlArg(argument, "filter_script")); /// switches with default value tribool argUpload = getUrlArg(argument, "upload"), argEmoji = getUrlArg(argument, "emoji"), argAddEmoji = getUrlArg(argument, "add_emoji"), argRemoveEmoji = getUrlArg(argument, "remove_emoji"); @@ -1429,10 +1410,10 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) ext.clash_proxies_style = gClashProxiesStyle; /// read preference from argument, assign global var if not in argument - ext.tfo.parse(argTFO).parse(gTFO); - ext.udp.parse(argUDP).parse(gUDP); - ext.skip_cert_verify.parse(argSkipCertVerify).parse(gSkipCertVerify); - ext.tls13.parse(argTLS13).parse(gTLS13); + ext.tfo.define(argTFO).define(gTFO); + ext.udp.define(argUDP).define(gUDP); + ext.skip_cert_verify.define(argSkipCertVerify).define(gSkipCertVerify); + ext.tls13.define(argTLS13).define(gTLS13); ext.sort_flag = argSort.get(gEnableSort); argUseSortScript.define(gSortScript.size() != 0); @@ -1455,7 +1436,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) if(!argExpandRulesets) ext.managed_config_prefix = gManagedConfigPrefix; - //load external configuration + /// load external configuration if(argExternalConfig.empty()) argExternalConfig = gDefaultExtConfig; if(argExternalConfig.size()) @@ -1498,17 +1479,16 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) argAddEmoji.define(extconf.add_emoji); argRemoveEmoji.define(extconf.remove_old_emoji); } - } else { if(!lSimpleSubscription) { - //loading custom groups + /// loading custom groups if(argCustomGroups.size() && !ext.nodelist) lCustomProxyGroups = split(argCustomGroups, "@"); - //loading custom rulesets + /// loading custom rulesets if(argCustomRulesets.size() && !ext.nodelist) lCustomRulesets = split(argCustomRulesets, "@"); } @@ -1539,19 +1519,44 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) else if(ext.rename_array.empty()) ext.rename_array = safe_get_renames(); - //check custom include/exclude settings + /// check custom include/exclude settings if(argIncludeRemark.size() && regValid(argIncludeRemark)) lIncludeRemarks = string_array{argIncludeRemark}; if(argExcludeRemark.size() && regValid(argExcludeRemark)) lExcludeRemarks = string_array{argExcludeRemark}; + /// initialize script runtime + if(authorized) + { + ext.js_runtime = new qjs::Runtime(); + script_runtime_init(*ext.js_runtime); + if(!gScriptCleanContext) + { + ext.js_context = new qjs::Context(*ext.js_runtime); + script_context_init(*ext.js_context); + } + } + //start parsing urls string_array stream_temp = safe_get_streams(), time_temp = safe_get_times(); //loading urls string_array urls; - std::vector nodes, insert_nodes; + std::vector nodes, insert_nodes; int groupID = 0; + + parse_settings parse_set; + parse_set.proxy = &proxy; + parse_set.exclude_remarks = &lExcludeRemarks; + parse_set.include_remarks = &lIncludeRemarks; + parse_set.stream_rules = &stream_temp; + parse_set.time_rules = &time_temp; + parse_set.sub_info = &subInfo; + parse_set.authorized = authorized; + parse_set.request_header = &request.headers; + parse_set.js_runtime = ext.js_runtime; + parse_set.js_context = ext.js_context; + if(gInsertUrls.size() && argEnableInsert) { groupID = -1; @@ -1561,7 +1566,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) { x = regTrim(x); writeLog(0, "Fetching node data from url '" + x + "'.", LOG_LEVEL_INFO); - if(addNodes(x, insert_nodes, groupID, proxy, lExcludeRemarks, lIncludeRemarks, stream_temp, time_temp, subInfo, authorized, request.headers) == -1) + if(addNodes(x, insert_nodes, groupID, parse_set) == -1) { if(gSkipFailedLinks) writeLog(0, "The following link doesn't contain any valid node info: " + x, LOG_LEVEL_WARNING); @@ -1582,7 +1587,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) x = regTrim(x); //std::cerr<<"Fetching node data from url '"<) ctx.eval("filter"); + nodes.erase(std::remove_if(nodes.begin(), nodes.end(), filter), nodes.end()); + } + catch(qjs::exception) + { + script_print_stack(ctx); + } + }, gScriptCleanContext); } //check custom group name if(argGroupName.size()) - for(nodeInfo &x : nodes) - x.group = argGroupName; + for(Proxy &x : nodes) + x.Group = argGroupName; if(subInfo.size() && argAppendUserinfo.get(gAppendUserinfo)) response.headers.emplace("Subscription-UserInfo", subInfo); @@ -1652,9 +1671,23 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) //do pre-process now preprocessNodes(nodes, ext); + /* + //insert node info to template + int index = 0; + std::string template_node_prefix; + for(Proxy &x : nodes) + { + template_node_prefix = std::to_string(index) + "."; + tpl_args.node_list[template_node_prefix + "remarks"] = x.remarks; + tpl_args.node_list[template_node_prefix + "group"] = x.Group; + tpl_args.node_list[template_node_prefix + "groupid"] = std::to_string(x.GroupId); + index++; + } + */ + string_array dummy_group; std::vector dummy_ruleset; - std::string managed_url = base64_decode(UrlDecode(getUrlArg(argument, "profile_data"))); + std::string managed_url = base64Decode(urlDecode(getUrlArg(argument, "profile_data"))); if(managed_url.empty()) managed_url = gManagedConfigPrefix + "/sub?" + argument; @@ -1669,7 +1702,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) if(ext.nodelist) { YAML::Node yamlnode; - netchToClash(nodes, yamlnode, dummy_group, argTarget == "clashr", ext); + proxyToClash(nodes, yamlnode, dummy_group, argTarget == "clashr", ext); output_content = YAML::Dump(yamlnode); } else @@ -1679,7 +1712,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) *status_code = 400; return base_content; } - output_content = netchToClash(nodes, base_content, lRulesetContent, lCustomProxyGroups, argTarget == "clashr", ext); + output_content = proxyToClash(nodes, base_content, lRulesetContent, lCustomProxyGroups, argTarget == "clashr", ext); } if(argUpload) @@ -1691,7 +1724,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) if(ext.nodelist) { - output_content = netchToSurge(nodes, base_content, dummy_ruleset, dummy_group, intSurgeVer, ext); + output_content = proxyToSurge(nodes, base_content, dummy_ruleset, dummy_group, intSurgeVer, ext); if(argUpload) uploadGist("surge" + argSurgeVer + "list", argUploadPath, output_content, true); @@ -1703,7 +1736,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) *status_code = 400; return base_content; } - output_content = netchToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, intSurgeVer, ext); + output_content = proxyToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, intSurgeVer, ext); if(argUpload) uploadGist("surge" + argSurgeVer, argUploadPath, output_content, true); @@ -1721,7 +1754,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) *status_code = 400; return base_content; } - output_content = netchToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, -3, ext); + output_content = proxyToSurge(nodes, base_content, lRulesetContent, lCustomProxyGroups, -3, ext); if(argUpload) uploadGist("surfboard", argUploadPath, output_content, true); @@ -1737,7 +1770,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) *status_code = 400; return base_content; } - output_content = netchToMellow(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); + output_content = proxyToMellow(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("mellow", argUploadPath, output_content, true); @@ -1750,37 +1783,37 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) *status_code = 400; return base_content; } - output_content = netchToSSSub(base_content, nodes, ext); + output_content = proxyToSSSub(base_content, nodes, ext); if(argUpload) uploadGist("sssub", argUploadPath, output_content, false); break; case "ss"_hash: writeLog(0, "Generate target: SS", LOG_LEVEL_INFO); - output_content = netchToSingle(nodes, 1, ext); + output_content = proxyToSingle(nodes, 1, ext); if(argUpload) uploadGist("ss", argUploadPath, output_content, false); break; case "ssr"_hash: writeLog(0, "Generate target: SSR", LOG_LEVEL_INFO); - output_content = netchToSingle(nodes, 2, ext); + output_content = proxyToSingle(nodes, 2, ext); if(argUpload) uploadGist("ssr", argUploadPath, output_content, false); break; case "v2ray"_hash: writeLog(0, "Generate target: v2rayN", LOG_LEVEL_INFO); - output_content = netchToSingle(nodes, 4, ext); + output_content = proxyToSingle(nodes, 4, ext); if(argUpload) uploadGist("v2ray", argUploadPath, output_content, false); break; case "trojan"_hash: writeLog(0, "Generate target: Trojan", LOG_LEVEL_INFO); - output_content = netchToSingle(nodes, 8, ext); + output_content = proxyToSingle(nodes, 8, ext); if(argUpload) uploadGist("trojan", argUploadPath, output_content, false); break; case "mixed"_hash: writeLog(0, "Generate target: Standard Subscription", LOG_LEVEL_INFO); - output_content = netchToSingle(nodes, 15, ext); + output_content = proxyToSingle(nodes, 15, ext); if(argUpload) uploadGist("sub", argUploadPath, output_content, false); break; @@ -1795,7 +1828,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) } } - output_content = netchToQuan(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); + output_content = proxyToQuan(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("quan", argUploadPath, output_content, false); @@ -1811,7 +1844,7 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) } } - output_content = netchToQuanX(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); + output_content = proxyToQuanX(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("quanx", argUploadPath, output_content, false); @@ -1827,14 +1860,14 @@ std::string subconverter(RESPONSE_CALLBACK_ARGS) } } - output_content = netchToLoon(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); + output_content = proxyToLoon(nodes, base_content, lRulesetContent, lCustomProxyGroups, ext); if(argUpload) uploadGist("loon", argUploadPath, output_content, false); break; case "ssd"_hash: writeLog(0, "Generate target: SSD", LOG_LEVEL_INFO); - output_content = netchToSSD(nodes, argGroupName, subInfo, ext); + output_content = proxyToSSD(nodes, argGroupName, subInfo, ext); if(argUpload) uploadGist("ssd", argUploadPath, output_content, false); break; @@ -1865,7 +1898,7 @@ std::string simpleToClashR(RESPONSE_CALLBACK_ARGS) *status_code = 400; return "Please insert your subscription link instead of clicking the default link."; } - request.argument = "target=clashr&url=" + UrlEncode(url); + request.argument = "target=clashr&url=" + urlEncode(url); return subconverter(request, response); } @@ -1876,7 +1909,7 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) INIReader ini; string_array dummy_str_array; - std::vector nodes; + std::vector nodes; std::string base_content, url = argument.size() <= 5 ? "" : argument.substr(5); const std::string proxygroup_name = gClashUseNewField ? "proxy-groups" : "Proxy Group", rule_name = gClashUseNewField ? "rules" : "Rule"; @@ -1968,11 +2001,17 @@ std::string surgeConfToClash(RESPONSE_CALLBACK_ARGS) eraseElements(dummy_str_array); std::string subInfo; + parse_settings parse_set; + parse_set.proxy = &proxy; + parse_set.exclude_remarks = parse_set.include_remarks = parse_set.stream_rules = parse_set.time_rules = &dummy_str_array; + parse_set.request_header = &request.headers; + parse_set.sub_info = &subInfo; + parse_set.authorized = !gAPIMode; for(std::string &x : links) { //std::cerr<<"Fetching node data from url '"<(upload, 0) + to_number(download, 0), tot = to_number(total, 0); @@ -2396,7 +2451,7 @@ int simpleGenerator() if(ini.ItemExist("profile")) { profile = ini.Get("profile"); - request.argument = "name=" + UrlEncode(profile) + "&token=" + gAccessToken + "&expand=true"; + request.argument = "name=" + urlEncode(profile) + "&token=" + gAccessToken + "&expand=true"; content = getProfile(request, response); } else @@ -2422,7 +2477,7 @@ int simpleGenerator() { if(y.first == "path") continue; - arguments += y.first + "=" + UrlEncode(y.second) + "&"; + arguments += y.first + "=" + urlEncode(y.second) + "&"; } arguments.erase(arguments.size() - 1); request.argument = arguments; @@ -2454,7 +2509,7 @@ std::string renderTemplate(RESPONSE_CALLBACK_ARGS) std::string &argument = request.argument; int *status_code = &response.status_code; - std::string path = UrlDecode(getUrlArg(argument, "path")); + std::string path = urlDecode(getUrlArg(argument, "path")); writeLog(0, "Trying to render template '" + path + "'...", LOG_LEVEL_INFO); if(!startsWith(path, gTemplatePath) || !fileExist(path)) diff --git a/src/interfaces.h b/src/handler/interfaces.h similarity index 92% rename from src/interfaces.h rename to src/handler/interfaces.h index 4379748..09f15e3 100644 --- a/src/interfaces.h +++ b/src/handler/interfaces.h @@ -5,8 +5,8 @@ #include #include -#include "subexport.h" -#include "webserver.h" +#include "../generator/config/subexport.h" +#include "../server/webserver.h" void refreshRulesets(string_array &ruleset_list, std::vector &rca); void readConf(); diff --git a/src/multithread.cpp b/src/handler/multithread.cpp similarity index 89% rename from src/multithread.cpp rename to src/handler/multithread.cpp index 5a7c689..f3fedce 100644 --- a/src/multithread.cpp +++ b/src/handler/multithread.cpp @@ -2,6 +2,9 @@ #include #include "webget.h" #include "multithread.h" +//#include "vfs.h" + +#include "../utils/network.h" //safety lock for multi-thread std::mutex on_emoji, on_rename, on_stream, on_time; @@ -60,7 +63,9 @@ void safe_set_times(string_array &data) std::shared_future fetchFileAsync(const std::string &path, const std::string &proxy, int cache_ttl, bool async) { std::shared_future retVal; - if(fileExist(path, true)) + /*if(vfs::vfs_exist(path)) + retVal = std::async(std::launch::async, [path](){return vfs::vfs_get(path);}); + else */if(fileExist(path, true)) retVal = std::async(std::launch::async, [path](){return fileGet(path, true);}); else if(isLink(path)) retVal = std::async(std::launch::async, [path, proxy, cache_ttl](){return webGet(path, proxy, cache_ttl);}); diff --git a/src/multithread.h b/src/handler/multithread.h similarity index 85% rename from src/multithread.h rename to src/handler/multithread.h index afe27de..3f8e3c6 100644 --- a/src/multithread.h +++ b/src/handler/multithread.h @@ -6,10 +6,10 @@ #include -#include "misc.h" -#include "ini_reader.h" +#include "../utils/ini_reader/ini_reader.h" +#include "../utils/string.h" -typedef std::lock_guard guarded_mutex; +using guarded_mutex = std::lock_guard; string_array safe_get_emojis(); string_array safe_get_renames(); diff --git a/src/upload.cpp b/src/handler/upload.cpp similarity index 91% rename from src/upload.cpp rename to src/handler/upload.cpp index adb26e4..39f1101 100644 --- a/src/upload.cpp +++ b/src/handler/upload.cpp @@ -1,9 +1,10 @@ #include +#include "../utils/ini_reader/ini_reader.h" +#include "../utils/logger.h" +#include "../utils/rapidjson_extra.h" +#include "../utils/system.h" #include "webget.h" -#include "ini_reader.h" -#include "logger.h" -#include "rapidjson_extra.h" std::string buildGistData(std::string name, std::string content) { @@ -70,7 +71,7 @@ int uploadGist(std::string name, std::string path, std::string content, bool wri { //std::cerr<<"No gist id is provided. Creating new gist...\n"; writeLog(0, "No Gist id is provided. Creating new Gist...", LOG_LEVEL_ERROR); - retVal = webPost("https://api.github.com/gists", buildGistData(path, content), getSystemProxy(), {"Authorization: token " + token}, &retData); + retVal = webPost("https://api.github.com/gists", buildGistData(path, content), getSystemProxy(), {{"Authorization", "token " + token}}, &retData); if(retVal != 201) { //std::cerr<<"Create new Gist failed! Return data:\n"< +#include "../utils/base64/base64.h" +#include "../utils/defer.h" +#include "../utils/file_extra.h" +#include "../utils/logger.h" +#include "../utils/urlencode.h" +#include "../version.h" #include "webget.h" -#include "version.h" -#include "misc.h" -#include "logger.h" #ifdef _WIN32 #ifndef _stat @@ -22,7 +25,7 @@ extern bool gPrintDbgInfo, gServeCacheOnFetchFail; extern int gLogLevel; /* -typedef std::lock_guard guarded_mutex; +using guarded_mutex = std::lock_guard; std::mutex cache_rw_lock; */ @@ -137,7 +140,7 @@ static int size_checker(void *clientp, curl_off_t dltotal, curl_off_t dlnow, cur curl_progress_data *data = reinterpret_cast(clientp); if(data->size_limit) { - if(dltotal > data->size_limit || dlnow > data->size_limit) + if(dlnow > data->size_limit) return 1; } } @@ -156,6 +159,7 @@ static inline void curl_set_common_options(CURL *curl_handle, const char *url, c curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 0L); curl_easy_setopt(curl_handle, CURLOPT_TIMEOUT, 15L); curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, user_agent_str.data()); + curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, ""); if(data) { if(data->size_limit) @@ -190,7 +194,7 @@ static int curlGet(const FetchArgument &argument, FetchResult &result) curl_progress_data limit; limit.size_limit = gMaxAllowedDownloadSize; curl_set_common_options(curl_handle, new_url.data(), &limit); - + list = curl_slist_append(list, "Content-Type: application/json;charset='utf-8'"); if(argument.request_headers) { for(auto &x : *argument.request_headers) @@ -216,6 +220,32 @@ static int curlGet(const FetchArgument &argument, FetchResult &result) else curl_easy_setopt(curl_handle, CURLOPT_HEADERFUNCTION, dummy_writer); + if(argument.cookies) + { + string_array cookies = split(*argument.cookies, "\r\n"); + for(auto &x : cookies) + curl_easy_setopt(curl_handle, CURLOPT_COOKIELIST, x.c_str()); + } + + switch(argument.method) + { + case HTTP_POST: + curl_easy_setopt(curl_handle, CURLOPT_POST, 1L); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, argument.post_data.data()); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, argument.post_data.size()); + break; + case HTTP_PATCH: + curl_easy_setopt(curl_handle, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, argument.post_data.data()); + curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDSIZE, argument.post_data.size()); + break; + case HTTP_HEAD: + curl_easy_setopt(curl_handle, CURLOPT_NOBODY, 1L); + break; + case HTTP_GET: + break; + } + unsigned int fail_count = 0, max_fails = 1; while(true) { @@ -227,6 +257,24 @@ static int curlGet(const FetchArgument &argument, FetchResult &result) } curl_easy_getinfo(curl_handle, CURLINFO_HTTP_CODE, &retVal); + + if(result.cookies) + { + curl_slist *cookies = nullptr; + curl_easy_getinfo(curl_handle, CURLINFO_COOKIELIST, &cookies); + if(cookies) + { + auto each = cookies; + while(each) + { + result.cookies->append(each->data); + *result.cookies += "\r\n"; + each = each->next; + } + } + curl_slist_free_all(cookies); + } + curl_easy_cleanup(curl_handle); if(data) @@ -248,9 +296,9 @@ static std::string dataGet(const std::string &url) if (comma == std::string::npos || comma == url.size() - 1) return std::string(); - std::string data = UrlDecode(url.substr(comma + 1)); + std::string data = urlDecode(url.substr(comma + 1)); if (endsWith(url.substr(0, comma), ";base64")) { - return urlsafe_base64_decode(data); + return urlSafeBase64Decode(data); } else { return data; } @@ -263,13 +311,13 @@ std::string buildSocks5ProxyString(const std::string &addr, int port, const std: return proxystr; } -std::string webGet(const std::string &url, const std::string &proxy, unsigned int cache_ttl, std::string *response_headers, string_map *request_headers) +std::string webGet(const std::string &url, const std::string &proxy, unsigned int cache_ttl, std::string *response_headers, string_icase_map *request_headers) { int return_code = 0; std::string content; - FetchArgument argument {url, proxy, request_headers, cache_ttl}; - FetchResult fetch_res {&return_code, &content, response_headers}; + FetchArgument argument {HTTP_GET, url, proxy, "", request_headers, nullptr, cache_ttl}; + FetchResult fetch_res {&return_code, &content, response_headers, nullptr}; if (startsWith(url, "data:")) return dataGet(url); @@ -375,9 +423,13 @@ int curlPost(const std::string &url, const std::string &data, const std::string return retVal; } -int webPost(const std::string &url, const std::string &data, const std::string &proxy, const string_array &request_headers, std::string *retData) +int webPost(const std::string &url, const std::string &data, const std::string &proxy, const string_icase_map &request_headers, std::string *retData) { - return curlPost(url, data, proxy, request_headers, retData); + //return curlPost(url, data, proxy, request_headers, retData); + int return_code = 0; + FetchArgument argument {HTTP_POST, url, proxy, "", &request_headers, nullptr, 0}; + FetchResult fetch_res {&return_code, retData, nullptr, nullptr}; + return webGet(argument, fetch_res); } int curlPatch(const std::string &url, const std::string &data, const std::string &proxy, const string_array &request_headers, std::string *retData) @@ -418,9 +470,13 @@ int curlPatch(const std::string &url, const std::string &data, const std::string return retVal; } -int webPatch(const std::string &url, const std::string &data, const std::string &proxy, const string_array &request_headers, std::string *retData) +int webPatch(const std::string &url, const std::string &data, const std::string &proxy, const string_icase_map &request_headers, std::string *retData) { - return curlPatch(url, data, proxy, request_headers, retData); + //return curlPatch(url, data, proxy, request_headers, retData); + int return_code = 0; + FetchArgument argument {HTTP_PATCH, url, proxy, "", &request_headers, nullptr, 0}; + FetchResult fetch_res {&return_code, retData, nullptr, nullptr}; + return webGet(argument, fetch_res); } int curlHead(const std::string &url, const std::string &proxy, const string_array &request_headers, std::string &response_headers) @@ -457,7 +513,59 @@ int curlHead(const std::string &url, const std::string &proxy, const string_arra return retVal; } -int webHead(const std::string &url, const std::string &proxy, const string_array &request_headers, std::string &response_headers) +int webHead(const std::string &url, const std::string &proxy, const string_icase_map &request_headers, std::string &response_headers) { - return curlHead(url, proxy, request_headers, response_headers); + //return curlHead(url, proxy, request_headers, response_headers); + int return_code = 0; + FetchArgument argument {HTTP_HEAD, url, proxy, "", &request_headers, nullptr, 0}; + FetchResult fetch_res {&return_code, nullptr, &response_headers, nullptr}; + return webGet(argument, fetch_res); +} + +string_array headers_map_to_array(const string_map &headers) +{ + string_array result; + for(auto &kv : headers) + result.push_back(kv.first + ": " + kv.second); + return result; +} + +int webGet(const FetchArgument& argument, FetchResult &result) +{ + return curlGet(argument, result); + /* + switch(argument.method) + { + case HTTP_GET: + return curlGet(argument, result); + case HTTP_POST: + { + string_array request_header; + if(argument.request_headers != nullptr) + request_header = headers_map_to_array(*argument.request_headers); + int res = curlPost(argument.url, argument.post_data, argument.proxy, request_header, result.content); + *result.status_code = res; + return res; + } + case HTTP_PATCH: + { + string_array request_header; + if(argument.request_headers != nullptr) + request_header = headers_map_to_array(*argument.request_headers); + int res = curlPatch(argument.url, argument.post_data, argument.proxy, request_header, result.content); + *result.status_code = res; + return res; + } + case HTTP_HEAD: + { + string_array request_header; + if(argument.request_headers != nullptr) + request_header = headers_map_to_array(*argument.request_headers); + int res = curlHead(argument.url, argument.proxy, request_header, *result.response_headers); + *result.status_code = res; + return res; + } + } + return -1; + */ } diff --git a/src/webget.h b/src/handler/webget.h similarity index 56% rename from src/webget.h rename to src/handler/webget.h index a2a0aec..8ce1a4a 100644 --- a/src/webget.h +++ b/src/handler/webget.h @@ -4,36 +4,45 @@ #include #include -#include "misc.h" +#include "../utils/map_extra.h" +#include "../utils/string.h" + +enum http_method +{ + HTTP_GET, + HTTP_HEAD, + HTTP_POST, + HTTP_PATCH +}; struct FetchArgument { + const http_method method; const std::string url; const std::string proxy; - string_map *request_headers = NULL; + const std::string post_data; + const string_icase_map *request_headers = nullptr; + std::string *cookies = nullptr; const unsigned int cache_ttl = 0; }; struct FetchResult { int *status_code; - std::string *content = NULL; - std::string *response_headers = NULL; + std::string *content = nullptr; + std::string *response_headers = nullptr; + std::string *cookies = nullptr; }; -std::string webGet(const std::string &url, const std::string &proxy = "", unsigned int cache_ttl = 0, std::string *response_headers = NULL, string_map *request_headers = NULL); +int webGet(const FetchArgument& argument, FetchResult &result); +std::string webGet(const std::string &url, const std::string &proxy = "", unsigned int cache_ttl = 0, std::string *response_headers = nullptr, string_icase_map *request_headers = nullptr); void flushCache(); -int webPost(const std::string &url, const std::string &data, const std::string &proxy, const string_array &request_headers, std::string *retData); -int webPatch(const std::string &url, const std::string &data, const std::string &proxy, const string_array &request_headers, std::string *retData); +int webPost(const std::string &url, const std::string &data, const std::string &proxy, const string_icase_map &request_headers, std::string *retData); +int webPatch(const std::string &url, const std::string &data, const std::string &proxy, const string_icase_map &request_headers, std::string *retData); std::string buildSocks5ProxyString(const std::string &addr, int port, const std::string &username, const std::string &password); // Unimplemented: (CURLOPT_HTTPHEADER: Host:) std::string httpGet(const std::string &host, const std::string &addr, const std::string &uri); std::string httpsGet(const std::string &host, const std::string &addr, const std::string &uri); -static inline bool isLink(const std::string &url) -{ - return startsWith(url, "https://") || startsWith(url, "http://") || startsWith(url, "data:"); -} - #endif // WEBGET_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index 0c87254..521c3be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,18 +3,31 @@ #include #include -#include "interfaces.h" +#include +#include + +#include "handler/interfaces.h" +#include "handler/webget.h" +#include "script/cron.h" +#include "server/socket.h" +#include "server/webserver.h" +#include "utils/defer.h" +#include "utils/file_extra.h" +#include "utils/logger.h" +#include "utils/network.h" +#include "utils/rapidjson_extra.h" +#include "utils/system.h" +#include "utils/urlencode.h" #include "version.h" -#include "misc.h" -#include "socket.h" -#include "webget.h" -#include "logger.h" + +//#include "vfs.h" extern std::string gPrefPath, gAccessToken, gListenAddress, gGenerateProfiles, gManagedConfigPrefix; extern bool gAPIMode, gGeneratorMode, gCFWChildProcess, gUpdateRulesetOnRequest; extern int gListenPort, gMaxConcurThreads, gMaxPendingConns; extern string_array gCustomRulesets; extern std::vector gRulesetContent; +extern bool gEnableCron; #ifndef _WIN32 void SetConsoleTitle(const std::string &title) @@ -97,6 +110,12 @@ void signal_handler(int sig) } } +void cron_tick_caller() +{ + if(gEnableCron) + cron_tick(); +} + int main(int argc, char *argv[]) { #ifndef _DEBUG @@ -143,7 +162,7 @@ int main(int argc, char *argv[]) if(!gUpdateRulesetOnRequest) refreshRulesets(gCustomRulesets, gRulesetContent); - std::string env_api_mode = GetEnv("API_MODE"), env_managed_prefix = GetEnv("MANAGED_PREFIX"), env_token = GetEnv("API_TOKEN"); + std::string env_api_mode = getEnv("API_MODE"), env_managed_prefix = getEnv("MANAGED_PREFIX"), env_token = getEnv("API_TOKEN"); gAPIMode = tribool().parse(toLower(env_api_mode)).get(gAPIMode); if(env_managed_prefix.size()) gManagedConfigPrefix = env_managed_prefix; @@ -258,20 +277,24 @@ int main(int argc, char *argv[]) { append_response("GET", "/get", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string { - std::string url = UrlDecode(getUrlArg(request.argument, "url")); + std::string url = urlDecode(getUrlArg(request.argument, "url")); return webGet(url, ""); }); append_response("GET", "/getlocal", "text/plain;charset=utf-8", [](RESPONSE_CALLBACK_ARGS) -> std::string { - return fileGet(UrlDecode(getUrlArg(request.argument, "path"))); + return fileGet(urlDecode(getUrlArg(request.argument, "path"))); }); } - std::string env_port = GetEnv("PORT"); + //append_response("POST", "/create-profile", "text/plain;charset=utf-8", createProfile); + + //append_response("GET", "/list-profiles", "text/plain;charset=utf-8", listProfiles); + + std::string env_port = getEnv("PORT"); if(env_port.size()) gListenPort = to_int(env_port, gListenPort); - listener_args args = {gListenAddress, gListenPort, gMaxPendingConns, gMaxConcurThreads}; + listener_args args = {gListenAddress, gListenPort, gMaxPendingConns, gMaxConcurThreads, cron_tick_caller, 200}; //std::cout<<"Serving HTTP @ http://"< -#include -#include -#include -#include -#include -#include -#include -//#include -#include -#include -#include - -/* -#ifdef USE_STD_REGEX -#include -#else -*/ -#include -typedef jpcre2::select jp; -//#endif // USE_STD_REGEX - -#include - -/* -#ifdef USE_MBEDTLS -#include -#else -#include -#endif // USE_MBEDTLS -*/ -#include "md5.h" - -#include "misc.h" - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -//#include -#include -#include -#else -#ifndef __hpux -#include -#endif /* __hpux */ -#ifndef _access -#define _access access -#endif // _access -#include -#endif // _WIN32 - -void sleep(int interval) -{ - /* - #ifdef _WIN32 - Sleep(interval); - #else - // Portable sleep for platforms other than Windows. - struct timeval wait = { 0, interval * 1000 }; - select(0, NULL, NULL, NULL, &wait); - #endif - */ - //upgrade to c++11 standard - std::this_thread::sleep_for(std::chrono::milliseconds(interval)); -} - -// ANSI code page (GBK on 936) to UTF8 -std::string ACPToUTF8(const std::string &str_src) -{ -#ifdef _WIN32 - const char* strGBK = str_src.c_str(); - int len = MultiByteToWideChar(CP_ACP, 0, strGBK, -1, NULL, 0); - wchar_t* wstr = new wchar_t[len + 1]; - memset(wstr, 0, len + 1); - MultiByteToWideChar(CP_ACP, 0, strGBK, -1, wstr, len); - len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); - char* str = new char[len + 1]; - memset(str, 0, len + 1); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); - std::string strTemp = str; - delete[] wstr; - delete[] str; - return strTemp; -#else - return str_src; -#endif // _WIN32 -} - -// UTF8 to ANSI code page (GBK on 936) -std::string UTF8ToACP(const std::string &str_src) -{ -#ifdef _WIN32 - const char* strUTF8 = str_src.data(); - int len = MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, NULL, 0); - wchar_t* wszGBK = new wchar_t[len + 1]; - memset(wszGBK, 0, len * 2 + 2); - MultiByteToWideChar(CP_UTF8, 0, strUTF8, -1, wszGBK, len); - len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); - char* szGBK = new char[len + 1]; - memset(szGBK, 0, len + 1); - WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); - std::string strTemp(szGBK); - if (wszGBK) - delete[] wszGBK; - if (szGBK) - delete[] szGBK; - return strTemp; -#else - return str_src; -#endif -} - -#ifdef _WIN32 -// std::string to wstring -void StringToWstring(std::wstring& szDst, const std::string &str) -{ - std::string temp = str; - int len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)temp.c_str(), -1, NULL,0); - wchar_t* wszUtf8 = new wchar_t[len + 1]; - memset(wszUtf8, 0, len * 2 + 2); - MultiByteToWideChar(CP_ACP, 0, (LPCSTR)temp.c_str(), -1, (LPWSTR)wszUtf8, len); - szDst = wszUtf8; - //std::wstring r = wszUtf8; - delete[] wszUtf8; -} -#else -/* Unimplemented: std::codecvt_utf8 */ -#endif // _WIN32 - -unsigned char ToHex(unsigned char x) -{ - return x > 9 ? x + 55 : x + 48; -} - -unsigned char FromHex(unsigned char x) -{ - unsigned char y; - if (x >= 'A' && x <= 'Z') - y = x - 'A' + 10; - else if (x >= 'a' && x <= 'z') - y = x - 'a' + 10; - else if (x >= '0' && x <= '9') - y = x - '0'; - else - y = x; - return y; -} - -std::string UrlEncode(const std::string& str) -{ - std::string strTemp = ""; - string_size length = str.length(); - for (string_size i = 0; i < length; i++) - { - if (isalnum((unsigned char)str[i]) || - (str[i] == '-') || - (str[i] == '_') || - (str[i] == '.') || - (str[i] == '~')) - strTemp += str[i]; - else - { - strTemp += '%'; - strTemp += ToHex((unsigned char)str[i] >> 4); - strTemp += ToHex((unsigned char)str[i] % 16); - } - } - return strTemp; -} - -std::string UrlDecode(const std::string& str) -{ - std::string strTemp; - string_size length = str.length(); - for (string_size i = 0; i < length; i++) - { - if (str[i] == '+') - strTemp += ' '; - else if (str[i] == '%') - { - if(i + 2 >= length) - return strTemp; - if(isalnum(str[i + 1]) && isalnum(str[i + 2])) - { - unsigned char high = FromHex((unsigned char)str[++i]); - unsigned char low = FromHex((unsigned char)str[++i]); - strTemp += high * 16 + low; - } - else - strTemp += str[i]; - } - else - strTemp += str[i]; - } - return strTemp; -} - -/* -static inline bool is_base64(unsigned char c) -{ - return (isalnum(c) || (c == '+') || (c == '/') || (c == '-') || (c == '_')); -} -*/ - -std::string base64_encode(const std::string &string_to_encode) -{ - char const* bytes_to_encode = string_to_encode.data(); - unsigned int in_len = string_to_encode.size(); - - std::string ret; - int i = 0; - unsigned char char_array_3[3]; - unsigned char char_array_4[4]; - - while (in_len--) - { - char_array_3[i++] = *(bytes_to_encode++); - if (i == 3) - { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for(i = 0; (i <4) ; i++) - ret += base64_chars[char_array_4[i]]; - i = 0; - } - } - - if (i) - { - int j; - for(j = i; j < 3; j++) - char_array_3[j] = '\0'; - - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - - for (j = 0; (j < i + 1); j++) - ret += base64_chars[char_array_4[j]]; - - while((i++ < 3)) - ret += '='; - - } - - return ret; - -} - -std::string base64_decode(const std::string &encoded_string, bool accept_urlsafe) -{ - string_size in_len = encoded_string.size(); - string_size i = 0; - string_size in_ = 0; - unsigned char char_array_4[4], char_array_3[3], uchar; - static unsigned char dtable[256], itable[256], table_ready = 0; - std::string ret; - - // Should not need thread_local with the flag... - if (!table_ready) - { - // No memset needed for static/TLS - for (string_size k = 0; k < base64_chars.length(); k++) - { - uchar = base64_chars[k]; // make compiler happy - dtable[uchar] = k; // decode (find) - itable[uchar] = 1; // is_base64 - } - const unsigned char dash = '-', add = '+', under = '_', slash = '/'; - // Add urlsafe table - dtable[dash] = dtable[add]; itable[dash] = 2; - dtable[under] = dtable[slash]; itable[under] = 2; - table_ready = 1; - } - - while (in_len-- && (encoded_string[in_] != '=')) - { - uchar = encoded_string[in_]; // make compiler happy - if (!(accept_urlsafe ? itable[uchar] : (itable[uchar] == 1))) // break away from the while condition - { - ret += uchar; // not base64 encoded data, copy to result - in_++; - i = 0; - continue; - } - char_array_4[i++] = uchar; - in_++; - if (i == 4) - { - for (string_size j = 0; j < 4; j++) - char_array_4[j] = dtable[char_array_4[j]]; - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (i = 0; (i < 3); i++) - ret += char_array_3[i]; - i = 0; - } - } - - if (i) - { - for (string_size j = i; j <4; j++) - char_array_4[j] = 0; - - for (string_size j = 0; j <4; j++) - char_array_4[j] = dtable[char_array_4[j]]; - - char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); - char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); - char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; - - for (string_size j = 0; (j < i - 1); j++) - ret += char_array_3[j]; - } - - return ret; -} - -std::vector split(const std::string &s, const std::string &seperator) -{ - std::vector result; - typedef std::string::size_type string_size; - string_size i = 0; - - while(i != s.size()) - { - int flag = 0; - while(i != s.size() && flag == 0) - { - flag = 1; - for(string_size x = 0; x < seperator.size(); ++x) - if(s[i] == seperator[x]) - { - ++i; - flag = 0; - break; - } - } - - flag = 0; - string_size j = i; - while(j != s.size() && flag == 0) - { - for(string_size x = 0; x < seperator.size(); ++x) - if(s[j] == seperator[x]) - { - flag = 1; - break; - } - if(flag == 0) - ++j; - } - if(i != j) - { - result.push_back(s.substr(i, j-i)); - i = j; - } - } - return result; -} - -std::string GetEnv(const std::string &name) -{ - std::string retVal; -#ifdef _WIN32 - char chrData[1024] = {}; - if(GetEnvironmentVariable(name.c_str(), chrData, 1023)) - retVal.assign(chrData); -#else - char *env = getenv(name.c_str()); - if(env != NULL) - retVal.assign(env); -#endif // _WIN32 - return retVal; -} - -std::string getSystemProxy() -{ -#ifdef _WIN32 - HKEY key; - auto ret = RegOpenKeyEx(HKEY_CURRENT_USER, R"(Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings)", 0, KEY_ALL_ACCESS, &key); - if(ret != ERROR_SUCCESS) - { - //std::cout << "open failed: " << ret << std::endl; - return std::string(); - } - - DWORD values_count, max_value_name_len, max_value_len; - ret = RegQueryInfoKey(key, NULL, NULL, NULL, NULL, NULL, NULL, - &values_count, &max_value_name_len, &max_value_len, NULL, NULL); - if(ret != ERROR_SUCCESS) - { - //std::cout << "query failed" << std::endl; - return std::string(); - } - - std::vector, DWORD, std::shared_ptr>> values; - for(DWORD i = 0; i < values_count; i++) - { - std::shared_ptr value_name(new char[max_value_name_len + 1], - std::default_delete()); - DWORD value_name_len = max_value_name_len + 1; - DWORD value_type, value_len; - RegEnumValue(key, i, value_name.get(), &value_name_len, NULL, &value_type, NULL, &value_len); - std::shared_ptr value(new BYTE[value_len], - std::default_delete()); - value_name_len = max_value_name_len + 1; - RegEnumValue(key, i, value_name.get(), &value_name_len, NULL, &value_type, value.get(), &value_len); - values.push_back(std::make_tuple(value_name, value_type, value)); - } - - DWORD ProxyEnable = 0; - for (auto x : values) - { - if (strcmp(std::get<0>(x).get(), "ProxyEnable") == 0) - { - ProxyEnable = *(DWORD*)(std::get<2>(x).get()); - } - } - - if (ProxyEnable) - { - for (auto x : values) - { - if (strcmp(std::get<0>(x).get(), "ProxyServer") == 0) - { - //std::cout << "ProxyServer: " << (char*)(std::get<2>(x).get()) << std::endl; - return std::string((char*)(std::get<2>(x).get())); - } - } - } - /* - else { - //std::cout << "Proxy not Enabled" << std::endl; - } - */ - //return 0; - return std::string(); -#else - string_array proxy_env = {"all_proxy", "ALL_PROXY", "http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY"}; - for(std::string &x : proxy_env) - { - char* proxy = getenv(x.c_str()); - if(proxy != NULL) - return std::string(proxy); - } - return std::string(); -#endif // _WIN32 -} - -void trim_self_of(std::string &str, char target, bool before, bool after) -{ - if (!before && !after) - return; - std::string::size_type pos = str.size() - 1; - if (after) - pos = str.find_last_not_of(target); - if (pos != std::string::npos) - str.erase(pos + 1); - if (before) - pos = str.find_first_not_of(target); - str.erase(0, pos); -} - -std::string trim_of(const std::string& str, char target, bool before, bool after) -{ - if (!before && !after) - return str; - std::string::size_type pos = 0; - if (before) - pos = str.find_first_not_of(target); - if (pos == std::string::npos) - { - return str; - } - std::string::size_type pos2 = str.size() - 1; - if (after) - pos2 = str.find_last_not_of(target); - if (pos2 != std::string::npos) - { - return str.substr(pos, pos2 - pos + 1); - } - return str.substr(pos); -} - -std::string trim(const std::string& str, bool before, bool after) -{ - return trim_of(str, ' ', before, after); -} - -std::string trim_quote(const std::string &str, bool before, bool after) -{ - return trim_of(str, '\"', before, after); -} - -std::string getUrlArg(const std::string &url, const std::string &request) -{ - //std::smatch result; - /* - if (regex_search(url.cbegin(), url.cend(), result, std::regex(request + "=(.*?)&"))) - { - return result[1]; - } - else if (regex_search(url.cbegin(), url.cend(), result, std::regex(request + "=(.*)"))) - { - return result[1]; - } - else - { - return std::string(); - } - */ - /* - std::string::size_type spos = url.find("?"); - if(spos != url.npos) - url.erase(0, spos + 1); - - string_array vArray, arglist = split(url, "&"); - for(std::string &x : arglist) - { - std::string::size_type epos = x.find("="); - if(epos != x.npos) - { - if(x.substr(0, epos) == request) - return x.substr(epos + 1); - } - } - */ - std::string pattern = request + "="; - std::string::size_type pos = url.size(); - while(pos) - { - pos = url.rfind(pattern, pos); - if(pos != url.npos) - { - if(pos == 0 || url[pos - 1] == '&' || url[pos - 1] == '?') - { - pos += pattern.size(); - return url.substr(pos, url.find("&", pos) - pos); - } - } - else - break; - pos--; - } - return std::string(); -} - -std::string replace_all_distinct(std::string str, const std::string &old_value, const std::string &new_value) -{ - for(std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) - { - if((pos = str.find(old_value, pos)) != std::string::npos) - str.replace(pos, old_value.length(), new_value); - else - break; - } - return str; -} -/* -#ifdef USE_STD_REGEX -bool regValid(const std::string ®) -{ - try - { - std::regex r(reg, std::regex::ECMAScript); - return true; - } - catch (std::regex_error &e) - { - return false; - } -} - -bool regFind(const std::string &src, const std::string &match) -{ - try - { - std::regex::flag_type flags = std::regex::extended | std::regex::ECMAScript; - std::string target = match; - if(match.find("(?i)") == 0) - { - target.erase(0, 4); - flags |= std::regex::icase; - } - std::regex reg(target, flags); - return regex_search(src, reg); - } - catch (std::regex_error &e) - { - return false; - } -} - -std::string regReplace(const std::string &src, const std::string &match, const std::string &rep) -{ - std::string result = ""; - try - { - std::regex::flag_type flags = std::regex::extended | std::regex::ECMAScript; - std::string target = match; - if(match.find("(?i)") == 0) - { - target.erase(0, 4); - flags |= std::regex::icase; - } - std::regex reg(target, flags); - regex_replace(back_inserter(result), src.begin(), src.end(), reg, rep); - } - catch (std::regex_error &e) - { - result = src; - } - return result; -} - -bool regMatch(const std::string &src, const std::string &match) -{ - try - { - std::regex::flag_type flags = std::regex::extended | std::regex::ECMAScript; - std::string target = match; - if(match.find("(?i)") == 0) - { - target.erase(0, 4); - flags |= std::regex::icase; - } - std::regex reg(target, flags); - return regex_match(src, reg); - } - catch (std::regex_error &e) - { - return false; - } -} - -int regGetMatch(const std::string &src, const std::string &match, size_t group_count, ...) -{ - try - { - std::regex::flag_type flags = std::regex::extended | std::regex::ECMAScript; - std::string target = match; - if(match.find("(?i)") == 0) - { - target.erase(0, 4); - flags |= std::regex::icase; - } - std::regex reg(target, flags); - std::smatch result; - if(regex_search(src.cbegin(), src.cend(), result, reg)) - { - if(result.size() < group_count - 1) - return -1; - va_list vl; - va_start(vl, group_count); - size_t index = 0; - while(group_count) - { - std::string* arg = va_arg(vl, std::string*); - if(arg != NULL) - *arg = std::move(result[index]); - index++; - group_count--; - } - va_end(vl); - } - else - return -2; - return 0; - } - catch (std::regex_error&) - { - return -3; - } -} - -#else -*/ -bool regMatch(const std::string &src, const std::string &match) -{ - jp::Regex reg; - reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_ANCHORED|PCRE2_ENDANCHORED|PCRE2_UTF).compile(); - if(!reg) - return false; - return reg.match(src, "g"); -} - -bool regFind(const std::string &src, const std::string &match) -{ - jp::Regex reg; - reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile(); - if(!reg) - return false; - return reg.match(src, "g"); -} - -std::string regReplace(const std::string &src, const std::string &match, const std::string &rep, bool global, bool multiline) -{ - jp::Regex reg; - reg.setPattern(match).addModifier(multiline ? "m" : "").addPcre2Option(PCRE2_UTF|PCRE2_MULTILINE|PCRE2_ALT_BSUX).compile(); - if(!reg) - return src; - return reg.replace(src, rep, global ? "gx" : "x"); -} - -bool regValid(const std::string ®) -{ - jp::Regex r; - r.setPattern(reg).addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile(); - return !!r; -} - -int regGetMatch(const std::string &src, const std::string &match, size_t group_count, ...) -{ - jp::Regex reg; - reg.setPattern(match).addModifier("m").addPcre2Option(PCRE2_UTF|PCRE2_ALT_BSUX).compile(); - jp::VecNum vec_num; - jp::RegexMatch rm; - size_t count = rm.setRegexObject(®).setSubject(src).setNumberedSubstringVector(&vec_num).setModifier("g").match(); - if(!count) - return -1; - va_list vl; - va_start(vl, group_count); - size_t index = 0, match_index = 0; - while(group_count) - { - std::string* arg = va_arg(vl, std::string*); - if(arg != NULL) - *arg = std::move(vec_num[match_index][index]); - index++; - group_count--; - if(vec_num[match_index].size() <= index) - { - match_index++; - index = 0; - } - if(vec_num.size() <= match_index) - break; - } - va_end(vl); - return 0; -} - -//#endif // USE_STD_REGEX - -std::string regTrim(const std::string &src) -{ - return regReplace(src, "^\\s*([\\s\\S]*)\\s*$", "$1", false, false); -} - -std::string speedCalc(double speed) -{ - if(speed == 0.0) - return std::string("0.00B"); - char str[10]; - std::string retstr; - if(speed >= 1073741824.0) - sprintf(str, "%.2fGB", speed / 1073741824.0); - else if(speed >= 1048576.0) - sprintf(str, "%.2fMB", speed / 1048576.0); - else if(speed >= 1024.0) - sprintf(str, "%.2fKB", speed / 1024.0); - else - sprintf(str, "%.2fB", speed); - retstr = str; - return retstr; -} - -std::string urlsafe_base64_reverse(const std::string &encoded_string) -{ - return replace_all_distinct(replace_all_distinct(encoded_string, "-", "+"), "_", "/"); -} - -std::string urlsafe_base64(const std::string &encoded_string) -{ - return replace_all_distinct(replace_all_distinct(replace_all_distinct(encoded_string, "+", "-"), "/", "_"), "=", ""); -} - -std::string urlsafe_base64_decode(const std::string &encoded_string) -{ - return base64_decode(encoded_string, true); -} - -std::string urlsafe_base64_encode(const std::string &string_to_encode) -{ - return urlsafe_base64(base64_encode(string_to_encode)); -} - -std::string getMD5(const std::string &data) -{ - std::string result; - - /* - unsigned int i = 0; - unsigned char digest[16] = {}; - -#ifdef USE_MBEDTLS - mbedtls_md5_context ctx; - - mbedtls_md5_init(&ctx); - mbedtls_md5_starts_ret(&ctx); - mbedtls_md5_update_ret(&ctx, reinterpret_cast(data.data()), data.size()); - mbedtls_md5_finish_ret(&ctx, reinterpret_cast(&digest)); - mbedtls_md5_free(&ctx); -#else - MD5_CTX ctx; - - MD5_Init(&ctx); - MD5_Update(&ctx, data.data(), data.size()); - MD5_Final((unsigned char *)&digest, &ctx); -#endif // USE_MBEDTLS - - char tmp[3] = {}; - for(i = 0; i < 16; i++) - { - snprintf(tmp, 3, "%02x", digest[i]); - result += tmp; - } - */ - - char result_str[MD5_STRING_SIZE]; - md5::md5_t md5; - md5.process(data.data(), data.size()); - md5.finish(); - md5.get_string(result_str); - result.assign(result_str); - - return result; -} - -bool isInScope(const std::string &path) -{ -#ifdef _WIN32 - if(path.find(":\\") != path.npos || path.find("..") != path.npos) - return false; -#else - if(startsWith(path, "/") || path.find("..") != path.npos) - return false; -#endif // _WIN32 - return true; -} - -// TODO: Add preprocessor option to disable (open web service safety) -std::string fileGet(const std::string &path, bool scope_limit) -{ - std::string content; - - if(scope_limit && !isInScope(path)) - return std::string(); - - std::FILE *fp = std::fopen(path.c_str(), "rb"); - if(fp) - { - std::fseek(fp, 0, SEEK_END); - long tot = std::ftell(fp); - /* - char *data = new char[tot + 1]; - data[tot] = '\0'; - std::rewind(fp); - std::fread(&data[0], 1, tot, fp); - std::fclose(fp); - content.assign(data, tot); - delete[] data; - */ - content.resize(tot); - std::rewind(fp); - std::fread(&content[0], 1, tot, fp); - std::fclose(fp); - } - - /* - std::stringstream sstream; - std::ifstream infile; - infile.open(path, std::ios::binary); - if(infile) - { - sstream< regLists = {"^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$", "^((?:[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4})*)?)::((?:([0-9A-Fa-f]{1,4}:)*[0-9A-Fa-f]{1,4})?)$", "^(::(?:[0-9A-Fa-f]{1,4})(?::[0-9A-Fa-f]{1,4}){5})|((?:[0-9A-Fa-f]{1,4})(?::[0-9A-Fa-f]{1,4}){5}::)$"}; - for(unsigned int i = 0; i < regLists.size(); i++) - { - if(regMatch(address, regLists[i])) - return true; - } - return false; -} - -std::string rand_str(const int len) -{ - std::string retData; - srand(time(NULL)); - int cnt = 0; - while(cnt < len) - { - switch((rand() % 3)) - { - case 1: - retData += ('A' + rand() % 26); - break; - case 2: - retData += ('a' + rand() % 26); - break; - default: - retData += ('0' + rand() % 10); - break; - } - cnt++; - } - return retData; -} - -void urlParse(std::string &url, std::string &host, std::string &path, int &port, bool &isTLS) -{ - std::vector args; - string_size pos; - - if(regMatch(url, "^https://(.*)")) - isTLS = true; - url = regReplace(url, "^(http|https)://", ""); - pos = url.find("/"); - if(pos == url.npos) - { - host = url; - path = "/"; - } - else - { - host = url.substr(0, pos); - path = url.substr(pos); - } - pos = host.rfind(":"); - if(regFind(host, "\\[(.*)\\]")) //IPv6 - { - args = split(regReplace(host, "\\[(.*)\\](.*)", "$1,$2"), ","); - if(args.size() == 2) //with port - port = to_int(args[1].substr(1)); - host = args[0]; - } - else if(pos != host.npos) - { - port = to_int(host.substr(pos + 1)); - host = host.substr(0, pos); - } - if(port == 0) - { - if(isTLS) - port = 443; - else - port = 80; - } -} - -bool is_str_utf8(const std::string &data) -{ - const char *str = data.c_str(); - unsigned int nBytes = 0; - for (unsigned int i = 0; str[i] != '\0'; ++i) - { - unsigned char chr = *(str + i); - if (nBytes == 0) - { - if (chr >= 0x80) - { - if (chr >= 0xFC && chr <= 0xFD) - nBytes = 6; - else if (chr >= 0xF8) - nBytes = 5; - else if (chr >= 0xF0) - nBytes = 4; - else if (chr >= 0xE0) - nBytes = 3; - else if (chr >= 0xC0) - nBytes = 2; - else - return false; - nBytes--; - } - } - else - { - if ((chr & 0xC0) != 0x80) - return false; - nBytes--; - } - } - if (nBytes != 0) - return false; - return true; -} - -void removeUTF8BOM(std::string &data) -{ - if(data.compare(0, 3, "\xEF\xBB\xBF") == 0) - data = data.substr(3); -} - -int shortAssemble(unsigned short num_a, unsigned short num_b) -{ - return (int)num_b << 16 | num_a; -} - -void shortDisassemble(int source, unsigned short &num_a, unsigned short &num_b) -{ - num_a = (unsigned short)source; - num_b = (unsigned short)(source >> 16); -} - -int to_int(const std::string &str, int def_value) -{ - if(str.empty()) - return def_value; - /* - int retval = 0; - char c; - std::stringstream ss(str); - if(!(ss >> retval)) - return def_value; - else if(ss >> c) - return def_value; - else - return retval; - */ - return std::atoi(str.data()); -} - -std::string getFormData(const std::string &raw_data) -{ - std::stringstream strstrm; - std::string line; - - std::string boundary; - std::string file; /* actual file content */ - - int i = 0; - - strstrm<= bl - 1) - { - // We are at the end of the file - endfile = true; - break; - } - k++; - } - j++; - } - file.append(buffer, j); - j = 0; - }; - break; - } - i++; - } - return file; -} - -std::string UTF8ToCodePoint(const std::string &data) -{ - std::stringstream ss; - for(std::string::size_type i = 0; i < data.size(); i++) - { - int charcode = data[i] & 0xff; - if((charcode >> 7) == 0) - { - ss<> 5) == 6) - { - ss<<"\\u"<> 4) == 14) - { - ss<<"\\u"<> 3) == 30) - { - ss<<"\\u"< -#include -#include -#include -#include -#include -#include - -#include - -#include "string_hash.h" - -#ifdef _WIN32 -#include -#define PATH_SLASH "\\" -#else -#include -#include -#define PATH_SLASH "//" -#endif // _WIN32 - -#define CONCAT(a,b) a ## b -#define DO_CONCAT(a,b) CONCAT(a,b) -template class __defer_struct final {private: T fn; bool __cancelled = false; public: explicit __defer_struct(T func) : fn(std::move(func)) {} ~__defer_struct() {if(!__cancelled) fn();} void cancel() {__cancelled = true;} }; -//#define defer(x) std::unique_ptr DO_CONCAT(__defer_deleter_,__LINE__) (nullptr, [&](...){x}); -#define defer(x) __defer_struct DO_CONCAT(__defer_deleter,__LINE__) ([&](...){x;}); - -#define GETBIT(x,n) (((int)x < 1) ? 0 : ((x >> (n - 1)) & 1)) -#define SETBIT(x,n,v) x ^= (-v ^ x) & (1UL << (n - 1)) - -typedef std::string::size_type string_size; -typedef std::vector string_array; -typedef std::map string_map; -typedef const std::string &refCnstStr; - -static const std::string base64_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -std::string UrlEncode(const std::string& str); -std::string UrlDecode(const std::string& str); -std::string base64_decode(const std::string &encoded_string, bool accept_urlsafe = false); -std::string base64_encode(const std::string &string_to_encode); - -std::vector split(const std::string &s, const std::string &seperator); -std::string getUrlArg(const std::string &url, const std::string &request); -std::string replace_all_distinct(std::string str, const std::string &old_value, const std::string &new_value); -std::string urlsafe_base64(const std::string &encoded_string); -std::string urlsafe_base64_reverse(const std::string &encoded_string); -std::string urlsafe_base64_decode(const std::string &encoded_string); -std::string urlsafe_base64_encode(const std::string &string_to_encode); -std::string UTF8ToACP(const std::string &str_src); -std::string ACPToUTF8(const std::string &str_src); -std::string trim_of(const std::string& str, char target, bool before = true, bool after = true); -std::string trim(const std::string& str, bool before = true, bool after = true); -std::string trim_quote(const std::string &str, bool before = true, bool after = true); -void trim_self_of(std::string &str, char target, bool before = true, bool after = true); -std::string getSystemProxy(); -std::string rand_str(const int len); -bool is_str_utf8(const std::string &data); -std::string getFormData(const std::string &raw_data); - -void sleep(int interval); -bool regValid(const std::string ®); -bool regFind(const std::string &src, const std::string &match); -std::string regReplace(const std::string &src, const std::string &match, const std::string &rep, bool global = true, bool multiline = true); -bool regMatch(const std::string &src, const std::string &match); -int regGetMatch(const std::string &src, const std::string &match, size_t group_count, ...); -std::string regTrim(const std::string &src); -std::string speedCalc(double speed); -std::string getMD5(const std::string &data); -bool isIPv4(const std::string &address); -bool isIPv6(const std::string &address); -void urlParse(std::string &url, std::string &host, std::string &path, int &port, bool &isTLS); -void removeUTF8BOM(std::string &data); -int shortAssemble(unsigned short num_a, unsigned short num_b); -void shortDisassemble(int source, unsigned short &num_a, unsigned short &num_b); -std::string UTF8ToCodePoint(const std::string &data); -std::string GetEnv(const std::string &name); -std::string toLower(const std::string &str); -std::string toUpper(const std::string &str); -void ProcessEscapeChar(std::string &str); -void ProcessEscapeCharReverse(std::string &str); - -std::string fileGet(const std::string &path, bool scope_limit = false); -int fileWrite(const std::string &path, const std::string &content, bool overwrite); -bool fileExist(const std::string &path, bool scope_limit = false); -bool fileCopy(const std::string &source, const std::string &dest); -std::string fileToBase64(const std::string &filepath); -std::string fileGetMD5(const std::string &filepath); - -template -int operateFiles(const std::string &path, F &&op) -{ - DIR* dir = opendir(path.data()); - if(!dir) - return -1; - struct dirent* dp; - while((dp = readdir(dir)) != NULL) - { - if(strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) - { - if(op(dp->d_name)) - break; - } - } - closedir(dir); - return 0; -} - -static inline bool strFind(const std::string &str, const std::string &target) -{ - return str.find(target) != str.npos; -} - -static inline bool startsWith(const std::string &hay, const std::string &needle) -{ - return hay.substr(0, needle.length()) == needle; -} - -static inline bool endsWith(const std::string &hay, const std::string &needle) -{ - std::string::size_type hl = hay.length(), nl = needle.length(); - return hl >= nl && hay.substr(hl - nl, nl) == needle; -} - -template static inline void eraseElements(std::vector &target) -{ - target.clear(); - target.shrink_to_fit(); -} - -template static inline void eraseElements(T &target) -{ - T().swap(target); -} - -template static inline T to_number(const U &value, T def_value = T()) -{ - T retval = 0.0; - char c; - std::stringstream ss; - ss << value; - if(!(ss >> retval)) - return def_value; - else if(ss >> c) - return def_value; - else - return retval; -} - -int to_int(const std::string &str, int def_value = 0); - -static inline bool count_least(const std::string &hay, const char needle, size_t cnt) -{ - string_size pos = hay.find(needle); - while(pos != hay.npos) - { - cnt--; - if(!cnt) - return true; - pos = hay.find(needle, pos + 1); - } - return false; -} - -static inline char getLineBreak(const std::string &str) -{ - return count_least(str, '\n', 1) ? '\n' : '\r'; -} - -class tribool -{ -private: - - char _M_VALUE = 0; - -public: - - tribool() { clear(); } - - template tribool(const T &value) { set(value); } - - tribool(const tribool &value) { _M_VALUE = value._M_VALUE; } - - ~tribool() = default; - - tribool& operator=(const tribool &src) - { - _M_VALUE = src._M_VALUE; - return *this; - } - - template tribool& operator=(const T &value) - { - set(value); - return *this; - } - - operator bool() const { return _M_VALUE == 3; } - - bool is_undef() const { return _M_VALUE <= 1; } - - template tribool& define(const T &value) - { - if(_M_VALUE <= 1) - *this = value; - return *this; - } - - template tribool& parse(const T &value) - { - return define(value); - } - - tribool reverse() - { - if(_M_VALUE > 1) - _M_VALUE = _M_VALUE > 2 ? 2 : 3; - return *this; - } - - bool get(const bool &def_value = false) const - { - if(_M_VALUE <= 1) - return def_value; - return _M_VALUE == 3; - } - - std::string get_str() const - { - switch(_M_VALUE) - { - case 2: - return "false"; - case 3: - return "true"; - } - return "undef"; - } - - template bool set(const T &value) - { - _M_VALUE = (bool)value + 2; - return _M_VALUE > 2; - } - - bool set(const std::string &str) - { - switch(hash_(str)) - { - case "true"_hash: - case "1"_hash: - _M_VALUE = 3; - break; - case "false"_hash: - case "0"_hash: - _M_VALUE = 2; - break; - default: - if(to_int(str, 0) > 1) - _M_VALUE = 3; - else - _M_VALUE = 0; - break; - } - return _M_VALUE; - } - - void clear() { _M_VALUE = 0; } -}; - -#ifndef HAVE_TO_STRING -namespace std -{ -template std::string to_string(const T& n) -{ - std::ostringstream ss; - ss << n; - return ss.str(); -} -} -#endif // HAVE_TO_STRING - -static inline int md(const char *path) -{ -#ifdef _WIN32 - return mkdir(path); -#else - return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); -#endif // _WIN32 -} - -#ifdef _WIN32 -void StringToWstring(std::wstring& szDst, const std::string &str); -#endif // _WIN32 - -#endif // MISC_H_INCLUDED diff --git a/src/nodeinfo.h b/src/nodeinfo.h deleted file mode 100644 index 97db928..0000000 --- a/src/nodeinfo.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef NODEINFO_H_INCLUDED -#define NODEINFO_H_INCLUDED - -#include - -struct nodeInfo -{ - int linkType = -1; - int id = -1; - int groupID = -1; - std::string group; - std::string remarks; - std::string server; - int port = 0; - std::string proxyStr; -}; - -#endif // NODEINFO_H_INCLUDED diff --git a/src/nodemanip.cpp b/src/nodemanip.cpp deleted file mode 100644 index 99ded5e..0000000 --- a/src/nodemanip.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include -#include -#include -#include - -#include "nodeinfo.h" -#include "printout.h" -#include "logger.h" -#include "webget.h" -#include "speedtestutil.h" -#include "script_duktape.h" - -std::string override_conf_port; -bool ss_libev, ssr_libev; -extern int gCacheSubscription; - -void copyNodes(std::vector &source, std::vector &dest) -{ - std::move(source.begin(), source.end(), std::back_inserter(dest)); -} - -int addNodes(std::string link, std::vector &allNodes, int groupID, const std::string &proxy, string_array &exclude_remarks, string_array &include_remarks, string_array &stream_rules, string_array &time_rules, std::string &subInfo, bool authorized, string_map &request_headers) -{ - int linkType = -1; - std::vector nodes; - nodeInfo node; - std::string strSub, extra_headers, custom_group; - - // TODO: replace with startsWith if appropriate - link = replace_all_distinct(link, "\"", ""); - - /// script:filepath,arg1,arg2,... - if(startsWith(link, "script:") && authorized) /// process subscription with script - { - writeLog(0, "Found script link. Start running...", LOG_LEVEL_INFO); - string_array args = split(link.substr(7), ","); - if(args.size() >= 1) - { - std::string script = fileGet(args[0], false); - duk_context *ctx = duktape_init(); - defer(duk_destroy_heap(ctx);) - duktape_peval(ctx, script); - duk_get_global_string(ctx, "parse"); - for(size_t i = 1; i < args.size(); i++) - duk_push_string(ctx, trim(args[i]).c_str()); - if(duk_pcall(ctx, args.size() - 1) == 0) - link = duktape_get_res_str(ctx); - else - { - writeLog(0, "Error when trying to evaluate script:\n" + duktape_get_err_stack(ctx), LOG_LEVEL_ERROR); - duk_pop(ctx); /// pop err - } - } - } - - /// tag:group_name,link - if(startsWith(link, "tag:")) - { - string_size pos = link.find(","); - if(pos != link.npos) - { - custom_group = link.substr(4, pos - 4); - link.erase(0, pos + 1); - } - } - - if(link == "nullnode") - { - node.groupID = 0; - writeLog(0, "Adding node placeholder..."); - allNodes.emplace_back(std::move(node)); - return 0; - } - - writeLog(LOG_TYPE_INFO, "Received Link."); - if(startsWith(link, "https://t.me/socks") || startsWith(link, "tg://socks")) - linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - else if(startsWith(link, "https://t.me/http") || startsWith(link, "tg://http")) - linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - else if(isLink(link) || startsWith(link, "surge:///install-config")) - linkType = SPEEDTEST_MESSAGE_FOUNDSUB; - else if(startsWith(link, "Netch://")) - linkType = SPEEDTEST_MESSAGE_FOUNDNETCH; - else if(fileExist(link)) - linkType = SPEEDTEST_MESSAGE_FOUNDLOCAL; - - switch(linkType) - { - case SPEEDTEST_MESSAGE_FOUNDSUB: - writeLog(LOG_TYPE_INFO, "Downloading subscription data..."); - if(startsWith(link, "surge:///install-config")) //surge config link - link = UrlDecode(getUrlArg(link, "url")); - strSub = webGet(link, proxy, gCacheSubscription, &extra_headers, &request_headers); - /* - if(strSub.size() == 0) - { - //try to get it again with system proxy - writeLog(LOG_TYPE_WARN, "Cannot download subscription directly. Using system proxy."); - strProxy = getSystemProxy(); - if(strProxy != "") - { - strSub = webGet(link, strProxy); - } - else - writeLog(LOG_TYPE_WARN, "No system proxy is set. Skipping."); - } - */ - if(strSub.size()) - { - writeLog(LOG_TYPE_INFO, "Parsing subscription data..."); - if(explodeConfContent(strSub, override_conf_port, ss_libev, ssr_libev, nodes) == SPEEDTEST_ERROR_UNRECOGFILE) - { - writeLog(LOG_TYPE_ERROR, "Invalid subscription!"); - return -1; - } - if(startsWith(strSub, "ssd://")) - { - getSubInfoFromSSD(strSub, subInfo); - } - else - { - if(!getSubInfoFromHeader(extra_headers, subInfo)) - getSubInfoFromNodes(nodes, stream_rules, time_rules, subInfo); - } - filterNodes(nodes, exclude_remarks, include_remarks, groupID); - for(nodeInfo &x : nodes) - { - x.groupID = groupID; - if(custom_group.size()) - x.group = custom_group; - } - copyNodes(nodes, allNodes); - } - else - { - writeLog(LOG_TYPE_ERROR, "Cannot download subscription data."); - return -1; - } - break; - case SPEEDTEST_MESSAGE_FOUNDLOCAL: - if(!authorized) - return -1; - writeLog(LOG_TYPE_INFO, "Parsing configuration file data..."); - if(explodeConf(link, override_conf_port, ss_libev, ssr_libev, nodes) == SPEEDTEST_ERROR_UNRECOGFILE) - { - writeLog(LOG_TYPE_ERROR, "Invalid configuration file!"); - return -1; - } - if(startsWith(strSub, "ssd://")) - { - getSubInfoFromSSD(strSub, subInfo); - } - else - { - getSubInfoFromNodes(nodes, stream_rules, time_rules, subInfo); - } - filterNodes(nodes, exclude_remarks, include_remarks, groupID); - for(nodeInfo &x : nodes) - { - x.groupID = groupID; - if(custom_group.size()) - x.group = custom_group; - } - copyNodes(nodes, allNodes); - break; - default: - explode(link, ss_libev, ssr_libev, override_conf_port, node); - if(node.linkType == -1) - { - writeLog(LOG_TYPE_ERROR, "No valid link found."); - return -1; - } - node.groupID = groupID; - if(custom_group.size()) - node.group = custom_group; - allNodes.emplace_back(std::move(node)); - } - return 0; -} diff --git a/src/nodemanip.h b/src/nodemanip.h deleted file mode 100644 index 4a30659..0000000 --- a/src/nodemanip.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NODEMANIP_H_INCLUDED -#define NODEMANIP_H_INCLUDED - -#include -#include - -#include "nodeinfo.h" - -int addNodes(std::string link, std::vector &allNodes, int groupID, const std::string &proxy, string_array &exclude_remarks, string_array &include_remarks, string_array &stream_rules, string_array &time_rules, std::string &subInfo, bool authorized, string_map &request_headers); - -#endif // NODEMANIP_H_INCLUDED diff --git a/src/parser/config/proxy.h b/src/parser/config/proxy.h new file mode 100644 index 0000000..303e068 --- /dev/null +++ b/src/parser/config/proxy.h @@ -0,0 +1,94 @@ +#ifndef PROXY_H_INCLUDED +#define PROXY_H_INCLUDED + +#include + +#include "../../utils/tribool.h" + +using String = std::string; + +enum ProxyType +{ + Unknow, + Shadowsocks, + ShadowsocksR, + VMess, + Trojan, + Snell, + HTTP, + HTTPS, + SOCKS5 +}; + +inline String getProxyTypeName(int type) +{ + switch(type) + { + case ProxyType::Shadowsocks: + return "SS"; + case ProxyType::ShadowsocksR: + return "SSR"; + case ProxyType::VMess: + return "VMess"; + case ProxyType::Trojan: + return "Trojan"; + case ProxyType::Snell: + return "Snell"; + case ProxyType::HTTP: + return "HTTP"; + case ProxyType::HTTPS: + return "HTTPS"; + case ProxyType::SOCKS5: + return "SOCKS5"; + default: + return "Unknown"; + } +} + +struct Proxy +{ + int Type = ProxyType::Unknow; + uint32_t Id = 0; + uint32_t GroupId = 0; + String Group; + String Remark; + String Hostname; + uint16_t Port = 0; + + String Username; + String Password; + String EncryptMethod; + String Plugin; + String PluginOption; + String Protocol; + String ProtocolParam; + String OBFS; + String OBFSParam; + String UserId; + uint8_t AlterId = 0; + String TransferProtocol; + String FakeType; + bool TLSSecure = false; + + String Host; + String Path; + String Edge; + + String QUICSecure; + String QUICSecret; + + tribool UDP; + tribool TCPFastOpen; + tribool AllowInsecure; + tribool TLS13; +}; + +#define SS_DEFAULT_GROUP "SSProvider" +#define SSR_DEFAULT_GROUP "SSRProvider" +#define V2RAY_DEFAULT_GROUP "V2RayProvider" +#define SOCKS_DEFAULT_GROUP "SocksProvider" +#define HTTP_DEFAULT_GROUP "HTTPProvider" +#define TROJAN_DEFAULT_GROUP "TrojanProvider" +#define SNELL_DEFAULT_GROUP "SnellProvider" + +#endif // PROXY_H_INCLUDED diff --git a/src/parser/infoparser.cpp b/src/parser/infoparser.cpp new file mode 100644 index 0000000..baaa2db --- /dev/null +++ b/src/parser/infoparser.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include + +#include "../parser/config/proxy.h" +#include "../utils/base64/base64.h" +#include "../utils/rapidjson_extra.h" +#include "../utils/regexp.h" +#include "../utils/string.h" + +unsigned long long streamToInt(const std::string &stream) +{ + if(!stream.size()) + return 0; + double streamval = 1.0; + std::vector units = {"B", "KB", "MB", "GB", "TB", "PB", "EB"}; + size_t index = units.size(); + do + { + index--; + if(endsWith(stream, units[index])) + { + streamval = std::pow(1024, index) * to_number(stream.substr(0, stream.size() - units[index].size()), 0.0); + break; + } + } + while(index != 0); + return (unsigned long long)streamval; +} + +static inline double percentToDouble(const std::string &percent) +{ + return stof(percent.substr(0, percent.size() - 1)) / 100.0; +} + +time_t dateStringToTimestamp(std::string date) +{ + time_t rawtime; + time(&rawtime); + if(startsWith(date, "left=")) + { + time_t seconds_left = 0; + date.erase(0, 5); + if(endsWith(date, "d")) + { + date.erase(date.size() - 1); + seconds_left = to_number(date, 0.0) * 86400.0; + } + return rawtime + seconds_left; + } + else + { + struct tm *expire_time; + std::vector date_array = split(date, ":"); + if(date_array.size() != 6) + return 0; + + expire_time = localtime(&rawtime); + expire_time->tm_year = to_int(date_array[0], 1900) - 1900; + expire_time->tm_mon = to_int(date_array[1], 1) - 1; + expire_time->tm_mday = to_int(date_array[2]); + expire_time->tm_hour = to_int(date_array[3]); + expire_time->tm_min = to_int(date_array[4]); + expire_time->tm_sec = to_int(date_array[5]); + return mktime(expire_time); + } +} + +bool getSubInfoFromHeader(const std::string &header, std::string &result) +{ + std::string pattern = R"(^(?i:Subscription-UserInfo): (.*?)\s*?$)", retStr; + if(regFind(header, pattern)) + { + regGetMatch(header, pattern, 2, 0, &retStr); + if(retStr.size()) + { + result = retStr; + return true; + } + } + return false; +} + +bool getSubInfoFromNodes(const std::vector &nodes, const string_array &stream_rules, const string_array &time_rules, std::string &result) +{ + std::string remarks, pattern, target, stream_info, time_info, retStr; + string_size spos; + + for(const Proxy &x : nodes) + { + remarks = x.Remark; + if(!stream_info.size()) + { + for(const std::string &y : stream_rules) + { + spos = y.rfind("|"); + if(spos == y.npos) + continue; + pattern = y.substr(0, spos); + target = y.substr(spos + 1); + if(regMatch(remarks, pattern)) + { + retStr = regReplace(remarks, pattern, target); + if(retStr != remarks) + { + stream_info = retStr; + break; + } + } + else + continue; + } + } + + remarks = x.Remark; + if(!time_info.size()) + { + for(const std::string &y : time_rules) + { + spos = y.rfind("|"); + if(spos == y.npos) + continue; + pattern = y.substr(0, spos); + target = y.substr(spos + 1); + if(regMatch(remarks, pattern)) + { + retStr = regReplace(remarks, pattern, target); + if(retStr != remarks) + { + time_info = retStr; + break; + } + } + else + continue; + } + } + + if(stream_info.size() && time_info.size()) + break; + } + + if(!stream_info.size() && !time_info.size()) + return false; + + //calculate how much stream left + unsigned long long total = 0, left, used = 0, expire = 0; + std::string total_str = getUrlArg(stream_info, "total"), left_str = getUrlArg(stream_info, "left"), used_str = getUrlArg(stream_info, "used"); + if(strFind(total_str, "%")) + { + if(used_str.size()) + { + used = streamToInt(used_str); + total = used / (1 - percentToDouble(total_str)); + } + else if(left_str.size()) + { + left = streamToInt(left_str); + total = left / percentToDouble(total_str); + used = total - left; + } + } + else + { + total = streamToInt(total_str); + if(used_str.size()) + { + used = streamToInt(used_str); + } + else if(left_str.size()) + { + left = streamToInt(left_str); + used = total - left; + } + } + + result = "upload=0; download=" + std::to_string(used) + "; total=" + std::to_string(total) + ";"; + + //calculate expire time + expire = dateStringToTimestamp(time_info); + if(expire) + result += " expire=" + std::to_string(expire) + ";"; + + return true; +} + +bool getSubInfoFromSSD(const std::string &sub, std::string &result) +{ + rapidjson::Document json; + json.Parse(urlSafeBase64Decode(sub.substr(6)).data()); + if(json.HasParseError()) + return false; + + std::string used_str = GetMember(json, "traffic_used"), total_str = GetMember(json, "traffic_total"), expire_str = GetMember(json, "expiry"); + if(!used_str.size() || !total_str.size()) + return false; + unsigned long long used = stod(used_str) * std::pow(1024, 3), total = stod(total_str) * std::pow(1024, 3), expire; + result = "upload=0; download=" + std::to_string(used) + "; total=" + std::to_string(total) + ";"; + + expire = dateStringToTimestamp(regReplace(expire_str, "(\\d+)-(\\d+)-(\\d+) (.*)", "$1:$2:$3:$4")); + if(expire) + result += " expire=" + std::to_string(expire) + ";"; + + return true; +} diff --git a/src/parser/infoparser.h b/src/parser/infoparser.h new file mode 100644 index 0000000..96d0fdd --- /dev/null +++ b/src/parser/infoparser.h @@ -0,0 +1,15 @@ +#ifndef INFOPARSER_H_INCLUDED +#define INFOPARSER_H_INCLUDED + +#include + +#include "../utils/string.h" +#include "config/proxy.h" + +bool getSubInfoFromHeader(const std::string &header, std::string &result); +bool getSubInfoFromNodes(const std::vector &nodes, const string_array &stream_rules, const string_array &time_rules, std::string &result); +bool getSubInfoFromSSD(const std::string &sub, std::string &result); +unsigned long long streamToInt(const std::string &stream); + + +#endif // INFOPARSER_H_INCLUDED diff --git a/src/speedtestutil.cpp b/src/parser/subparser.cpp similarity index 50% rename from src/speedtestutil.cpp rename to src/parser/subparser.cpp index 0970476..4b7cdaa 100644 --- a/src/speedtestutil.cpp +++ b/src/parser/subparser.cpp @@ -1,20 +1,17 @@ -#include -#include -#include -#include +#include +#include -#define PCRE2_CODE_UNIT_WIDTH 8 -#include - -#include "yamlcpp_extra.h" -#include "misc.h" -#include "printout.h" -#include "logger.h" -#include "speedtestutil.h" -#include "webget.h" -#include "rapidjson_extra.h" -#include "ini_reader.h" -#include "string_hash.h" +#include "../utils/base64/base64.h" +#include "../utils/ini_reader/ini_reader.h" +#include "../utils/network.h" +#include "../utils/rapidjson_extra.h" +#include "../utils/regexp.h" +#include "../utils/string.h" +#include "../utils/string_hash.h" +#include "../utils/urlencode.h" +#include "../utils/yamlcpp_extra.h" +#include "config/proxy.h" +#include "subparser.h" using namespace rapidjson; using namespace YAML; @@ -27,30 +24,117 @@ std::string modSSMD5 = "f7653207090ce3389115e9c88541afe0"; //remake from speedtestutil -void explodeVmess(std::string vmess, const std::string &custom_port, nodeInfo &node) +void commonConstruct(Proxy &node, ProxyType type, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const tribool &udp, const tribool &tfo, const tribool &scv, const tribool &tls13) +{ + node.Type = type; + node.Group = group; + node.Remark = remarks; + node.Hostname = server; + node.Port = to_int(port); + node.UDP = udp; + node.TCPFastOpen = tfo; + node.AllowInsecure = scv; + node.TLS13 = tls13; +} + +void vmessConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &id, const std::string &aid, const std::string &net, const std::string &cipher, const std::string &path, const std::string &host, const std::string &edge, const std::string &tls, tribool udp, tribool tfo, tribool scv, tribool tls13) +{ + commonConstruct(node, ProxyType::VMess, group, remarks, add, port, udp, tfo, scv, tls13); + node.UserId = id.empty() ? "00000000-0000-0000-0000-000000000000" : id; + node.AlterId = to_int(aid); + node.EncryptMethod = cipher; + node.TransferProtocol = net.empty() ? "tcp" : net; + node.Host = host.empty() ? add.data() : trim(host); + node.Edge = edge; + + if(net == "ws" || net == "http") + node.Path = path.empty() ? "/" : trim(path); + else + { + if(net == "quic") + { + node.QUICSecure = host; + node.QUICSecret = path; + } + node.FakeType = type; + } +} + +void ssrConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &protocol, const std::string &method, const std::string &obfs, const std::string &password, const std::string &obfsparam, const std::string &protoparam, tribool udp, tribool tfo, tribool scv) +{ + commonConstruct(node, ProxyType::ShadowsocksR, group, remarks, server, port, udp, tfo, scv, tribool()); + node.Password = password; + node.EncryptMethod = method; + node.Protocol = protocol; + node.ProtocolParam = protoparam; + node.OBFS = obfs; + node.OBFSParam = obfsparam; +} + +void ssConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &method, const std::string &plugin, const std::string &pluginopts, tribool udp, tribool tfo, tribool scv, tribool tls13) +{ + commonConstruct(node, ProxyType::Shadowsocks, group, remarks, server, port, udp, tfo, scv, tls13); + node.Password = password; + node.EncryptMethod = method; + node.Plugin = plugin; + node.PluginOption = pluginopts; +} + +void socksConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &username, const std::string &password, tribool udp, tribool tfo, tribool scv) +{ + commonConstruct(node, ProxyType::SOCKS5, group, remarks, server, port, udp, tfo, scv, tribool()); + node.Username = username; + node.Password = password; +} + +void httpConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &username, const std::string &password, bool tls, tribool tfo, tribool scv, tribool tls13) +{ + commonConstruct(node, tls ? ProxyType::HTTPS : ProxyType::HTTP, group, remarks, server, port, tribool(), tfo, scv, tls13); + node.Username = username; + node.Password = password; + node.TLSSecure = tls; +} + +void trojanConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &host, bool tlssecure, tribool udp, tribool tfo, tribool scv, tribool tls13) +{ + commonConstruct(node, ProxyType::Trojan, group, remarks, server, port, udp, tfo, scv, tls13); + node.Password = password; + node.Host = host; + node.TLSSecure = tlssecure; +} + +void snellConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &obfs, const std::string &host, tribool udp, tribool tfo, tribool scv) +{ + commonConstruct(node, ProxyType::Snell, group, remarks, server, port, udp, tfo, scv, tribool()); + node.Password = password; + node.OBFS = obfs; + node.Host = host; +} + +void explodeVmess(std::string vmess, Proxy &node) { std::string version, ps, add, port, type, id, aid, net, path, host, tls; Document jsondata; std::vector vArray; if(regMatch(vmess, "vmess://(.*?)@(.*)")) { - explodeStdVMess(vmess, custom_port, node); + explodeStdVMess(vmess, node); return; } else if(regMatch(vmess, "vmess://(.*?)\\?(.*)")) //shadowrocket style link { - explodeShadowrocket(vmess, custom_port, node); + explodeShadowrocket(vmess, node); return; } else if(regMatch(vmess, "vmess1://(.*?)\\?(.*)")) //kitsunebi style link { - explodeKitsunebi(vmess, custom_port, node); + explodeKitsunebi(vmess, node); return; } - vmess = urlsafe_base64_decode(regReplace(vmess, "(vmess|vmess1)://", "")); + vmess = urlSafeBase64Decode(regReplace(vmess, "(vmess|vmess1)://", "")); if(regMatch(vmess, "(.*?) = (.*)")) { - explodeQuan(vmess, custom_port, node); + explodeQuan(vmess, node); return; } jsondata.Parse(vmess.data()); @@ -62,7 +146,7 @@ void explodeVmess(std::string vmess, const std::string &custom_port, nodeInfo &n GetMember(jsondata, "ps", ps); GetMember(jsondata, "add", add); - port = custom_port.size() ? custom_port : GetMember(jsondata, "port"); + port = GetMember(jsondata, "port"); if(port == "0") return; GetMember(jsondata, "type", type); @@ -91,22 +175,19 @@ void explodeVmess(std::string vmess, const std::string &custom_port, nodeInfo &n } add = trim(add); - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.remarks = ps; - node.server = add; - node.port = to_int(port, 1); - node.proxyStr = vmessConstruct(node.group, ps, add, port, type, id, aid, net, "auto", path, host, "", tls); + + vmessConstruct(node, V2RAY_DEFAULT_GROUP, ps, add, port, type, id, aid, net, "auto", path, host, "", tls); } -void explodeVmessConf(std::string content, const std::string &custom_port, bool libev, std::vector &nodes) +void explodeVmessConf(std::string content, std::vector &nodes) { - nodeInfo node; + Proxy node; Document json; rapidjson::Value nodejson, settings; std::string group, ps, add, port, type, id, aid, net, path, host, edge, tls, cipher, subid; tribool udp, tfo, scv; - int configType, index = nodes.size(); + int configType; + uint32_t index = nodes.size(); std::map subdata; std::map::iterator iter; std::string streamset = "streamSettings", tcpset = "tcpSettings", wsset = "wsSettings"; @@ -125,7 +206,7 @@ void explodeVmessConf(std::string content, const std::string &custom_port, bool { nodejson = json["outbounds"][0]; add = GetMember(nodejson["settings"]["vnext"][0], "address"); - port = custom_port.size() ? custom_port : GetMember(nodejson["settings"]["vnext"][0], "port"); + port = GetMember(nodejson["settings"]["vnext"][0], "port"); if(port == "0") return; if(nodejson["settings"]["vnext"][0]["users"].Size()) @@ -173,29 +254,24 @@ void explodeVmessConf(std::string content, const std::string &custom_port, bool } } } - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.remarks = add + ":" + port; - node.server = add; - node.port = to_int(port, 1); - node.proxyStr = vmessConstruct(node.group, node.remarks, add, port, type, id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, add + ":" + port, add, port, type, id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); nodes.emplace_back(std::move(node)); - node = nodeInfo(); } return; } } catch(std::exception & e) { - writeLog(0, "VMessConf parser throws an error. Leaving...", LOG_LEVEL_WARNING); - return; + //writeLog(0, "VMessConf parser throws an error. Leaving...", LOG_LEVEL_WARNING); + //return; //ignore + throw; } //read all subscribe remark as group name - for(unsigned int i = 0; i < json["subItem"].Size(); i++) + for(uint32_t i = 0; i < json["subItem"].Size(); i++) subdata.insert(std::pair(json["subItem"][i]["id"].GetString(), json["subItem"][i]["remarks"].GetString())); - for(unsigned int i = 0; i < json["vmess"].Size(); i++) + for(uint32_t i = 0; i < json["vmess"].Size(); i++) { if(json["vmess"][i]["address"].IsNull() || json["vmess"][i]["port"].IsNull() || json["vmess"][i]["id"].IsNull()) continue; @@ -203,7 +279,7 @@ void explodeVmessConf(std::string content, const std::string &custom_port, bool //common info json["vmess"][i]["remarks"] >> ps; json["vmess"][i]["address"] >> add; - port = custom_port.size() ? custom_port : GetMember(json["vmess"][i], "port"); + port = GetMember(json["vmess"][i], "port"); if(port == "0") continue; json["vmess"][i]["subid"] >> subid; @@ -230,95 +306,78 @@ void explodeVmessConf(std::string content, const std::string &custom_port, bool json["vmess"][i]["requestHost"] >> host; json["vmess"][i]["streamSecurity"] >> tls; json["vmess"][i]["security"] >> cipher; - group = V2RAY_DEFAULT_GROUP; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.proxyStr = vmessConstruct(group, ps, add, port, type, id, aid, net, cipher, path, host, "", tls, udp, tfo, scv); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, ps, add, port, type, id, aid, net, cipher, path, host, "", tls, udp, tfo, scv); break; case 3: //ss config json["vmess"][i]["id"] >> id; json["vmess"][i]["security"] >> cipher; - group = SS_DEFAULT_GROUP; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(group, ps, add, port, id, cipher, "", "", libev, udp, tfo, scv); + ssConstruct(node, SS_DEFAULT_GROUP, ps, add, port, id, cipher, "", "", udp, tfo, scv); break; case 4: //socks config - group = SOCKS_DEFAULT_GROUP; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.proxyStr = socksConstruct(group, ps, add, port, "", "", udp, tfo, scv); + socksConstruct(node, SOCKS_DEFAULT_GROUP, ps, add, port, "", "", udp, tfo, scv); break; default: continue; } - - node.group = group; - node.remarks = ps; - node.id = index; - node.server = add; - node.port = to_int(port, 1); + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); + index++; } return; } -void explodeSS(std::string ss, bool libev, const std::string &custom_port, nodeInfo &node) +void explodeSS(std::string ss, Proxy &node) { std::string ps, password, method, server, port, plugins, plugin, pluginopts, addition, group = SS_DEFAULT_GROUP, secret; //std::vector args, secret; - ss = replace_all_distinct(ss.substr(5), "/?", "?"); + ss = replaceAllDistinct(ss.substr(5), "/?", "?"); if(strFind(ss, "#")) { - ps = UrlDecode(ss.substr(ss.find("#") + 1)); + ps = urlDecode(ss.substr(ss.find("#") + 1)); ss.erase(ss.find("#")); } if(strFind(ss, "?")) { addition = ss.substr(ss.find("?") + 1); - plugins = UrlDecode(getUrlArg(addition, "plugin")); + plugins = urlDecode(getUrlArg(addition, "plugin")); plugin = plugins.substr(0, plugins.find(";")); pluginopts = plugins.substr(plugins.find(";") + 1); if(getUrlArg(addition, "group").size()) - group = urlsafe_base64_decode(getUrlArg(addition, "group")); + group = urlSafeBase64Decode(getUrlArg(addition, "group")); ss.erase(ss.find("?")); } if(strFind(ss, "@")) { - if(regGetMatch(ss, "(.*?)@(.*):(.*)", 4, 0, &secret, &server, &port)) + if(regGetMatch(ss, "(\\S+?)@(\\S+):(\\d+)", 4, 0, &secret, &server, &port)) return; - if(regGetMatch(urlsafe_base64_decode(secret), "(.*?):(.*)", 3, 0, &method, &password)) + if(regGetMatch(urlSafeBase64Decode(secret), "(\\S+?):(\\S+)", 3, 0, &method, &password)) return; } else { - if(regGetMatch(urlsafe_base64_decode(ss), "(.*?):(.*)@(.*):(.*)", 5, 0, &method, &password, &server, &port)) + if(regGetMatch(urlSafeBase64Decode(ss), "(\\S+?):(\\S+)@(\\S+):(\\d+)", 5, 0, &method, &password, &server, &port)) return; } - if(custom_port.size()) - port = custom_port; if(port == "0") return; if(ps.empty()) ps = server + ":" + port; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.group = group; - node.remarks = ps; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = ssConstruct(group, ps, server, port, password, method, plugin, pluginopts, libev); + ssConstruct(node, group, ps, server, port, password, method, plugin, pluginopts); } -void explodeSSD(std::string link, bool libev, const std::string &custom_port, std::vector &nodes) +void explodeSSD(std::string link, std::vector &nodes) { Document jsondata; - nodeInfo node; - unsigned int index = nodes.size(), listType = 0, listCount = 0; + Proxy node; + uint32_t index = nodes.size(), listType = 0, listCount = 0; std::string group, port, method, password, server, remarks; std::string plugin, pluginopts; - std::map node_map; + std::map node_map; - link = urlsafe_base64_decode(link.substr(6)); + link = urlSafeBase64Decode(link.substr(6)); jsondata.Parse(link.c_str()); if(jsondata.HasParseError()) return; @@ -335,7 +394,7 @@ void explodeSSD(std::string link, bool libev, const std::string &custom_port, st { listType = 1; listCount = jsondata["servers"].MemberCount(); - unsigned int node_index = 0; + uint32_t node_index = 0; for(rapidjson::Value::MemberIterator iter = jsondata["servers"].MemberBegin(); iter != jsondata["servers"].MemberEnd(); iter++) { node_map.emplace(node_index, iter->name.GetString()); @@ -346,14 +405,14 @@ void explodeSSD(std::string link, bool libev, const std::string &custom_port, st return; rapidjson::Value singlenode; - for(unsigned int i = 0; i < listCount; i++) + for(uint32_t i = 0; i < listCount; i++) { //get default info - GetMember(jsondata, "port", port); - GetMember(jsondata, "encryption", method); - GetMember(jsondata, "password", password); - GetMember(jsondata, "plugin", plugin); - GetMember(jsondata, "plugin_options", pluginopts); + port = GetMember(jsondata, "port"); + method = GetMember(jsondata, "encryption"); + password = GetMember(jsondata, "password"); + plugin = GetMember(jsondata, "plugin"); + pluginopts = GetMember(jsondata, "plugin_options"); //get server-specific info switch(listType) @@ -375,32 +434,25 @@ void explodeSSD(std::string link, bool libev, const std::string &custom_port, st GetMember(singlenode, "plugin", plugin); GetMember(singlenode, "plugin_options", pluginopts); - if(custom_port.size()) - port = custom_port; if(port == "0") continue; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.group = group; - node.remarks = remarks; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = ssConstruct(group, remarks, server, port, password, method, plugin, pluginopts, libev); - node.id = index; + ssConstruct(node, group, remarks, server, port, password, method, plugin, pluginopts); + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); index++; } return; } -void explodeSSAndroid(std::string ss, bool libev, const std::string &custom_port, std::vector &nodes) +void explodeSSAndroid(std::string ss, std::vector &nodes) { std::string ps, password, method, server, port, group = SS_DEFAULT_GROUP; std::string plugin, pluginopts; Document json; - nodeInfo node; + Proxy node; int index = nodes.size(); //first add some extra data before parsing ss = "{\"nodes\":" + ss + "}"; @@ -408,13 +460,13 @@ void explodeSSAndroid(std::string ss, bool libev, const std::string &custom_port if(json.HasParseError()) return; - for(unsigned int i = 0; i < json["nodes"].Size(); i++) + for(uint32_t i = 0; i < json["nodes"].Size(); i++) { server = GetMember(json["nodes"][i], "server"); if(server.empty()) continue; ps = GetMember(json["nodes"][i], "remarks"); - port = custom_port.size() ? custom_port : GetMember(json["nodes"][i], "server_port"); + port = GetMember(json["nodes"][i], "server_port"); if(port == "0") continue; if(ps.empty()) @@ -424,22 +476,17 @@ void explodeSSAndroid(std::string ss, bool libev, const std::string &custom_port plugin = GetMember(json["nodes"][i], "plugin"); pluginopts = GetMember(json["nodes"][i], "plugin_opts"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.id = index; - node.group = group; - node.remarks = ps; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = ssConstruct(group, ps, server, port, password, method, plugin, pluginopts, libev); + ssConstruct(node, group, ps, server, port, password, method, plugin, pluginopts); + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); index++; } } -void explodeSSConf(std::string content, const std::string &custom_port, bool libev, std::vector &nodes) +void explodeSSConf(std::string content, std::vector &nodes) { - nodeInfo node; + Proxy node; Document json; std::string ps, password, method, server, port, plugin, pluginopts, group = SS_DEFAULT_GROUP; int index = nodes.size(); @@ -452,10 +499,10 @@ void explodeSSConf(std::string content, const std::string &custom_port, bool lib return; GetMember(json, "remarks", group); - for(unsigned int i = 0; i < json[section].Size(); i++) + for(uint32_t i = 0; i < json[section].Size(); i++) { ps = GetMember(json[section][i], "remarks"); - port = custom_port.size() ? custom_port : GetMember(json[section][i], "server_port"); + port = GetMember(json[section][i], "server_port"); if(port == "0") continue; if(ps.empty()) @@ -467,42 +514,35 @@ void explodeSSConf(std::string content, const std::string &custom_port, bool lib plugin = GetMember(json[section][i], "plugin"); pluginopts = GetMember(json[section][i], "plugin_opts"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.group = group; - node.remarks = ps; - node.id = index; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = ssConstruct(group, ps, server, port, password, method, plugin, pluginopts, libev); + node.Id = index; + ssConstruct(node, group, ps, server, port, password, method, plugin, pluginopts); nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); index++; } return; } -void explodeSSR(std::string ssr, bool ss_libev, bool ssr_libev, const std::string &custom_port, nodeInfo &node) +void explodeSSR(std::string ssr, Proxy &node) { std::string strobfs; std::string remarks, group, server, port, method, password, protocol, protoparam, obfs, obfsparam, remarks_base64; - ssr = replace_all_distinct(ssr.substr(6), "\r", ""); - ssr = urlsafe_base64_decode(ssr); + ssr = replaceAllDistinct(ssr.substr(6), "\r", ""); + ssr = urlSafeBase64Decode(ssr); if(strFind(ssr, "/?")) { strobfs = ssr.substr(ssr.find("/?") + 2); ssr = ssr.substr(0, ssr.find("/?")); - group = urlsafe_base64_decode(getUrlArg(strobfs, "group")); - remarks = urlsafe_base64_decode(getUrlArg(strobfs, "remarks")); - remarks_base64 = urlsafe_base64_reverse(getUrlArg(strobfs, "remarks")); - obfsparam = regReplace(urlsafe_base64_decode(getUrlArg(strobfs, "obfsparam")), "\\s", ""); - protoparam = regReplace(urlsafe_base64_decode(getUrlArg(strobfs, "protoparam")), "\\s", ""); + group = urlSafeBase64Decode(getUrlArg(strobfs, "group")); + remarks = urlSafeBase64Decode(getUrlArg(strobfs, "remarks")); + remarks_base64 = urlSafeBase64Reverse(getUrlArg(strobfs, "remarks")); + obfsparam = regReplace(urlSafeBase64Decode(getUrlArg(strobfs, "obfsparam")), "\\s", ""); + protoparam = regReplace(urlSafeBase64Decode(getUrlArg(strobfs, "protoparam")), "\\s", ""); } - if(regGetMatch(ssr, "(.*):(.*?):(.*?):(.*?):(.*?):(.*)", 7, 0, &server, &port, &protocol, &method, &obfs, &password)) + if(regGetMatch(ssr, "(\\S+):(\\d+?):(\\S+?):(\\S+?):(\\S+?):(\\S+)", 7, 0, &server, &port, &protocol, &method, &obfs, &password)) return; - password = urlsafe_base64_decode(password); - if(custom_port.size()) - port = custom_port; + password = urlSafeBase64Decode(password); if(port == "0") return; @@ -511,28 +551,22 @@ void explodeSSR(std::string ssr, bool ss_libev, bool ssr_libev, const std::strin if(remarks.empty()) { remarks = server + ":" + port; - remarks_base64 = base64_encode(remarks); + remarks_base64 = base64Encode(remarks); } - node.group = group; - node.remarks = remarks; - node.server = server; - node.port = to_int(port, 1); if(find(ss_ciphers.begin(), ss_ciphers.end(), method) != ss_ciphers.end() && (obfs.empty() || obfs == "plain") && (protocol.empty() || protocol == "origin")) { - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(group, remarks, server, port, password, method, "", "", ss_libev); + ssConstruct(node, group, remarks, server, port, password, method, "", ""); } else { - node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.proxyStr = ssrConstruct(group, remarks, remarks_base64, server, port, protocol, method, obfs, password, obfsparam, protoparam, ssr_libev); + ssrConstruct(node, group, remarks, remarks_base64, server, port, protocol, method, obfs, password, obfsparam, protoparam); } } -void explodeSSRConf(std::string content, const std::string &custom_port, bool ss_libev, bool ssr_libev, std::vector &nodes) +void explodeSSRConf(std::string content, std::vector &nodes) { - nodeInfo node; + Proxy node; Document json; std::string remarks, remarks_base64, group, server, port, method, password, protocol, protoparam, obfs, obfsparam, plugin, pluginopts; int index = nodes.size(); @@ -545,9 +579,7 @@ void explodeSSRConf(std::string content, const std::string &custom_port, bool ss { server = GetMember(json, "server"); port = GetMember(json, "server_port"); - node.remarks = server + ":" + port; - node.server = server; - node.port = to_int(port, 1); + remarks = server + ":" + port; method = GetMember(json, "method"); obfs = GetMember(json, "obfs"); protocol = GetMember(json, "protocol"); @@ -555,31 +587,27 @@ void explodeSSRConf(std::string content, const std::string &custom_port, bool ss { plugin = GetMember(json, "plugin"); pluginopts = GetMember(json, "plugin_opts"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(node.group, node.remarks, server, port, password, method, plugin, pluginopts, ss_libev); + ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts); } else { protoparam = GetMember(json, "protocol_param"); obfsparam = GetMember(json, "obfs_param"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.group = SSR_DEFAULT_GROUP; - node.proxyStr = ssrConstruct(node.group, node.remarks, base64_encode(node.remarks), server, port, protocol, method, obfs, password, obfsparam, protoparam, ssr_libev); + ssrConstruct(node, SSR_DEFAULT_GROUP, remarks, server, port, protocol, method, obfs, password, obfsparam, protoparam); } nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); return; } - for(unsigned int i = 0; i < json["configs"].Size(); i++) + for(uint32_t i = 0; i < json["configs"].Size(); i++) { group = GetMember(json["configs"][i], "group"); if(group.empty()) group = SSR_DEFAULT_GROUP; remarks = GetMember(json["configs"][i], "remarks"); server = GetMember(json["configs"][i], "server"); - port = custom_port.size() ? custom_port : GetMember(json["configs"][i], "server_port"); + port = GetMember(json["configs"][i], "server_port"); if(port == "0") continue; if(remarks.empty()) @@ -594,21 +622,16 @@ void explodeSSRConf(std::string content, const std::string &custom_port, bool ss obfs = GetMember(json["configs"][i], "obfs"); obfsparam = GetMember(json["configs"][i], "obfsparam"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.group = group; - node.remarks = remarks; - node.id = index; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = ssrConstruct(group, remarks, remarks_base64, server, port, protocol, method, obfs, password, obfsparam, protoparam, ssr_libev); + ssrConstruct(node, group, remarks, remarks_base64, server, port, protocol, method, obfs, password, obfsparam, protoparam); + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); index++; } return; } -void explodeSocks(std::string link, const std::string &custom_port, nodeInfo &node) +void explodeSocks(std::string link, Proxy &node) { std::string group, remarks, server, port, username, password; if(strFind(link, "socks://")) //v2rayn socks link @@ -616,10 +639,10 @@ void explodeSocks(std::string link, const std::string &custom_port, nodeInfo &no std::vector arguments; if(strFind(link, "#")) { - remarks = UrlDecode(link.substr(link.find("#") + 1)); + remarks = urlDecode(link.substr(link.find("#") + 1)); link.erase(link.find("#")); } - link = urlsafe_base64_decode(link.substr(8)); + link = urlSafeBase64Decode(link.substr(8)); arguments = split(link, ":"); if(arguments.size() < 2) return; @@ -630,56 +653,42 @@ void explodeSocks(std::string link, const std::string &custom_port, nodeInfo &no { server = getUrlArg(link, "server"); port = getUrlArg(link, "port"); - username = UrlDecode(getUrlArg(link, "user")); - password = UrlDecode(getUrlArg(link, "pass")); - remarks = UrlDecode(getUrlArg(link, "remarks")); - group = UrlDecode(getUrlArg(link, "group")); + username = urlDecode(getUrlArg(link, "user")); + password = urlDecode(getUrlArg(link, "pass")); + remarks = urlDecode(getUrlArg(link, "remarks")); + group = urlDecode(getUrlArg(link, "group")); } if(group.empty()) group = SOCKS_DEFAULT_GROUP; if(remarks.empty()) remarks = server + ":" + port; - if(custom_port.size()) - port = custom_port; if(port == "0") return; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.group = group; - node.remarks = remarks; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = socksConstruct(group, remarks, server, port, username, password); + socksConstruct(node, group, remarks, server, port, username, password); } -void explodeHTTP(const std::string &link, const std::string &custom_port, nodeInfo &node) +void explodeHTTP(const std::string &link, Proxy &node) { std::string group, remarks, server, port, username, password; server = getUrlArg(link, "server"); port = getUrlArg(link, "port"); - username = UrlDecode(getUrlArg(link, "user")); - password = UrlDecode(getUrlArg(link, "pass")); - remarks = UrlDecode(getUrlArg(link, "remarks")); - group = UrlDecode(getUrlArg(link, "group")); + username = urlDecode(getUrlArg(link, "user")); + password = urlDecode(getUrlArg(link, "pass")); + remarks = urlDecode(getUrlArg(link, "remarks")); + group = urlDecode(getUrlArg(link, "group")); if(group.empty()) group = HTTP_DEFAULT_GROUP; if(remarks.empty()) remarks = server + ":" + port; - if(custom_port.size()) - port = custom_port; if(port == "0") return; - node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - node.group = group; - node.remarks = remarks; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = httpConstruct(group, remarks, server, port, username, password, strFind(link, "/https")); + httpConstruct(node, group, remarks, server, port, username, password, strFind(link, "/https")); } -void explodeHTTPSub(std::string link, const std::string &custom_port, nodeInfo &node) +void explodeHTTPSub(std::string link, Proxy &node) { std::string group, remarks, server, port, username, password; std::string addition; @@ -689,11 +698,11 @@ void explodeHTTPSub(std::string link, const std::string &custom_port, nodeInfo & { addition = link.substr(pos + 1); link.erase(pos); - remarks = UrlDecode(getUrlArg(addition, "remarks")); - group = UrlDecode(getUrlArg(addition, "group")); + remarks = urlDecode(getUrlArg(addition, "remarks")); + group = urlDecode(getUrlArg(addition, "group")); } link.erase(0, link.find("://") + 3); - link = urlsafe_base64_decode(link); + link = urlSafeBase64Decode(link); if(strFind(link, "@")) { if(regGetMatch(link, "(.*?):(.*?)@(.*):(.*)", 5, 0, &username, &password, &server, &port)) @@ -709,20 +718,13 @@ void explodeHTTPSub(std::string link, const std::string &custom_port, nodeInfo & group = HTTP_DEFAULT_GROUP; if(remarks.empty()) remarks = server + ":" + port; - if(custom_port.size()) - port = custom_port; if(port == "0") return; - node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - node.group = group; - node.remarks = remarks; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = httpConstruct(group, remarks, server, port, username, password, tls); + httpConstruct(node, group, remarks, server, port, username, password, tls); } -void explodeTrojan(std::string trojan, const std::string &custom_port, nodeInfo &node) +void explodeTrojan(std::string trojan, Proxy &node) { std::string server, port, psk, addition, group, remark, host; tribool tfo, scv; @@ -731,7 +733,7 @@ void explodeTrojan(std::string trojan, const std::string &custom_port, nodeInfo if(pos != trojan.npos) { - remark = UrlDecode(trojan.substr(pos + 1)); + remark = urlDecode(trojan.substr(pos + 1)); trojan.erase(pos); } pos = trojan.find("?"); @@ -743,15 +745,13 @@ void explodeTrojan(std::string trojan, const std::string &custom_port, nodeInfo if(regGetMatch(trojan, "(.*?)@(.*):(.*)", 4, 0, &psk, &server, &port)) return; - if(custom_port.size()) - port = custom_port; if(port == "0") return; host = getUrlArg(addition, "peer"); tfo = getUrlArg(addition, "tfo"); scv = getUrlArg(addition, "allowInsecure"); - group = UrlDecode(getUrlArg(addition, "group")); + group = urlDecode(getUrlArg(addition, "group")); if(remark.empty()) remark = server + ":" + port; @@ -760,15 +760,10 @@ void explodeTrojan(std::string trojan, const std::string &custom_port, nodeInfo if(group.empty()) group = TROJAN_DEFAULT_GROUP; - node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; - node.group = group; - node.remarks = remark; - node.server = server; - node.port = to_int(port, 1); - node.proxyStr = trojanConstruct(group, remark, server, port, psk, host, true, tribool(), tfo, scv); + trojanConstruct(node, group, remark, server, port, psk, host, true, tribool(), tfo, scv); } -void explodeQuan(const std::string &quan, const std::string &custom_port, nodeInfo &node) +void explodeQuan(const std::string &quan, Proxy &node) { std::string strTemp, itemName, itemVal; std::string group = V2RAY_DEFAULT_GROUP, ps, add, port, cipher, type = "none", id, aid = "0", net = "tcp", path, host, edge, tls; @@ -782,14 +777,14 @@ void explodeQuan(const std::string &quan, const std::string &custom_port, nodeIn return; ps = trim(configs[0]); add = trim(configs[2]); - port = custom_port.size() ? custom_port : trim(configs[3]); + port = trim(configs[3]); if(port == "0") return; cipher = trim(configs[4]); - id = trim(replace_all_distinct(configs[5], "\"", "")); + id = trim(replaceAllDistinct(configs[5], "\"", "")); //read link - for(unsigned int i = 6; i < configs.size(); i++) + for(uint32_t i = 6; i < configs.size(); i++) { vArray = split(configs[i], "="); if(vArray.size() < 2) @@ -798,45 +793,49 @@ void explodeQuan(const std::string &quan, const std::string &custom_port, nodeIn itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "group"_hash: group = itemVal; break; - case "over-tls"_hash: tls = itemVal == "true" ? "tls" : ""; break; - case "tls-host"_hash: host = itemVal; break; - case "obfs-path"_hash: path = replace_all_distinct(itemVal, "\"", ""); break; - case "obfs-header"_hash: - headers = split(replace_all_distinct(replace_all_distinct(itemVal, "\"", ""), "[Rr][Nn]", "|"), "|"); - for(std::string &x : headers) - { - if(regFind(x, "(?i)Host: ")) - host = x.substr(6); - else if(regFind(x, "(?i)Edge: ")) - edge = x.substr(6); - } - break; - case "obfs"_hash: - if(itemVal == "ws") - net = "ws"; - break; - default: continue; + case "group"_hash: + group = itemVal; + break; + case "over-tls"_hash: + tls = itemVal == "true" ? "tls" : ""; + break; + case "tls-host"_hash: + host = itemVal; + break; + case "obfs-path"_hash: + path = replaceAllDistinct(itemVal, "\"", ""); + break; + case "obfs-header"_hash: + headers = split(replaceAllDistinct(replaceAllDistinct(itemVal, "\"", ""), "[Rr][Nn]", "|"), "|"); + for(std::string &x : headers) + { + if(regFind(x, "(?i)Host: ")) + host = x.substr(6); + else if(regFind(x, "(?i)Edge: ")) + edge = x.substr(6); + } + break; + case "obfs"_hash: + if(itemVal == "ws") + net = "ws"; + break; + default: + continue; } } if(path.empty()) path = "/"; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = group; - node.remarks = ps; - node.server = add; - node.port = to_int(port, 1); - node.proxyStr = vmessConstruct(group, ps, add, port, type, id, aid, net, cipher, path, host, edge, tls); + vmessConstruct(node, group, ps, add, port, type, id, aid, net, cipher, path, host, edge, tls); } } -void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::string &custom_port, nodeInfo &node) +void explodeNetch(std::string netch, Proxy &node) { Document json; std::string type, group, remark, address, port, username, password, method, plugin, pluginopts, protocol, protoparam, obfs, obfsparam, id, aid, transprot, faketype, host, edge, path, tls; tribool udp, tfo, scv; - netch = urlsafe_base64_decode(netch.substr(8)); + netch = urlSafeBase64Decode(netch.substr(8)); json.Parse(netch.data()); if(json.HasParseError()) @@ -848,7 +847,7 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s udp = GetMember(json, "EnableUDP"); tfo = GetMember(json, "EnableTFO"); scv = GetMember(json, "AllowInsecure"); - port = custom_port.size() ? custom_port : GetMember(json, "Port"); + port = GetMember(json, "Port"); if(port == "0") return; method = GetMember(json, "EncryptMethod"); @@ -862,9 +861,7 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s pluginopts = GetMember(json, "PluginOption"); if(group.empty()) group = SS_DEFAULT_GROUP; - node.group = group; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(group, remark, address, port, password, method, plugin, pluginopts, ss_libev, udp, tfo, scv); + ssConstruct(node, group, remark, address, port, password, method, plugin, pluginopts, udp, tfo, scv); break; case "SSR"_hash: protocol = GetMember(json, "Protocol"); @@ -875,9 +872,7 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s pluginopts = GetMember(json, "PluginOption"); if(group.empty()) group = SS_DEFAULT_GROUP; - node.group = group; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(group, remark, address, port, password, method, plugin, pluginopts, ss_libev, udp, tfo, scv); + ssConstruct(node, group, remark, address, port, password, method, plugin, pluginopts, udp, tfo, scv); } else { @@ -885,9 +880,7 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s obfsparam = GetMember(json, "OBFSParam"); if(group.empty()) group = SSR_DEFAULT_GROUP; - node.group = group; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.proxyStr = ssrConstruct(group, remark, base64_encode(remark), address, port, protocol, method, obfs, password, obfsparam, protoparam, ssr_libev, udp, tfo, scv); + ssrConstruct(node, group, remark, address, port, protocol, method, obfs, password, obfsparam, protoparam, udp, tfo, scv); } break; case "VMess"_hash: @@ -899,56 +892,42 @@ void explodeNetch(std::string netch, bool ss_libev, bool ssr_libev, const std::s path = GetMember(json, "Path"); edge = GetMember(json, "Edge"); tls = GetMember(json, "TLSSecure"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; if(group.empty()) group = V2RAY_DEFAULT_GROUP; - node.group = group; - node.proxyStr = vmessConstruct(group, remark, address, port, faketype, id, aid, transprot, method, path, host, edge, tls, udp, tfo, scv); + vmessConstruct(node, group, remark, address, port, faketype, id, aid, transprot, method, path, host, edge, tls, udp, tfo, scv); break; case "Socks5"_hash: username = GetMember(json, "Username"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; if(group.empty()) group = SOCKS_DEFAULT_GROUP; - node.group = group; - node.proxyStr = socksConstruct(group, remark, address, port, username, password, udp, tfo, scv); + socksConstruct(node, group, remark, address, port, username, password, udp, tfo, scv); break; case "HTTP"_hash: case "HTTPS"_hash: - node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; if(group.empty()) group = HTTP_DEFAULT_GROUP; - node.group = group; - node.proxyStr = httpConstruct(group, remark, address, port, username, password, type == "HTTPS", tfo, scv); + httpConstruct(node, group, remark, address, port, username, password, type == "HTTPS", tfo, scv); break; case "Trojan"_hash: host = GetMember(json, "Host"); tls = GetMember(json, "TLSSecure"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; if(group.empty()) group = TROJAN_DEFAULT_GROUP; - node.group = group; - node.proxyStr = trojanConstruct(group, remark, address, port, password, host, tls == "true", udp, tfo, scv); + trojanConstruct(node, group, remark, address, port, password, host, tls == "true", udp, tfo, scv); break; case "Snell"_hash: obfs = GetMember(json, "OBFS"); host = GetMember(json, "Host"); - node.linkType = SPEEDTEST_MESSAGE_FOUNDSNELL; if(group.empty()) group = SNELL_DEFAULT_GROUP; - node.group = group; - node.proxyStr = snellConstruct(group, remark, address, port, password, obfs, host, udp, tfo, scv); + snellConstruct(node, group, remark, address, port, password, obfs, host, udp, tfo, scv); break; default: return; } - - node.remarks = remark; - node.server = address; - node.port = (unsigned short)to_int(port, 1); } -void explodeClash(Node yamlnode, const std::string &custom_port, std::vector &nodes, bool ss_libev, bool ssr_libev) +void explodeClash(Node yamlnode, std::vector &nodes) { std::string proxytype, ps, server, port, cipher, group, password; //common std::string type = "none", id, aid = "0", net = "tcp", path, host, edge, tls; //vmess @@ -956,17 +935,17 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= proxytype; singleproxy["name"] >>= ps; singleproxy["server"] >>= server; - port = custom_port.empty() ? safe_as(singleproxy["port"]) : custom_port; + singleproxy["port"] >>= port; if(port.empty() || port == "0") continue; udp = safe_as(singleproxy["udp"]); @@ -994,8 +973,7 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector(singleproxy["tls"]) == "true" ? "tls" : ""; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.proxyStr = vmessConstruct(group, ps, server, port, "", id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); + vmessConstruct(node, group, ps, server, port, "", id, aid, net, cipher, path, host, edge, tls, udp, tfo, scv); break; case "ss"_hash: group = SS_DEFAULT_GROUP; @@ -1061,12 +1039,11 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= user; singleproxy["password"] >>= password; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.proxyStr = socksConstruct(group, ps, server, port, user, password); + socksConstruct(node, group, ps, server, port, user, password); break; case "ssr"_hash: group = SSR_DEFAULT_GROUP; singleproxy["cipher"] >>= cipher; - if (cipher == "dummy") cipher = "none"; + if(cipher == "dummy") cipher = "none"; singleproxy["password"] >>= password; singleproxy["protocol"] >>= protocol; singleproxy["obfs"] >>= obfs; @@ -1094,8 +1070,7 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= obfsparam; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.proxyStr = ssrConstruct(group, ps, base64_encode(ps), server, port, protocol, cipher, obfs, password, obfsparam, protoparam, ssr_libev, udp, tfo, scv); + ssrConstruct(node, group, ps, server, port, protocol, cipher, obfs, password, obfsparam, protoparam, udp, tfo, scv); break; case "http"_hash: group = HTTP_DEFAULT_GROUP; @@ -1104,16 +1079,14 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= password; singleproxy["tls"] >>= tls; - node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - node.proxyStr = httpConstruct(group, ps, server, port, user, password, tls == "true", tfo, scv); + httpConstruct(node, group, ps, server, port, user, password, tls == "true", tfo, scv); break; case "trojan"_hash: group = TROJAN_DEFAULT_GROUP; singleproxy["password"] >>= password; singleproxy["sni"] >>= host; - node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; - node.proxyStr = trojanConstruct(group, ps, server, port, password, host, true, udp, tfo, scv); + trojanConstruct(node, group, ps, server, port, password, host, true, udp, tfo, scv); break; case "snell"_hash: group = SNELL_DEFAULT_GROUP; @@ -1121,26 +1094,21 @@ void explodeClash(Node yamlnode, const std::string &custom_port, std::vector>= obfs; singleproxy["obfs-opts"]["host"] >>= host; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSNELL; - node.proxyStr = snellConstruct(group, ps, server, port, password, obfs, host, udp, tfo, scv); + snellConstruct(node, group, ps, server, port, password, obfs, host, udp, tfo, scv); break; default: continue; } - node.group = group; - node.remarks = ps; - node.server = server; - node.port = to_int(port, 1); - node.id = index; + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); index++; } return; } -void explodeStdVMess(std::string vmess, const std::string &custom_port, nodeInfo &node) +void explodeStdVMess(std::string vmess, Proxy &node) { std::string add, port, type, id, aid, net, path, host, tls, remarks; std::string addition; @@ -1150,7 +1118,7 @@ void explodeStdVMess(std::string vmess, const std::string &custom_port, nodeInfo pos = vmess.rfind("#"); if(pos != vmess.npos) { - remarks = UrlDecode(vmess.substr(pos + 1)); + remarks = urlDecode(vmess.substr(pos + 1)); vmess.erase(pos); } const std::string stdvmess_matcher = R"(^([a-z]+)(?:\+([a-z]+))?:([\da-f]{4}(?:[\da-f]{4}-){4}[\da-f]{12})-(\d+)@(.+):(\d+)(?:\/?\?(.*))?$)"; @@ -1177,21 +1145,14 @@ void explodeStdVMess(std::string vmess, const std::string &custom_port, nodeInfo return; } - if(!custom_port.empty()) - port = custom_port; if(remarks.empty()) remarks = add + ":" + port; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.remarks = remarks; - node.server = add; - node.port = to_int(port, 0); - node.proxyStr = vmessConstruct(node.group, remarks, add, port, type, id, aid, net, "auto", path, host, "", tls); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, "auto", path, host, "", tls); return; } -void explodeShadowrocket(std::string rocket, const std::string &custom_port, nodeInfo &node) +void explodeShadowrocket(std::string rocket, Proxy &node) { std::string add, port, type, id, aid, net = "tcp", path, host, tls, cipher, remarks; std::string obfs; //for other style of link @@ -1202,13 +1163,11 @@ void explodeShadowrocket(std::string rocket, const std::string &custom_port, nod addition = rocket.substr(pos + 1); rocket.erase(pos); - if(regGetMatch(urlsafe_base64_decode(rocket), "(.*?):(.*)@(.*):(.*)", 5, 0, &cipher, &id, &add, &port)) + if(regGetMatch(urlSafeBase64Decode(rocket), "(.*?):(.*)@(.*):(.*)", 5, 0, &cipher, &id, &add, &port)) return; - if(custom_port.size()) - port = custom_port; if(port == "0") return; - remarks = UrlDecode(getUrlArg(addition, "remarks")); + remarks = urlDecode(getUrlArg(addition, "remarks")); obfs = getUrlArg(addition, "obfs"); if(obfs.size()) { @@ -1234,15 +1193,10 @@ void explodeShadowrocket(std::string rocket, const std::string &custom_port, nod if(remarks.empty()) remarks = add + ":" + port; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.remarks = remarks; - node.server = add; - node.port = to_int(port, 0); - node.proxyStr = vmessConstruct(node.group, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls); } -void explodeKitsunebi(std::string kit, const std::string &custom_port, nodeInfo &node) +void explodeKitsunebi(std::string kit, Proxy &node) { std::string add, port, type, id, aid = "0", net = "tcp", path, host, tls, cipher = "auto", remarks; std::string addition; @@ -1268,8 +1222,6 @@ void explodeKitsunebi(std::string kit, const std::string &custom_port, nodeInfo path = port.substr(pos); port.erase(pos); } - if(custom_port.size()) - port = custom_port; if(port == "0") return; net = getUrlArg(addition, "network"); @@ -1279,19 +1231,14 @@ void explodeKitsunebi(std::string kit, const std::string &custom_port, nodeInfo if(remarks.empty()) remarks = add + ":" + port; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.remarks = remarks; - node.server = add; - node.port = to_int(port, 0); - node.proxyStr = vmessConstruct(node.group, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, add, port, type, id, aid, net, cipher, path, host, "", tls); } -bool explodeSurge(std::string surge, const std::string &custom_port, std::vector &nodes, bool libev) +bool explodeSurge(std::string surge, std::vector &nodes) { std::multimap proxies; - nodeInfo node; - unsigned int i, index = nodes.size(); + Proxy node; + uint32_t i, index = nodes.size(); INIReader ini; /* @@ -1356,51 +1303,56 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector */ //if(mod_md5 == modSSMD5) //is SSEncrypt module + { + if(configs.size() < 5) + continue; + server = trim(configs[1]); + port = trim(configs[2]); + if(port == "0") + continue; + method = trim(configs[3]); + password = trim(configs[4]); + + for(i = 6; i < configs.size(); i++) { - if(configs.size() < 5) + vArray = split(configs[i], "="); + if(vArray.size() < 2) continue; - server = trim(configs[1]); - port = custom_port.empty() ? trim(configs[2]) : custom_port; - if(port == "0") + itemName = trim(vArray[0]); + itemVal = trim(vArray[1]); + switch(hash_(itemName)) + { + case "obfs"_hash: + plugin = "simple-obfs"; + pluginopts_mode = itemVal; + break; + case "obfs-host"_hash: + pluginopts_host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + default: continue; - method = trim(configs[3]); - password = trim(configs[4]); - - for(i = 6; i < configs.size(); i++) - { - vArray = split(configs[i], "="); - if(vArray.size() < 2) - continue; - itemName = trim(vArray[0]); - itemVal = trim(vArray[1]); - switch(hash_(itemName)) - { - case "obfs"_hash: - plugin = "simple-obfs"; - pluginopts_mode = itemVal; - break; - case "obfs-host"_hash: pluginopts_host = itemVal; break; - case "udp-relay"_hash: udp = itemVal; break; - case "tfo"_hash: tfo = itemVal; break; - default: continue; - } } - if(plugin.size()) - { - pluginopts = "obfs=" + pluginopts_mode; - pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; - } - - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(node.group, remarks, server, port, password, method, plugin, pluginopts, libev, udp, tfo, scv); } + if(plugin.size()) + { + pluginopts = "obfs=" + pluginopts_mode; + pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; + } + + ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, tfo, scv); + } //else // continue; - break; + break; case "ss"_hash: //surge 3 style ss proxy server = trim(configs[1]); - port = custom_port.empty() ? trim(configs[2]) : custom_port; + port = trim(configs[2]); if(port == "0") continue; @@ -1413,16 +1365,27 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "encrypt-method"_hash: method = itemVal; break; - case "password"_hash: password = itemVal; break; - case "obfs"_hash: - plugin = "simple-obfs"; - pluginopts_mode = itemVal; - break; - case "obfs-host"_hash: pluginopts_host = itemVal; break; - case "udp-relay"_hash: udp = itemVal; break; - case "tfo"_hash: tfo = itemVal; break; - default: continue; + case "encrypt-method"_hash: + method = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "obfs"_hash: + plugin = "simple-obfs"; + pluginopts_mode = itemVal; + break; + case "obfs-host"_hash: + pluginopts_host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + default: + continue; } } if(plugin.size()) @@ -1431,15 +1394,11 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector pluginopts += pluginopts_host.empty() ? "" : ";obfs-host=" + pluginopts_host; } - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(node.group, remarks, server, port, password, method, plugin, pluginopts, libev, udp, tfo, scv); + ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, tfo, scv); break; case "socks5"_hash: //surge 3 style socks5 proxy - node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.group = SOCKS_DEFAULT_GROUP; server = trim(configs[1]); - port = custom_port.empty() ? trim(configs[2]) : custom_port; + port = trim(configs[2]); if(port == "0") continue; if(configs.size() >= 5) @@ -1456,17 +1415,24 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "udp-relay"_hash: udp = itemVal; break; - case "tfo"_hash: tfo = itemVal; break; - case "skip-cert-verify"_hash: scv = itemVal; break; - default: continue; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + default: + continue; } } - node.proxyStr = socksConstruct(node.group, remarks, server, port, username, password, udp, tfo, scv); + socksConstruct(node, SOCKS_DEFAULT_GROUP, remarks, server, port, username, password, udp, tfo, scv); break; case "vmess"_hash: //surge 4 style vmess proxy server = trim(configs[1]); - port = custom_port.empty() ? trim(configs[2]) : custom_port; + port = trim(configs[2]); if(port == "0") continue; net = "tcp"; @@ -1481,43 +1447,58 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "username"_hash: id = itemVal; break; - case "ws"_hash: net = itemVal == "true" ? "ws" : "tcp"; break; - case "tls"_hash: tls = itemVal == "true" ? "tls" : ""; break; - case "ws-path"_hash: path = itemVal; break; - case "obfs-host"_hash: host = itemVal; break; - case "ws-headers"_hash: - headers = split(itemVal, "|"); - for(auto &y : headers) - { - header = split(trim(y), ":"); - if(header.size() != 2) - continue; - else if(regMatch(header[0], "(?i)host")) - host = trim_quote(header[1]); - else if(regMatch(header[0], "(?i)edge")) - edge = trim_quote(header[1]); - } - break; - case "udp-relay"_hash: udp = itemVal; break; - case "tfo"_hash: tfo = itemVal; break; - case "skip-cert-verify"_hash: scv = itemVal; break; - case "tls13"_hash: tls13 = itemVal; break; - default: continue; + case "username"_hash: + id = itemVal; + break; + case "ws"_hash: + net = itemVal == "true" ? "ws" : "tcp"; + break; + case "tls"_hash: + tls = itemVal == "true" ? "tls" : ""; + break; + case "ws-path"_hash: + path = itemVal; + break; + case "obfs-host"_hash: + host = itemVal; + break; + case "ws-headers"_hash: + headers = split(itemVal, "|"); + for(auto &y : headers) + { + header = split(trim(y), ":"); + if(header.size() != 2) + continue; + else if(regMatch(header[0], "(?i)host")) + host = trimQuote(header[1]); + else if(regMatch(header[0], "(?i)edge")) + edge = trimQuote(header[1]); + } + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + default: + continue; } } if(host.empty() && !isIPv4(server) && !isIPv6(server)) host = server; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.proxyStr = vmessConstruct(node.group, remarks, server, port, "", id, "0", net, method, path, host, edge, tls, udp, tfo, scv, tls13); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, server, port, "", id, "0", net, method, path, host, edge, tls, udp, tfo, scv, tls13); break; case "http"_hash: //http proxy - node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - node.group = HTTP_DEFAULT_GROUP; server = trim(configs[1]); - port = custom_port.empty() ? trim(configs[2]) : custom_port; + port = trim(configs[2]); if(port == "0") continue; for(i = 3; i < configs.size(); i++) @@ -1529,19 +1510,24 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "username"_hash: username = itemVal; break; - case "password"_hash: password = itemVal; break; - case "skip-cert-verify"_hash: scv = itemVal; break; - default: continue; + case "username"_hash: + username = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + default: + continue; } } - node.proxyStr = httpConstruct(node.group, remarks, server, port, username, password, false, tfo, scv); + httpConstruct(node, HTTP_DEFAULT_GROUP, remarks, server, port, username, password, false, tfo, scv); break; case "trojan"_hash: // surge 4 style trojan proxy - node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; - node.group = TROJAN_DEFAULT_GROUP; server = trim(configs[1]); - port = custom_port.empty() ? trim(configs[2]) : custom_port; + port = trim(configs[2]); if(port == "0") continue; @@ -1554,25 +1540,33 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "password"_hash: password = itemVal; break; - case "sni"_hash: host = itemVal; break; - case "udp-relay"_hash: udp = itemVal; break; - case "tfo"_hash: tfo = itemVal; break; - case "skip-cert-verify"_hash: scv = itemVal; break; - default: continue; + case "password"_hash: + password = itemVal; + break; + case "sni"_hash: + host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + default: + continue; } } if(host.empty() && !isIPv4(server) && !isIPv6(server)) host = server; - node.proxyStr = trojanConstruct(node.group, remarks, server, port, password, host, true, udp, tfo, scv); + trojanConstruct(node, TROJAN_DEFAULT_GROUP, remarks, server, port, password, host, true, udp, tfo, scv); break; case "snell"_hash: - node.linkType = SPEEDTEST_MESSAGE_FOUNDSNELL; - node.group = SNELL_DEFAULT_GROUP; - server = trim(configs[1]); - port = custom_port.empty() ? trim(configs[2]) : custom_port; + port = trim(configs[2]); if(port == "0") continue; @@ -1585,26 +1579,39 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "psk"_hash: password = itemVal; break; - case "obfs"_hash: plugin = itemVal; break; - case "obfs-host"_hash: host = itemVal; break; - case "udp-relay"_hash: udp = itemVal; break; - case "tfo"_hash: tfo = itemVal; break; - case "skip-cert-verify"_hash: scv = itemVal; break; - default: continue; + case "psk"_hash: + password = itemVal; + break; + case "obfs"_hash: + plugin = itemVal; + break; + case "obfs-host"_hash: + host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "tfo"_hash: + tfo = itemVal; + break; + case "skip-cert-verify"_hash: + scv = itemVal; + break; + default: + continue; } } if(host.empty() && !isIPv4(server) && !isIPv6(server)) host = server; - node.proxyStr = snellConstruct(node.group, remarks, server, port, password, plugin, host, udp, tfo, scv); + snellConstruct(node, SNELL_DEFAULT_GROUP, remarks, server, port, password, plugin, host, udp, tfo, scv); break; default: switch(hash_(remarks)) { case "shadowsocks"_hash: //quantumult x style ss/ssr link server = trim(configs[0].substr(0, configs[0].rfind(":"))); - port = custom_port.empty() ? trim(configs[0].substr(configs[0].rfind(":") + 1)) : custom_port; + port = trim(configs[0].substr(configs[0].rfind(":") + 1)); if(port == "0") continue; @@ -1617,38 +1624,59 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "method"_hash: method = itemVal; break; - case "password"_hash: password = itemVal; break; - case "tag"_hash: remarks = itemVal; break; - case "ssr-protocol"_hash: protocol = itemVal; break; - case "ssr-protocol-param"_hash: protoparam = itemVal; break; - case "obfs"_hash: + case "method"_hash: + method = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "ssr-protocol"_hash: + protocol = itemVal; + break; + case "ssr-protocol-param"_hash: + protoparam = itemVal; + break; + case "obfs"_hash: + { + switch(hash_(itemVal)) { - switch(hash_(itemVal)) - { - case "http"_hash: - case "tls"_hash: - plugin = "simple-obfs"; - pluginopts_mode = itemVal; - break; - case "wss"_hash: - tls = "tls"; - [[fallthrough]]; - case "ws"_hash: - pluginopts_mode = "websocket"; - plugin = "v2ray-plugin"; - break; - default: - pluginopts_mode = itemVal; - } + case "http"_hash: + case "tls"_hash: + plugin = "simple-obfs"; + pluginopts_mode = itemVal; break; + case "wss"_hash: + tls = "tls"; + [[fallthrough]]; + case "ws"_hash: + pluginopts_mode = "websocket"; + plugin = "v2ray-plugin"; + break; + default: + pluginopts_mode = itemVal; } - case "obfs-host"_hash: pluginopts_host = itemVal; break; - case "obfs-uri"_hash: path = itemVal; break; - case "udp-relay"_hash: udp = itemVal; break; - case "fast-open"_hash: tfo = itemVal; break; - case "tls13"_hash: tls13 = itemVal; break; - default: continue; + break; + } + case "obfs-host"_hash: + pluginopts_host = itemVal; + break; + case "obfs-uri"_hash: + path = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + default: + continue; } } if(remarks.empty()) @@ -1674,20 +1702,16 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector if(protocol.size()) { - node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.group = SSR_DEFAULT_GROUP; - node.proxyStr = ssrConstruct(node.group, remarks, base64_encode(remarks), server, port, protocol, method, pluginopts_mode, password, pluginopts_host, protoparam, libev, udp, tfo, scv); + ssrConstruct(node, SSR_DEFAULT_GROUP, remarks, server, port, protocol, method, pluginopts_mode, password, pluginopts_host, protoparam, udp, tfo, scv); } else { - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.group = SS_DEFAULT_GROUP; - node.proxyStr = ssConstruct(node.group, remarks, server, port, password, method, plugin, pluginopts, libev, udp, tfo, scv, tls13); + ssConstruct(node, SS_DEFAULT_GROUP, remarks, server, port, password, method, plugin, pluginopts, udp, tfo, scv, tls13); } break; case "vmess"_hash: //quantumult x style vmess link server = trim(configs[0].substr(0, configs[0].rfind(":"))); - port = custom_port.empty() ? trim(configs[0].substr(configs[0].rfind(":") + 1)) : custom_port; + port = trim(configs[0].substr(configs[0].rfind(":") + 1)); if(port == "0") continue; net = "tcp"; @@ -1701,24 +1725,50 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "method"_hash: method = itemVal; break; - case "password"_hash: id = itemVal; break; - case "tag"_hash: remarks = itemVal; break; - case "obfs"_hash: - switch(hash_(itemVal)) - { - case "ws"_hash: net = "ws"; break; - case "over-tls"_hash: tls = "tls"; break; - case "wss"_hash: net = "ws"; tls = "tls"; break; - } + case "method"_hash: + method = itemVal; + break; + case "password"_hash: + id = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "obfs"_hash: + switch(hash_(itemVal)) + { + case "ws"_hash: + net = "ws"; break; - case "obfs-host"_hash: host = itemVal; break; - case "obfs-uri"_hash: path = itemVal; break; - case "over-tls"_hash: tls = itemVal == "true" ? "tls" : ""; break; - case "udp-relay"_hash: udp = itemVal; break; - case "fast-open"_hash: tfo = itemVal; break; - case "tls13"_hash: tls13 = itemVal; break; - default: continue; + case "over-tls"_hash: + tls = "tls"; + break; + case "wss"_hash: + net = "ws"; + tls = "tls"; + break; + } + break; + case "obfs-host"_hash: + host = itemVal; + break; + case "obfs-uri"_hash: + path = itemVal; + break; + case "over-tls"_hash: + tls = itemVal == "true" ? "tls" : ""; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + default: + continue; } } if(remarks.empty()) @@ -1727,13 +1777,11 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector if(host.empty() && !isIPv4(server) && !isIPv6(server)) host = server; - node.linkType = SPEEDTEST_MESSAGE_FOUNDVMESS; - node.group = V2RAY_DEFAULT_GROUP; - node.proxyStr = vmessConstruct(node.group, remarks, server, port, "", id, "0", net, method, path, host, "", tls, udp, tfo, scv, tls13); + vmessConstruct(node, V2RAY_DEFAULT_GROUP, remarks, server, port, "", id, "0", net, method, path, host, "", tls, udp, tfo, scv, tls13); break; case "trojan"_hash: //quantumult x style trojan link server = trim(configs[0].substr(0, configs[0].rfind(":"))); - port = custom_port.empty() ? trim(configs[0].substr(configs[0].rfind(":") + 1)) : custom_port; + port = trim(configs[0].substr(configs[0].rfind(":") + 1)); if(port == "0") continue; @@ -1746,15 +1794,32 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "password"_hash: password = itemVal; break; - case "tag"_hash: remarks = itemVal; break; - case "over-tls"_hash: tls = itemVal; break; - case "tls-host"_hash: host = itemVal; break; - case "udp-relay"_hash: udp = itemVal; break; - case "fast-open"_hash: tfo = itemVal; break; - case "tls-verification"_hash: scv = itemVal == "false"; break; - case "tls13"_hash: tls13 = itemVal; break; - default: continue; + case "password"_hash: + password = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "over-tls"_hash: + tls = itemVal; + break; + case "tls-host"_hash: + host = itemVal; + break; + case "udp-relay"_hash: + udp = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + case "tls-verification"_hash: + scv = itemVal == "false"; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + default: + continue; } } if(remarks.empty()) @@ -1763,13 +1828,11 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector if(host.empty() && !isIPv4(server) && !isIPv6(server)) host = server; - node.linkType = SPEEDTEST_MESSAGE_FOUNDTROJAN; - node.group = TROJAN_DEFAULT_GROUP; - node.proxyStr = trojanConstruct(node.group, remarks, server, port, password, host, tls == "true", udp, tfo, scv, tls13); + trojanConstruct(node, TROJAN_DEFAULT_GROUP, remarks, server, port, password, host, tls == "true", udp, tfo, scv, tls13); break; case "http"_hash: //quantumult x style http links server = trim(configs[0].substr(0, configs[0].rfind(":"))); - port = custom_port.empty() ? trim(configs[0].substr(configs[0].rfind(":") + 1)) : custom_port; + port = trim(configs[0].substr(configs[0].rfind(":") + 1)); if(port == "0") continue; @@ -1782,14 +1845,29 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector itemVal = trim(vArray[1]); switch(hash_(itemName)) { - case "username"_hash: username = itemVal; break; - case "password"_hash: password = itemVal; break; - case "tag"_hash: remarks = itemVal; break; - case "over-tls"_hash: tls = itemVal; break; - case "tls-verification"_hash: scv = itemVal == "false"; break; - case "tls13"_hash: tls13 = itemVal; break; - case "fast-open"_hash: tfo = itemVal; break; - default: continue; + case "username"_hash: + username = itemVal; + break; + case "password"_hash: + password = itemVal; + break; + case "tag"_hash: + remarks = itemVal; + break; + case "over-tls"_hash: + tls = itemVal; + break; + case "tls-verification"_hash: + scv = itemVal == "false"; + break; + case "tls13"_hash: + tls13 = itemVal; + break; + case "fast-open"_hash: + tfo = itemVal; + break; + default: + continue; } } if(remarks.empty()) @@ -1803,9 +1881,7 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector if(password == "none") password.clear(); - node.linkType = SPEEDTEST_MESSAGE_FOUNDHTTP; - node.group = HTTP_DEFAULT_GROUP; - node.proxyStr = httpConstruct(node.group, remarks, server, port, username, password, tls == "true", tfo, scv, tls13); + httpConstruct(node, HTTP_DEFAULT_GROUP, remarks, server, port, username, password, tls == "true", tfo, scv, tls13); break; default: continue; @@ -1813,36 +1889,33 @@ bool explodeSurge(std::string surge, const std::string &custom_port, std::vector break; } - node.remarks = remarks; - node.server = server; - node.port = to_int(port); - node.id = index; + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); index++; } return index; } -void explodeSSTap(std::string sstap, const std::string &custom_port, std::vector &nodes, bool ss_libev, bool ssr_libev) +void explodeSSTap(std::string sstap, std::vector &nodes) { std::string configType, group, remarks, server, port; std::string cipher; std::string user, pass; std::string protocol, protoparam, obfs, obfsparam; Document json; - nodeInfo node; - unsigned int index = nodes.size(); + Proxy node; + uint32_t index = nodes.size(); json.Parse(sstap.data()); if(json.HasParseError()) return; - for(unsigned int i = 0; i < json["configs"].Size(); i++) + for(uint32_t i = 0; i < json["configs"].Size(); i++) { json["configs"][i]["group"] >> group; json["configs"][i]["remarks"] >> remarks; json["configs"][i]["server"] >> server; - port = custom_port.size() ? custom_port : GetMember(json["configs"][i], "server_port"); + port = GetMember(json["configs"][i], "server_port"); if(port == "0") continue; @@ -1855,8 +1928,7 @@ void explodeSSTap(std::string sstap, const std::string &custom_port, std::vector { case 5: //socks 5 json["configs"][i]["username"] >> user; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSOCKS; - node.proxyStr = socksConstruct(group, remarks, server, port, user, pass); + socksConstruct(node, group, remarks, server, port, user, pass); break; case 6: //ss/ssr json["configs"][i]["protocol"] >> protocol; @@ -1864,36 +1936,31 @@ void explodeSSTap(std::string sstap, const std::string &custom_port, std::vector json["configs"][i]["method"] >> cipher; if(find(ss_ciphers.begin(), ss_ciphers.end(), cipher) != ss_ciphers.end() && protocol == "origin" && obfs == "plain") //is ss { - node.linkType = SPEEDTEST_MESSAGE_FOUNDSS; - node.proxyStr = ssConstruct(group, remarks, server, port, pass, cipher, "", "", ss_libev); + ssConstruct(node, group, remarks, server, port, pass, cipher, "", ""); } else //is ssr cipher { json["configs"][i]["obfsparam"] >> obfsparam; json["configs"][i]["protocolparam"] >> protoparam; - node.linkType = SPEEDTEST_MESSAGE_FOUNDSSR; - node.proxyStr = ssrConstruct(group, remarks, base64_encode(remarks), server, port, protocol, cipher, obfs, pass, obfsparam, protoparam, ssr_libev); + ssrConstruct(node, group, remarks, base64Encode(remarks), server, port, protocol, cipher, obfs, pass, obfsparam, protoparam); } break; default: continue; } - node.group = group; - node.remarks = remarks; - node.id = index; - node.server = server; - node.port = to_int(port, 1); + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); + index++; } } -void explodeNetchConf(std::string netch, bool ss_libev, bool ssr_libev, const std::string &custom_port, std::vector &nodes) +void explodeNetchConf(std::string netch, std::vector &nodes) { Document json; - nodeInfo node; - unsigned int index = nodes.size(); + Proxy node; + uint32_t index = nodes.size(); json.Parse(netch.data()); if(json.HasParseError()) @@ -1902,153 +1969,96 @@ void explodeNetchConf(std::string netch, bool ss_libev, bool ssr_libev, const st if(!json.HasMember("Server")) return; - for(unsigned int i = 0; i < json["Server"].Size(); i++) + for(uint32_t i = 0; i < json["Server"].Size(); i++) { - explodeNetch("Netch://" + base64_encode(SerializeObject(json["Server"][i])), ss_libev, ssr_libev, custom_port, node); + explodeNetch("Netch://" + base64Encode(SerializeObject(json["Server"][i])), node); - node.id = index; + node.Id = index; nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); index++; } } -bool applyMatcher(const std::string &rule, std::string &real_rule, const nodeInfo &node); - -bool chkIgnore(const nodeInfo &node, string_array &exclude_remarks, string_array &include_remarks) +int explodeConfContent(const std::string &content, std::vector &nodes) { - bool excluded = false, included = false; - //std::string remarks = UTF8ToACP(node.remarks); - //std::string remarks = node.remarks; - //writeLog(LOG_TYPE_INFO, "Comparing exclude remarks..."); - excluded = std::any_of(exclude_remarks.cbegin(), exclude_remarks.cend(), [&node](const auto &x) - { - std::string real_rule; - if(applyMatcher(x, real_rule, node)) - { - if(real_rule.empty()) return true; - return regFind(node.remarks, real_rule); - } - else - return false; - }); - if(include_remarks.size() != 0) - { - //writeLog(LOG_TYPE_INFO, "Comparing include remarks..."); - included = std::any_of(include_remarks.cbegin(), include_remarks.cend(), [&node](const auto &x) - { - std::string real_rule; - if(applyMatcher(x, real_rule, node)) - { - if(real_rule.empty()) return true; - return regFind(node.remarks, real_rule); - } - else - return false; - }); - } - else - { - included = true; - } - - return excluded || !included; -} - -int explodeConf(std::string filepath, const std::string &custom_port, bool sslibev, bool ssrlibev, std::vector &nodes) -{ - std::ifstream infile; - std::stringstream contentstrm; - infile.open(filepath); - - contentstrm << infile.rdbuf(); - infile.close(); - - return explodeConfContent(contentstrm.str(), custom_port, sslibev, ssrlibev, nodes); -} - -int explodeConfContent(const std::string &content, const std::string &custom_port, bool sslibev, bool ssrlibev, std::vector &nodes) -{ - int filetype = -1; + ConfType filetype = ConfType::Unknow; if(strFind(content, "\"version\"")) - filetype = SPEEDTEST_MESSAGE_FOUNDSS; + filetype = ConfType::SS; else if(strFind(content, "\"serverSubscribes\"")) - filetype = SPEEDTEST_MESSAGE_FOUNDSSR; + filetype = ConfType::SSR; else if(strFind(content, "\"uiItem\"") || strFind(content, "vnext")) - filetype = SPEEDTEST_MESSAGE_FOUNDVMESS; + filetype = ConfType::V2Ray; else if(strFind(content, "\"proxy_apps\"")) - filetype = SPEEDTEST_MESSAGE_FOUNDSSCONF; + filetype = ConfType::SSConf; else if(strFind(content, "\"idInUse\"")) - filetype = SPEEDTEST_MESSAGE_FOUNDSSTAP; + filetype = ConfType::SSTap; else if(strFind(content, "\"local_address\"") && strFind(content, "\"local_port\"")) - filetype = SPEEDTEST_MESSAGE_FOUNDSSR; //use ssr config parser + filetype = ConfType::SSR; //use ssr config parser else if(strFind(content, "\"ModeFileNameType\"")) - filetype = SPEEDTEST_MESSAGE_FOUNDNETCH; + filetype = ConfType::Netch; switch(filetype) { - case SPEEDTEST_MESSAGE_FOUNDSS: - explodeSSConf(content, custom_port, sslibev, nodes); + case ConfType::SS: + explodeSSConf(content, nodes); break; - case SPEEDTEST_MESSAGE_FOUNDSSR: - explodeSSRConf(content, custom_port, sslibev, ssrlibev, nodes); + case ConfType::SSR: + explodeSSRConf(content, nodes); break; - case SPEEDTEST_MESSAGE_FOUNDVMESS: - explodeVmessConf(content, custom_port, sslibev, nodes); + case ConfType::V2Ray: + explodeVmessConf(content, nodes); break; - case SPEEDTEST_MESSAGE_FOUNDSSCONF: - explodeSSAndroid(content, sslibev, custom_port, nodes); + case ConfType::SSConf: + explodeSSAndroid(content, nodes); break; - case SPEEDTEST_MESSAGE_FOUNDSSTAP: - explodeSSTap(content, custom_port, nodes, sslibev, ssrlibev); + case ConfType::SSTap: + explodeSSTap(content, nodes); break; - case SPEEDTEST_MESSAGE_FOUNDNETCH: - explodeNetchConf(content, sslibev, ssrlibev, custom_port, nodes); + case ConfType::Netch: + explodeNetchConf(content, nodes); break; default: //try to parse as a local subscription - explodeSub(content, sslibev, ssrlibev, custom_port, nodes); + explodeSub(content, nodes); } - if(nodes.size() == 0) - return SPEEDTEST_ERROR_UNRECOGFILE; - else - return SPEEDTEST_ERROR_NONE; + return nodes.size() != 0; } -void explode(const std::string &link, bool sslibev, bool ssrlibev, const std::string &custom_port, nodeInfo &node) +void explode(const std::string &link, Proxy &node) { // TODO: replace strFind with startsWith if appropriate if(strFind(link, "ssr://")) - explodeSSR(link, sslibev, ssrlibev, custom_port, node); + explodeSSR(link, node); else if(strFind(link, "vmess://") || strFind(link, "vmess1://")) - explodeVmess(link, custom_port, node); + explodeVmess(link, node); else if(strFind(link, "ss://")) - explodeSS(link, sslibev, custom_port, node); + explodeSS(link, node); else if(strFind(link, "socks://") || strFind(link, "https://t.me/socks") || strFind(link, "tg://socks")) - explodeSocks(link, custom_port, node); + explodeSocks(link, node); else if(strFind(link, "https://t.me/http") || strFind(link, "tg://http")) //telegram style http link - explodeHTTP(link, custom_port, node); + explodeHTTP(link, node); else if(strFind(link, "Netch://")) - explodeNetch(link, sslibev, ssrlibev, custom_port, node); + explodeNetch(link, node); else if(strFind(link, "trojan://")) - explodeTrojan(link, custom_port, node); + explodeTrojan(link, node); else if(isLink(link)) - explodeHTTPSub(link, custom_port, node); + explodeHTTPSub(link, node); } -void explodeSub(std::string sub, bool sslibev, bool ssrlibev, const std::string &custom_port, std::vector &nodes) +void explodeSub(std::string sub, std::vector &nodes) { std::stringstream strstream; std::string strLink; bool processed = false; - nodeInfo node; + Proxy node; //try to parse as SSD configuration if(startsWith(sub, "ssd://")) { - explodeSSD(sub, sslibev, custom_port, nodes); + explodeSSD(sub, nodes); processed = true; } @@ -2061,19 +2071,20 @@ void explodeSub(std::string sub, bool sslibev, bool ssrlibev, const std::string Node yamlnode = Load(sub); if(yamlnode.size() && (yamlnode["Proxy"].IsDefined() || yamlnode["proxies"].IsDefined())) { - explodeClash(yamlnode, custom_port, nodes, sslibev, ssrlibev); + explodeClash(yamlnode, nodes); processed = true; } } } catch (std::exception &e) { - writeLog(0, e.what(), LOG_LEVEL_DEBUG); + //writeLog(0, e.what(), LOG_LEVEL_DEBUG); //ignore + throw; } //try to parse as surge configuration - if(!processed && explodeSurge(sub, custom_port, nodes, sslibev)) + if(!processed && explodeSurge(sub, nodes)) { processed = true; } @@ -2081,10 +2092,10 @@ void explodeSub(std::string sub, bool sslibev, bool ssrlibev, const std::string //try to parse as normal subscription if(!processed) { - sub = urlsafe_base64_decode(sub); + sub = urlSafeBase64Decode(sub); if(regFind(sub, "(vmess|shadowsocks|http|trojan)\\s*?=")) { - if(explodeSurge(sub, custom_port, nodes, sslibev)) + if(explodeSurge(sub, nodes)) return; } strstream << sub; @@ -2093,309 +2104,13 @@ void explodeSub(std::string sub, bool sslibev, bool ssrlibev, const std::string { if(strLink.rfind("\r") != strLink.npos) strLink.erase(strLink.size() - 1); - node.linkType = -1; - explode(strLink, sslibev, ssrlibev, custom_port, node); - if(strLink.size() == 0 || node.linkType == -1) + explode(strLink, node); + if(strLink.size() == 0 || node.Type == ProxyType::Unknow) { continue; } nodes.emplace_back(std::move(node)); - node = nodeInfo(); + node = Proxy(); } } } - -void filterNodes(std::vector &nodes, string_array &exclude_remarks, string_array &include_remarks, int groupID) -{ - int node_index = 0; - std::vector::iterator iter = nodes.begin(); - while(iter != nodes.end()) - { - if(chkIgnore(*iter, exclude_remarks, include_remarks)) - { - writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been ignored and will not be added."); - nodes.erase(iter); - } - else - { - writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been added."); - iter->id = node_index; - iter->groupID = groupID; - ++node_index; - ++iter; - } - } - /* - std::vector> exclude_patterns, include_patterns; - std::vector> exclude_match_data, include_match_data; - unsigned int i = 0; - PCRE2_SIZE erroroffset; - int errornumber, rc; - - for(i = 0; i < exclude_remarks.size(); i++) - { - std::unique_ptr pattern(pcre2_compile(reinterpret_cast(exclude_remarks[i].c_str()), exclude_remarks[i].size(), PCRE2_UTF | PCRE2_MULTILINE | PCRE2_ALT_BSUX, &errornumber, &erroroffset, NULL), &pcre2_code_free); - if(!pattern) - return; - exclude_patterns.emplace_back(std::move(pattern)); - pcre2_jit_compile(exclude_patterns[i].get(), 0); - std::unique_ptr match_data(pcre2_match_data_create_from_pattern(exclude_patterns[i].get(), NULL), &pcre2_match_data_free); - exclude_match_data.emplace_back(std::move(match_data)); - } - for(i = 0; i < include_remarks.size(); i++) - { - std::unique_ptr pattern(pcre2_compile(reinterpret_cast(include_remarks[i].c_str()), include_remarks[i].size(), PCRE2_UTF | PCRE2_MULTILINE | PCRE2_ALT_BSUX, &errornumber, &erroroffset, NULL), &pcre2_code_free); - if(!pattern) - return; - include_patterns.emplace_back(std::move(pattern)); - pcre2_jit_compile(include_patterns[i].get(), 0); - std::unique_ptr match_data(pcre2_match_data_create_from_pattern(include_patterns[i].get(), NULL), &pcre2_match_data_free); - include_match_data.emplace_back(std::move(match_data)); - } - writeLog(LOG_TYPE_INFO, "Filter started."); - while(iter != nodes.end()) - { - bool excluded = false, included = false; - for(i = 0; i < exclude_patterns.size(); i++) - { - rc = pcre2_match(exclude_patterns[i].get(), reinterpret_cast(iter->remarks.c_str()), iter->remarks.size(), 0, 0, exclude_match_data[i].get(), NULL); - if (rc < 0) - { - switch(rc) - { - case PCRE2_ERROR_NOMATCH: break; - default: return; - } - } - else - excluded = true; - } - if(include_patterns.size() > 0) - for(i = 0; i < include_patterns.size(); i++) - { - rc = pcre2_match(include_patterns[i].get(), reinterpret_cast(iter->remarks.c_str()), iter->remarks.size(), 0, 0, include_match_data[i].get(), NULL); - if (rc < 0) - { - switch(rc) - { - case PCRE2_ERROR_NOMATCH: break; - default: return; - } - } - else - included = true; - } - else - included = true; - if(excluded || !included) - { - writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been ignored and will not be added."); - nodes.erase(iter); - } - else - { - writeLog(LOG_TYPE_INFO, "Node " + iter->group + " - " + iter->remarks + " has been added."); - iter->id = node_index; - iter->groupID = groupID; - ++node_index; - ++iter; - } - } - */ - writeLog(LOG_TYPE_INFO, "Filter done."); -} - -unsigned long long streamToInt(const std::string &stream) -{ - if(!stream.size()) - return 0; - double streamval = 1.0; - std::vector units = {"B", "KB", "MB", "GB", "TB", "PB", "EB"}; - size_t index = units.size(); - do - { - index--; - if(endsWith(stream, units[index])) - { - streamval = std::pow(1024, index) * to_number(stream.substr(0, stream.size() - units[index].size()), 0.0); - break; - } - } while(index != 0); - return (unsigned long long)streamval; -} - -static inline double percentToDouble(const std::string &percent) -{ - return stof(percent.substr(0, percent.size() - 1)) / 100.0; -} - -time_t dateStringToTimestamp(std::string date) -{ - time_t rawtime; - time(&rawtime); - if(startsWith(date, "left=")) - { - time_t seconds_left = 0; - date.erase(0, 5); - if(endsWith(date, "d")) - { - date.erase(date.size() - 1); - seconds_left = to_number(date, 0.0) * 86400.0; - } - return rawtime + seconds_left; - } - else - { - struct tm *expire_time; - std::vector date_array = split(date, ":"); - if(date_array.size() != 6) - return 0; - - expire_time = localtime(&rawtime); - expire_time->tm_year = to_int(date_array[0], 1900) - 1900; - expire_time->tm_mon = to_int(date_array[1], 1) - 1; - expire_time->tm_mday = to_int(date_array[2]); - expire_time->tm_hour = to_int(date_array[3]); - expire_time->tm_min = to_int(date_array[4]); - expire_time->tm_sec = to_int(date_array[5]); - return mktime(expire_time); - } -} - -bool getSubInfoFromHeader(const std::string &header, std::string &result) -{ - std::string pattern = R"(^(?i:Subscription-UserInfo): (.*?)\s*?$)", retStr; - if(regFind(header, pattern)) - { - regGetMatch(header, pattern, 2, 0, &retStr); - if(retStr.size()) - { - result = retStr; - return true; - } - } - return false; -} - -bool getSubInfoFromNodes(const std::vector &nodes, const string_array &stream_rules, const string_array &time_rules, std::string &result) -{ - std::string remarks, pattern, target, stream_info, time_info, retStr; - string_size spos; - - for(const nodeInfo &x : nodes) - { - remarks = x.remarks; - if(!stream_info.size()) - { - for(const std::string &y : stream_rules) - { - spos = y.rfind("|"); - if(spos == y.npos) - continue; - pattern = y.substr(0, spos); - target = y.substr(spos + 1); - if(regMatch(remarks, pattern)) - { - retStr = regReplace(remarks, pattern, target); - if(retStr != remarks) - { - stream_info = retStr; - break; - } - } - else - continue; - } - } - - remarks = x.remarks; - if(!time_info.size()) - { - for(const std::string &y : time_rules) - { - spos = y.rfind("|"); - if(spos == y.npos) - continue; - pattern = y.substr(0, spos); - target = y.substr(spos + 1); - if(regMatch(remarks, pattern)) - { - retStr = regReplace(remarks, pattern, target); - if(retStr != remarks) - { - time_info = retStr; - break; - } - } - else - continue; - } - } - - if(stream_info.size() && time_info.size()) - break; - } - - if(!stream_info.size() && !time_info.size()) - return false; - - //calculate how much stream left - unsigned long long total = 0, left, used = 0, expire = 0; - std::string total_str = getUrlArg(stream_info, "total"), left_str = getUrlArg(stream_info, "left"), used_str = getUrlArg(stream_info, "used"); - if(strFind(total_str, "%")) - { - if(used_str.size()) - { - used = streamToInt(used_str); - total = used / (1 - percentToDouble(total_str)); - } - else if(left_str.size()) - { - left = streamToInt(left_str); - total = left / percentToDouble(total_str); - used = total - left; - } - } - else - { - total = streamToInt(total_str); - if(used_str.size()) - { - used = streamToInt(used_str); - } - else if(left_str.size()) - { - left = streamToInt(left_str); - used = total - left; - } - } - - result = "upload=0; download=" + std::to_string(used) + "; total=" + std::to_string(total) + ";"; - - //calculate expire time - expire = dateStringToTimestamp(time_info); - if(expire) - result += " expire=" + std::to_string(expire) + ";"; - - return true; -} - -bool getSubInfoFromSSD(const std::string &sub, std::string &result) -{ - rapidjson::Document json; - json.Parse(urlsafe_base64_decode(sub.substr(6)).data()); - if(json.HasParseError()) - return false; - - std::string used_str = GetMember(json, "traffic_used"), total_str = GetMember(json, "traffic_total"), expire_str = GetMember(json, "expiry"); - if(!used_str.size() || !total_str.size()) - return false; - unsigned long long used = stod(used_str) * std::pow(1024, 3), total = stod(total_str) * std::pow(1024, 3), expire; - result = "upload=0; download=" + std::to_string(used) + "; total=" + std::to_string(total) + ";"; - - expire = dateStringToTimestamp(regReplace(expire_str, "(\\d+)-(\\d+)-(\\d+) (.*)", "$1:$2:$3:$4")); - if(expire) - result += " expire=" + std::to_string(expire) + ";"; - - return true; -} diff --git a/src/parser/subparser.h b/src/parser/subparser.h new file mode 100644 index 0000000..5afa71c --- /dev/null +++ b/src/parser/subparser.h @@ -0,0 +1,45 @@ +#ifndef SUBPARSER_H_INCLUDED +#define SUBPARSER_H_INCLUDED + +#include + +#include "config/proxy.h" + +enum class ConfType +{ + Unknow, + SS, + SSR, + V2Ray, + SSConf, + SSTap, + Netch, + SOCKS, + HTTP, + SUB, + Local +}; + +void vmessConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &add, const std::string &port, const std::string &type, const std::string &id, const std::string &aid, const std::string &net, const std::string &cipher, const std::string &path, const std::string &host, const std::string &edge, const std::string &tls, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); +void ssrConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &protocol, const std::string &method, const std::string &obfs, const std::string &password, const std::string &obfsparam, const std::string &protoparam, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +void ssConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &method, const std::string &plugin, const std::string &pluginopts, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); +void socksConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &username, const std::string &password, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +void httpConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &username, const std::string &password, bool tls, tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); +void trojanConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &host, bool tlssecure, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool(), tribool tls13 = tribool()); +void snellConstruct(Proxy &node, const std::string &group, const std::string &remarks, const std::string &server, const std::string &port, const std::string &password, const std::string &obfs, const std::string &host, tribool udp = tribool(), tribool tfo = tribool(), tribool scv = tribool()); +void explodeVmess(std::string vmess, Proxy &node); +void explodeSSR(std::string ssr, Proxy &node); +void explodeSS(std::string ss, Proxy &node); +void explodeTrojan(std::string trojan, Proxy &node); +void explodeQuan(const std::string &quan, Proxy &node); +void explodeStdVMess(std::string vmess, Proxy &node); +void explodeShadowrocket(std::string kit, Proxy &node); +void explodeKitsunebi(std::string kit, Proxy &node); +/// Parse a link +void explode(const std::string &link, Proxy &node); +void explodeSSD(std::string link, std::vector &nodes); +void explodeSub(std::string sub, std::vector &nodes); +int explodeConf(const std::string &filepath, std::vector &nodes); +int explodeConfContent(const std::string &content, std::vector &nodes); + +#endif // SUBPARSER_H_INCLUDED diff --git a/src/script/cron.cpp b/src/script/cron.cpp new file mode 100644 index 0000000..05c6c85 --- /dev/null +++ b/src/script/cron.cpp @@ -0,0 +1,135 @@ +#include +#include +#include + +#include "../handler/interfaces.h" +#include "../handler/multithread.h" +#include "../server/webserver.h" +#include "../utils/logger.h" +#include "../utils/rapidjson_extra.h" +#include "../utils/system.h" +#include "script_quickjs.h" + +extern bool gEnableCron; +extern string_array gCronTasks; +extern std::string gProxyConfig, gAccessToken; +extern int gCacheConfig; + +libcron::Cron cron; + +static std::string parseProxy(const std::string &source) +{ + std::string proxy = source; + if(source == "SYSTEM") + proxy = getSystemProxy(); + else if(source == "NONE") + proxy = ""; + return proxy; +} + +struct script_info +{ + std::string name; + time_t begin_time = 0; + time_t timeout = 0; +}; + +int timeout_checker(JSRuntime *rt, void *opaque) +{ + script_info info = *((script_info*)opaque); + if(info.timeout != 0 && time(NULL) >= info.begin_time + info.timeout) /// timeout reached + { + writeLog(0, "Script '" + info.name + "' has exceeded timeout " + std::to_string(info.timeout) + ", terminate now.", LOG_LEVEL_WARNING); + return 1; + } + return 0; +} + +void refresh_schedule() +{ + cron.clear_schedules(); + for(std::string &x : gCronTasks) + { + string_array arguments = split(x, "`"); + if(arguments.size() < 3) + continue; + std::string &name = arguments[0], &cronexp = arguments[1], &path = arguments[2]; + cron.add_schedule(name, cronexp, [=](auto &) + { + qjs::Runtime runtime; + qjs::Context context(runtime); + try + { + script_runtime_init(runtime); + script_context_init(context); + defer(script_cleanup(context);) + std::string proxy = parseProxy(gProxyConfig); + std::string script = fetchFile(path, proxy, gCacheConfig); + if(script.empty()) + { + writeLog(0, "Script '" + name + "' run failed: file is empty or not exist!", LOG_LEVEL_WARNING); + return; + } + script_info info; + if(arguments.size() >= 4 && !arguments[3].empty()) + { + info.begin_time = time(NULL); + info.timeout = to_int(arguments[3], 0); + info.name = name; + JS_SetInterruptHandler(JS_GetRuntime(context.ctx), timeout_checker, &info); + } + context.eval(script); + } + catch (qjs::exception) + { + script_print_stack(context); + } + }); + } +} + +std::string list_cron_schedule(RESPONSE_CALLBACK_ARGS) +{ + std::string &argument = request.argument; + std::string token = getUrlArg(argument, "token"); + rapidjson::StringBuffer sb; + rapidjson::Writer writer(sb); + writer.StartObject(); + if(token != gAccessToken) + { + response.status_code = 403; + writer.Key("code"); + writer.Int(403); + writer.Key("data"); + writer.String("Unauthorized"); + writer.EndObject(); + return sb.GetString(); + } + writer.Key("code"); + writer.Int(200); + writer.Key("tasks"); + writer.StartArray(); + for(std::string &x : gCronTasks) + { + string_array arguments = split(x, "`"); + if(arguments.size() < 3) + continue; + writer.StartObject(); + std::string &name = arguments[0], &cronexp = arguments[1], &path = arguments[2]; + writer.Key("name"); + writer.String(name.data()); + writer.Key("cronexp"); + writer.String(cronexp.data()); + writer.Key("path"); + writer.String(path.data()); + writer.EndObject(); + } + writer.EndArray(); + writer.EndObject(); + return sb.GetString(); +} + +size_t cron_tick() +{ + return cron.tick(); +} diff --git a/src/script/cron.h b/src/script/cron.h new file mode 100644 index 0000000..d9a7a2b --- /dev/null +++ b/src/script/cron.h @@ -0,0 +1,7 @@ +#ifndef CRON_H_INCLUDED +#define CRON_H_INCLUDED + +void refresh_schedule(); +size_t cron_tick(); + +#endif // CRON_H_INCLUDED diff --git a/src/script.cpp b/src/script/script.cpp similarity index 96% rename from src/script.cpp rename to src/script/script.cpp index 8ca0b57..02161fb 100644 --- a/src/script.cpp +++ b/src/script/script.cpp @@ -3,11 +3,12 @@ #include #include -#include "misc.h" -#include "multithread.h" -#include "nodeinfo.h" -#include "socket.h" -#include "webget.h" +#include "../utils/string.h" +#include "../utils/string_hash.h" +#include "../handler/webget.h" +#include "../handler/multithread.h" +#include "../utils/base64/base64.h" +#include "../utils/network.h" extern int gCacheConfig; extern std::string gProxyConfig; @@ -132,14 +133,14 @@ static duk_ret_t fetch(duk_context *ctx) static duk_ret_t atob(duk_context *ctx) { std::string data = duk_safe_to_string(ctx, -1); - duk_push_string(ctx, base64_encode(data).c_str()); + duk_push_string(ctx, base64Encode(data).c_str()); return 1; } static duk_ret_t btoa(duk_context *ctx) { std::string data = duk_safe_to_string(ctx, -1); - data = base64_decode(data, true); + data = base64Decode(data, true); duk_push_lstring(ctx, data.c_str(), data.size()); return 1; } diff --git a/src/script/script.h b/src/script/script.h new file mode 100644 index 0000000..e6014e4 --- /dev/null +++ b/src/script/script.h @@ -0,0 +1,22 @@ +#ifndef SCRIPT_H_INCLUDED +#define SCRIPT_H_INCLUDED + +#include +#include + +template int evalScript(const std::string &script, return_type &return_value, input_type&... input_value) +{ + chaiscript::ChaiScript chai; + try + { + auto fun = chai.eval>(script); + return_value = fun(input_value...); + } + catch (std::exception&) + { + return -1; + } + return 0; +} + +#endif // SCRIPT_H_INCLUDED diff --git a/src/script_duktape.h b/src/script/script_duktape.h similarity index 84% rename from src/script_duktape.h rename to src/script/script_duktape.h index f031f0d..ac9c57b 100644 --- a/src/script_duktape.h +++ b/src/script/script_duktape.h @@ -5,6 +5,7 @@ #include #include "nodeinfo.h" +#include "misc.h" duk_context *duktape_init(); int duktape_push_nodeinfo(duk_context *ctx, const nodeInfo &node); @@ -16,4 +17,8 @@ std::string duktape_get_res_str(duk_context *ctx); bool duktape_get_res_bool(duk_context *ctx); std::string duktape_get_err_stack(duk_context *ctx); +#define SCRIPT_ENGINE_INIT(name) \ + duk_context* name = duktape_init(); \ + defer(duk_destroy_heap(name);) + #endif // SCRIPT_DUKTAPE_H_INCLUDED diff --git a/src/script/script_quickjs.cpp b/src/script/script_quickjs.cpp new file mode 100644 index 0000000..ead3274 --- /dev/null +++ b/src/script/script_quickjs.cpp @@ -0,0 +1,551 @@ +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif // _WIN32 + +#include "../handler/multithread.h" +#include "../handler/webget.h" +#include "../parser/config/proxy.h" +#include "../utils/map_extra.h" +#include "../utils/system.h" +#include "script_quickjs.h" + +extern int gCacheConfig; +extern std::string gProxyConfig; + +std::string parseProxy(const std::string &source); + +static const std::string qjs_require_module {R"(import * as std from 'std' +import * as os from 'os' + +let modules = {} + +let debug = console.log +{ + let _debugOptions = std.getenv('DEBUG') + if (typeof _debugOptions == 'undefined' || _debugOptions.split(',').indexOf('require') === -1) { + debug = function () {} + } +} + +class CJSModule { + constructor (id) { + this.id = id + this._failed = null + this._loaded = false + this.exports = {} + } + + load () { + const __file = this.id + const __dir = _basename(this.id) + const _require = require + + let ctx = { exports: {} } + // Prevents modules from changing exports + Object.seal(ctx) + + const _mark = '<