diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3f415e1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +tab_width = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{c,cc,cpp,h,hpp}] +indent_style = space +indent_size = 4 + +[*.{sma,inc}] +indent_style = space +indent_size = 4 + +[CMakeLists.txt] +indent_style = space +indent_size = 4 + +[*.cmake] +indent_style = space +indent_size = 4 diff --git a/CMakeLists.txt b/CMakeLists.txt index d0294a2..31316fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ add_subdirectory(src) if (GCC) target_compile_options(cpr PRIVATE + -Wno-attribute-warning -Wno-error=attribute-warning ) endif () diff --git a/README.md b/README.md index eb688df..c06c620 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,14 @@ public http_complete(EzHttpRequest:request_id) ezhttp_save_data_to_file(request_id, fmt("addons/amxmodx/response_%d.json", request_id)) } +// ezhttp_create_options() now uses auto_destroy = true by default. +// The handle is cleaned up automatically after the last request using it finishes. +// If you want to keep the same options handle alive for later reuse, create it like this: +// +// new EzHttpOptions:reusable = ezhttp_create_options(.auto_destroy = false) +// ... +// ezhttp_destroy_options(reusable) + // -------------------------------------------------------------------- public ftp_upload() diff --git a/amxx/scripting/include/easy_http.inc b/amxx/scripting/include/easy_http.inc index 6bf87c7..f112e32 100644 --- a/amxx/scripting/include/easy_http.inc +++ b/amxx/scripting/include/easy_http.inc @@ -56,14 +56,20 @@ enum EzHttpPluginEndBehaviour * Creates new options object. This object allows you to configure your request by specifying * such parameters as user agent, query parameters, headers, and etc. * Options are reusable request templates. Their values are copied into a request when it is sent. + * By default the options handle is destroyed automatically when the last request using it finishes. + * + * @param auto_destroy When true, the handle is released automatically after it becomes idle. + * Set to false when you want to keep reusing the same handle across + * multiple request lifecycles and destroy it manually later. * * @return EzHttpOptions handle. */ -native EzHttpOptions:ezhttp_create_options(); +native EzHttpOptions:ezhttp_create_options(bool:auto_destroy = true); /** * Destroys an options object previously created via ezhttp_create_options(). * It is safe to destroy an options object after sending a request because the request keeps its own snapshot. + * This is mainly useful when ezhttp_create_options(.auto_destroy = false) was used or when you want eager cleanup. * * @param options_id Options identifier created via ezhttp_create_options(). * @@ -235,6 +241,7 @@ native ezhttp_option_set_user_data(EzHttpOptions:options_id, const data[], len); * * EZH_CANCEL_REQUEST to cancel the request; * * EZH_FORGET_REQUEST to complete the request, but ignore its result * (callback will not be called). + * Passing any other value raises a native error. * * @noreturn */ @@ -578,6 +585,7 @@ native ezhttp_get_user_data(EzHttpRequest:request_id, data[]); * @param on_complete The function to call when the upload is complete. * Signature: public on_complete(EzHttpRequest:request_id) * @param security Member of EzHttpFtpSecurity. The security strategy use for the FTP connection. + * Passing any other value raises a native error. * @param options_id The options to use for the request. * * @return The request handle. @@ -601,6 +609,7 @@ native EzHttpRequest:ezhttp_ftp_upload( * @param on_complete The function to call when the upload is complete. * Signature: public on_complete(EzHttpRequest:request_id) * @param security Member of EzHttpFtpSecurity. The security strategy use for the FTP connection. + * Passing any other value raises a native error. * @param options_id The options to use for the request. * * @return The request handle. @@ -625,6 +634,7 @@ native EzHttpRequest:ezhttp_ftp_upload2( * @param on_complete The function to call when the download is complete. * Signature: public on_complete(EzHttpRequest:request_id) * @param security Member of EzHttpFtpSecurity. The security strategy use for the FTP connection. + * Passing any other value raises a native error. * @param options_id The options to use for the request. * * @return The request handle. @@ -650,6 +660,7 @@ native EzHttpRequest:ezhttp_ftp_download( * @param on_complete The function to call when the download is complete. * Signature: public on_complete(EzHttpRequest:request_id) * @param security Member of EzHttpFtpSecurity. The security strategy use for the FTP connection. + * Passing any other value raises a native error. * @param options_id The options to use for the request. * * @return The request handle. diff --git a/amxx_test/scripting/ez_http_test.sma b/amxx_test/scripting/ez_http_test.sma index 5f606e9..bfccb2f 100644 --- a/amxx_test/scripting/ez_http_test.sma +++ b/amxx_test/scripting/ez_http_test.sma @@ -24,7 +24,9 @@ TEST_LIST_ASYNC = { { "test_save_to_file", "test save to file" }, { "test_fail_by_timeout", "test timeout" }, { "test_options_reuse_concurrent", "test reusing the same options in concurrent requests" }, + { "test_options_reuse_sequential", "test reusing the same options after completion when auto destroy is disabled" }, { "test_user_data_snapshot", "test request keeps user data snapshot from dispatch time" }, + { "test_options_auto_destroy_default", "test options auto destroy after becoming idle by default" }, { "test_destroy_options_after_dispatch", "test options can be destroyed after dispatch" }, { "test_ftp_download", "test ftp download" }, { "test_ftp_download_wildcard", "test ftp download wildcard" }, @@ -47,10 +49,17 @@ new g_TestOptionsReuseConcurrent[_TestT]; new g_TestOptionsReuseConcurrentCompleted = 0; new bool:g_TestOptionsReuseConcurrentFinished = false; +new g_TestOptionsReuseSequential[_TestT]; +new EzHttpOptions:g_TestOptionsReuseSequentialHandle = EzHttpOptions:0; +new bool:g_TestOptionsReuseSequentialSecondDispatchStarted = false; + new g_TestUserDataSnapshot[_TestT]; new g_TestUserDataSnapshotCompleted = 0; new bool:g_TestUserDataSnapshotFinished = false; +new g_TestOptionsAutoDestroyDefault[_TestT]; +new EzHttpOptions:g_TestOptionsAutoDestroyDefaultHandle = EzHttpOptions:0; + new bool:g_TestDestroyOptionsAfterDispatchDestroyed = false; new EzHttpRequest:g_TestDestroyOptionsAfterDispatchRequestId = EzHttpRequest:0; @@ -129,12 +138,38 @@ stock build_test_url(url[], max_len, const path[]) formatex(url[len], max_len - len, "%s", path); } -stock cleanup_ftp_wildcard_download_dir() +stock bool:cleanup_ftp_wildcard_download_dir() { - delete_file(FTP_WILDCARD_FILE_1); - delete_file(FTP_WILDCARD_FILE_2); - delete_file(FTP_WILDCARD_FILE_3); + if (!dir_exists(FTP_WILDCARD_DOWNLOAD_DIR)) + { + return true; + } + + // ftp.gnu.org can add new hello-2.12.x signature files over time, + // so cleanup enumerates the directory instead of deleting a fixed list. + new entry[PLATFORM_MAX_PATH]; + new full_path[PLATFORM_MAX_PATH]; + new FileType:type; + new dir = open_dir(FTP_WILDCARD_DOWNLOAD_DIR, entry, charsmax(entry), type); + + if (dir) + { + do + { + if (!equal(entry, ".") && !equal(entry, "..") && type == FileType_File) + { + formatex(full_path, charsmax(full_path), "%s/%s", FTP_WILDCARD_DOWNLOAD_DIR, entry); + delete_file(full_path); + } + } + while (next_file(dir, entry, charsmax(entry), type)); + + close_dir(dir); + } + rmdir(FTP_WILDCARD_DOWNLOAD_DIR); + + return !dir_exists(FTP_WILDCARD_DOWNLOAD_DIR); } public plugin_init() @@ -150,6 +185,7 @@ public plugin_init() public plugin_end() { + cleanup_ftp_wildcard_download_dir(); log_amx("[ez_http_test] plugin_end"); } @@ -158,14 +194,14 @@ public run_tests() UTEST_RUN_ASYNC(UT_VERBOSE) } -START_ASYNC_TEST(test_get_parameters) +START_ASYNC_TEST(test_get_parameters) { new EzHttpOptions:opt = ezhttp_create_options(); new url[256]; ezhttp_option_add_url_parameter(opt, "MyParam1", "ParamVal1"); ezhttp_option_add_url_parameter(opt, "MyParam2", "ParamVal2"); - + EZHTTP_OPTION_SET_TEST_DATA(opt) build_test_url(url, charsmax(url), "/get"); @@ -186,7 +222,7 @@ public test_get_parameters_complete(EzHttpRequest:request_id) } new EzJSON:json_args = ezjson_object_get_value(json_root, "args"); - + server_print("test_get_parameters request elapsed: %f", ezhttp_get_elapsed(request_id)); // asserts @@ -320,6 +356,102 @@ public test_options_reuse_concurrent_complete(EzHttpRequest:request_id, const da } } +START_ASYNC_TEST(test_options_reuse_sequential) +{ + copy_test_state(g_TestOptionsReuseSequential, __test); + g_TestOptionsReuseSequentialSecondDispatchStarted = false; + g_TestOptionsReuseSequentialHandle = ezhttp_create_options(false); + + ezhttp_option_set_header(g_TestOptionsReuseSequentialHandle, "X-Reused-Sequential", "shared-sequential"); + + new url[256]; + build_test_url(url, charsmax(url), "/delay/1?request=reuse_seq_a"); + + new EzHttpRequest:request_id = ezhttp_get( + url, + "test_options_reuse_sequential_complete", + g_TestOptionsReuseSequentialHandle + ); + + _test_assert(g_TestOptionsReuseSequential, request_id != EzHttpRequest:0, __LINE__, "request id must not be zero"); + + if (request_id == EzHttpRequest:0) + { + ezhttp_destroy_options(g_TestOptionsReuseSequentialHandle); + g_TestOptionsReuseSequentialHandle = EzHttpOptions:0; + finish_async_aggregate_test(g_TestOptionsReuseSequential); + } +} + +public test_options_reuse_sequential_complete(EzHttpRequest:request_id) +{ + new EzJSON:json_root; + if (!prepare_json_response(g_TestOptionsReuseSequential, request_id, json_root, __LINE__)) + { + if (g_TestOptionsReuseSequentialHandle != EzHttpOptions:0) + { + ezhttp_destroy_options(g_TestOptionsReuseSequentialHandle); + g_TestOptionsReuseSequentialHandle = EzHttpOptions:0; + } + + finish_async_aggregate_test(g_TestOptionsReuseSequential); + return; + } + + new EzJSON:json_args = ezjson_object_get_value(json_root, "args"); + new EzJSON:json_headers = ezjson_object_get_value(json_root, "headers"); + new request_name[64]; + new header_value[64]; + + ezjson_object_get_string(json_args, "request", request_name, charsmax(request_name)); + ezjson_object_get_string(json_headers, "X-Reused-Sequential", header_value, charsmax(header_value)); + + _test_assert(g_TestOptionsReuseSequential, equal(header_value, "shared-sequential"), __LINE__, "sequential reuse must preserve headers"); + + if (!g_TestOptionsReuseSequentialSecondDispatchStarted) + { + _test_assert(g_TestOptionsReuseSequential, equal(request_name, "reuse_seq_a"), __LINE__, "first sequential callback returned unexpected marker"); + + ezjson_free(json_headers); + ezjson_free(json_args); + ezjson_free(json_root); + + g_TestOptionsReuseSequentialSecondDispatchStarted = true; + + new url[256]; + build_test_url(url, charsmax(url), "/delay/1?request=reuse_seq_b"); + + new EzHttpRequest:request_b = ezhttp_get( + url, + "test_options_reuse_sequential_complete", + g_TestOptionsReuseSequentialHandle + ); + + _test_assert(g_TestOptionsReuseSequential, request_b != EzHttpRequest:0, __LINE__, "second sequential request id must not be zero"); + + if (request_b == EzHttpRequest:0) + { + ezhttp_destroy_options(g_TestOptionsReuseSequentialHandle); + g_TestOptionsReuseSequentialHandle = EzHttpOptions:0; + finish_async_aggregate_test(g_TestOptionsReuseSequential); + } + + return; + } + + _test_assert(g_TestOptionsReuseSequential, equal(request_name, "reuse_seq_b"), __LINE__, "second sequential callback returned unexpected marker"); + + ezjson_free(json_headers); + ezjson_free(json_args); + ezjson_free(json_root); + + new bool:destroyed = ezhttp_destroy_options(g_TestOptionsReuseSequentialHandle); + _test_assert(g_TestOptionsReuseSequential, destroyed, __LINE__, "options with auto destroy disabled must still require explicit cleanup"); + + g_TestOptionsReuseSequentialHandle = EzHttpOptions:0; + finish_async_aggregate_test(g_TestOptionsReuseSequential); +} + START_ASYNC_TEST(test_user_data_snapshot) { copy_test_state(g_TestUserDataSnapshot, __test); @@ -424,7 +556,7 @@ public test_post_form_complete(EzHttpRequest:request_id) new tmp_data[256]; ASSERT_TRUE(ezjson_object_get_count(json_form) == 2); - + ezjson_object_get_string(json_form, "MyFormEntry1", tmp_data, charsmax(tmp_data)); ASSERT_TRUE(equal(tmp_data, "FormVal1")); @@ -766,6 +898,71 @@ public test_ftp_download_complete(EzHttpRequest:request_id) END_ASYNC_TEST() } +START_ASYNC_TEST(test_options_auto_destroy_default) +{ + copy_test_state(g_TestOptionsAutoDestroyDefault, __test); + g_TestOptionsAutoDestroyDefaultHandle = ezhttp_create_options(); + + ezhttp_option_set_header(g_TestOptionsAutoDestroyDefaultHandle, "X-Auto-Destroy-Option", "idle-cleanup"); + + new url[256]; + build_test_url(url, charsmax(url), "/delay/1?request=auto_destroy_default"); + + new EzHttpRequest:request_id = ezhttp_get( + url, + "test_options_auto_destroy_default_complete", + g_TestOptionsAutoDestroyDefaultHandle + ); + + _test_assert(g_TestOptionsAutoDestroyDefault, request_id != EzHttpRequest:0, __LINE__, "request id must not be zero"); + + if (request_id == EzHttpRequest:0) + { + g_TestOptionsAutoDestroyDefaultHandle = EzHttpOptions:0; + finish_async_aggregate_test(g_TestOptionsAutoDestroyDefault); + } +} + +public test_options_auto_destroy_default_complete(EzHttpRequest:request_id) +{ + new EzJSON:json_root; + if (!prepare_json_response(g_TestOptionsAutoDestroyDefault, request_id, json_root, __LINE__)) + { + g_TestOptionsAutoDestroyDefaultHandle = EzHttpOptions:0; + finish_async_aggregate_test(g_TestOptionsAutoDestroyDefault); + return; + } + + new EzJSON:json_args = ezjson_object_get_value(json_root, "args"); + new EzJSON:json_headers = ezjson_object_get_value(json_root, "headers"); + new request_name[64]; + new header_value[64]; + + ezjson_object_get_string(json_args, "request", request_name, charsmax(request_name)); + ezjson_object_get_string(json_headers, "X-Auto-Destroy-Option", header_value, charsmax(header_value)); + + _test_assert(g_TestOptionsAutoDestroyDefault, equal(request_name, "auto_destroy_default"), __LINE__, "auto destroy request marker mismatch"); + _test_assert(g_TestOptionsAutoDestroyDefault, equal(header_value, "idle-cleanup"), __LINE__, "auto destroy request lost its header"); + + ezjson_free(json_headers); + ezjson_free(json_args); + ezjson_free(json_root); + + new EzHttpOptions:reused_handle = ezhttp_create_options(false); + _test_assert( + g_TestOptionsAutoDestroyDefault, + reused_handle == g_TestOptionsAutoDestroyDefaultHandle, + __LINE__, + "default auto destroy must release the options handle after the request becomes idle" + ); + + if (reused_handle != EzHttpOptions:0) + ezhttp_destroy_options(reused_handle); + + g_TestOptionsAutoDestroyDefaultHandle = EzHttpOptions:0; + finish_async_aggregate_test(g_TestOptionsAutoDestroyDefault); +} + START_ASYNC_TEST(test_destroy_options_after_dispatch) { new EzHttpOptions:opt = ezhttp_create_options(); @@ -796,7 +993,7 @@ public test_destroy_options_after_dispatch_complete(EzHttpRequest:request_id) ASSERT_TRUE_MSG(g_TestDestroyOptionsAfterDispatchDestroyed, "options must be destroyable after dispatch"); ASSERT_INT_EQ(g_TestDestroyOptionsAfterDispatchRequestId, request_id); - + new EzJSON:json_root; if (!prepare_json_response(__test, request_id, json_root, __LINE__)) { @@ -827,6 +1024,7 @@ START_ASYNC_TEST(test_ftp_download_wildcard) new EzHttpOptions:opt = ezhttp_create_options(); cleanup_ftp_wildcard_download_dir(); + ASSERT_TRUE_MSG(!dir_exists(FTP_WILDCARD_DOWNLOAD_DIR), "failed to clean leftover wildcard download directory before test"); EZHTTP_OPTION_SET_TEST_DATA(opt) ezhttp_ftp_download("", "", "ftp.gnu.org", "gnu/hello/hello-2.12*.tar.gz.sig", FTP_WILDCARD_DOWNLOAD_DIR, "test_ftp_download_wildcard_complete", EZH_UNSECURE, opt); @@ -845,6 +1043,7 @@ public test_ftp_download_wildcard_complete(EzHttpRequest:request_id) ASSERT_TRUE_MSG(file_exists(FTP_WILDCARD_FILE_3), "expected hello-2.12.2.tar.gz.sig to be downloaded"); cleanup_ftp_wildcard_download_dir(); + ASSERT_TRUE_MSG(!dir_exists(FTP_WILDCARD_DOWNLOAD_DIR), "wildcard download directory cleanup failed"); END_ASYNC_TEST() } diff --git a/src/EasyHttpModule.cpp b/src/EasyHttpModule.cpp index f2981fd..a585997 100644 --- a/src/EasyHttpModule.cpp +++ b/src/EasyHttpModule.cpp @@ -1,12 +1,27 @@ #include "EasyHttpModule.h" #include "easy_http/EasyHttp.h" #include "utils/TraceLog.h" +#include #include using namespace ezhttp; -EasyHttpModule::EasyHttpModule(std::string ca_cert_path) : - ca_cert_path_(std::move(ca_cert_path)) +namespace +{ + bool IsValidPluginEndBehaviour(PluginEndBehaviour behaviour) + { + switch (behaviour) + { + case PluginEndBehaviour::CancelRequests: + case PluginEndBehaviour::ForgetRequests: + return true; + } + + return false; + } +} + +EasyHttpModule::EasyHttpModule(std::string ca_cert_path) : ca_cert_path_(std::move(ca_cert_path)) { // as this is a first insertion in queue then these EasyHttps will have QueueId == 1 and therefore QueueId == QueueId::Main CreateQueue(); @@ -38,10 +53,39 @@ void EasyHttpModule::ServerDeactivate() ezhttp::trace::Writef("EasyHttpModule", "ServerDeactivate exit requests=%zu queues=%zu forgotten=%zu options=%zu", requests_.size(), easy_http_pack_.size(), forgotten_easy_http_.size(), options_.size()); } -RequestId EasyHttpModule::SendRequest(RequestMethod method, const std::string& url, OptionsData options, int callback_id, std::unique_ptr callback_data, int callback_data_len) +RequestId EasyHttpModule::SendRequest( + RequestMethod method, + const std::string &url, + OptionsData options, + int callback_id, + std::unique_ptr callback_data, + int callback_data_len, + OptionsId source_options_id) { + QueueId queue_id = options.queue_id; + if (!IsQueueExists(queue_id)) + { + ezhttp::trace::Writef( + "EasyHttpModule", + "SendRequest rejected invalid queue=%d url=%s", + static_cast(queue_id), + url.c_str()); + return RequestId::Null; + } + + if (!IsValidPluginEndBehaviour(options.plugin_end_behaviour)) + { + ezhttp::trace::Writef( + "EasyHttpModule", + "SendRequest rejected invalid plugin_end_behaviour=%d queue=%d url=%s", + static_cast(options.plugin_end_behaviour), + static_cast(queue_id), + url.c_str()); + return RequestId::Null; + } + RequestId request_id = requests_.Add(RequestData()); - RequestData& request = GetRequest(request_id); + RequestData &request = GetRequest(request_id); const uint32_t request_generation = ++next_request_generation_; request.generation = request_generation; @@ -50,14 +94,14 @@ RequestId EasyHttpModule::SendRequest(RequestMethod method, const std::string& u request.callback_data_len = callback_data_len; request.callback_id = callback_id; - QueueId queue_id = options.queue_id; - if (!IsQueueExists(queue_id)) // not so good, error suppression is going on, need to fix this in future - queue_id = QueueId::Main; + if (source_options_id != OptionsId::Null) + TrackAutoDestroyOptions(request, source_options_id, options.generation); - auto& easy_http = GetEasyHttp(queue_id, options.plugin_end_behaviour); + auto &easy_http = GetEasyHttp(queue_id, options.plugin_end_behaviour); const RequestOptions request_options = options.options_builder.BuildOptions(); - EasyHttpInterface::ResponseCallback cb_proxy = [this, request_id, request_generation](const Response& response) { + EasyHttpInterface::ResponseCallback cb_proxy = [this, request_id, request_generation](const Response &response) + { ezhttp::trace::Writef("EasyHttpModule", "callback enter request=%d generation=%u", static_cast(request_id), request_generation); if (!IsRequestExists(request_id)) { @@ -65,7 +109,7 @@ RequestId EasyHttpModule::SendRequest(RequestMethod method, const std::string& u return; } - RequestData& current_request = GetRequest(request_id); + RequestData ¤t_request = GetRequest(request_id); if (current_request.generation != request_generation) { ezhttp::trace::Writef("EasyHttpModule", "callback skip generation mismatch request=%d current=%u expected=%u", static_cast(request_id), current_request.generation, request_generation); @@ -87,8 +131,7 @@ RequestId EasyHttpModule::SendRequest(RequestMethod method, const std::string& u static_cast(options.plugin_end_behaviour), callback_id, request.request_control.get(), - url.c_str() - ); + url.c_str()); return request_id; } @@ -98,9 +141,12 @@ bool EasyHttpModule::DeleteRequest(RequestId handle) return requests_.Remove(handle); } -OptionsId EasyHttpModule::CreateOptions() +OptionsId EasyHttpModule::CreateOptions(bool auto_destroy) { - return options_.Add(OptionsData{}); + OptionsData options; + options.generation = ++next_options_generation_; + options.auto_destroy = auto_destroy; + return options_.Add(std::move(options)); } bool EasyHttpModule::DeleteOptions(OptionsId handle) @@ -113,7 +159,8 @@ void EasyHttpModule::FinalizeRequest(RequestId handle) if (!IsRequestExists(handle)) return; - RequestData& request = GetRequest(handle); + RequestData &request = GetRequest(handle); + ReleaseAutoDestroyOptions(request); ezhttp::trace::Writef("EasyHttpModule", "FinalizeRequest request=%d callback_id=%d control=%p", static_cast(handle), request.callback_id, request.request_control.get()); if (request.callback_id != -1) { @@ -134,7 +181,7 @@ void EasyHttpModule::CleanupCompletedForgottenRequests() { for (auto it = requests_.begin(); it != requests_.end();) { - auto& request = it->second; + auto &request = it->second; if (!request.request_control || !request.request_control->completed.load() || !request.request_control->forgotten.load()) { ++it; @@ -144,18 +191,71 @@ void EasyHttpModule::CleanupCompletedForgottenRequests() if (request.callback_id != -1) MF_UnregisterSPForward(request.callback_id); + ReleaseAutoDestroyOptions(request); ezhttp::trace::Writef("EasyHttpModule", "CleanupCompletedForgottenRequests remove request=%d control=%p", static_cast(it->first), request.request_control.get()); it = requests_.Remove(it); } } +void EasyHttpModule::TrackAutoDestroyOptions(RequestData &request, OptionsId options_id, uint32_t options_generation) +{ + if (!IsOptionsExists(options_id)) + return; + + OptionsData &options = GetOptions(options_id); + if (options.generation != options_generation || !options.auto_destroy) + return; + + ++options.active_requests; + request.auto_destroy_options_id = options_id; + request.auto_destroy_options_generation = options_generation; + + ezhttp::trace::Writef( + "EasyHttpModule", + "TrackAutoDestroyOptions options=%d generation=%u active_requests=%zu", + static_cast(options_id), + options_generation, + options.active_requests); +} + +void EasyHttpModule::ReleaseAutoDestroyOptions(const RequestData &request) +{ + if (request.auto_destroy_options_id == OptionsId::Null || !IsOptionsExists(request.auto_destroy_options_id)) + return; + + OptionsData &options = GetOptions(request.auto_destroy_options_id); + if (options.generation != request.auto_destroy_options_generation || !options.auto_destroy) + return; + + if (options.active_requests == 0) + return; + + --options.active_requests; + ezhttp::trace::Writef( + "EasyHttpModule", + "ReleaseAutoDestroyOptions options=%d generation=%u active_requests=%zu", + static_cast(request.auto_destroy_options_id), + request.auto_destroy_options_generation, + options.active_requests); + + if (options.active_requests == 0) + { + DeleteOptions(request.auto_destroy_options_id); + ezhttp::trace::Writef( + "EasyHttpModule", + "ReleaseAutoDestroyOptions deleted options=%d generation=%u", + static_cast(request.auto_destroy_options_id), + request.auto_destroy_options_generation); + } +} + void EasyHttpModule::ShutdownWithoutCallbacks() { ezhttp::trace::Writef("EasyHttpModule", "ShutdownWithoutCallbacks begin forgotten=%zu queues=%zu requests=%zu options=%zu", forgotten_easy_http_.size(), easy_http_pack_.size(), requests_.size(), options_.size()); - for (auto& pack_kv : easy_http_pack_) + for (auto &pack_kv : easy_http_pack_) { - auto& terminating_ez = pack_kv.second.terminating_easy_http; - auto& forgettable_ez = pack_kv.second.forgettable_easy_http; + auto &terminating_ez = pack_kv.second.terminating_easy_http; + auto &forgettable_ez = pack_kv.second.forgettable_easy_http; if (terminating_ez) { @@ -170,7 +270,7 @@ void EasyHttpModule::ShutdownWithoutCallbacks() } } - for (auto& request_kv : requests_) + for (auto &request_kv : requests_) { request_kv.second.callback_id = -1; } @@ -181,19 +281,17 @@ void EasyHttpModule::ShutdownWithoutCallbacks() ezhttp::trace::Writef("EasyHttpModule", "ShutdownWithoutCallbacks end forgotten=%zu queues=%zu requests=%zu options=%zu", forgotten_easy_http_.size(), easy_http_pack_.size(), requests_.size(), options_.size()); } - void EasyHttpModule::ResetForMapChangeWithoutCallbacks() { ezhttp::trace::Writef("EasyHttpModule", "ResetForMapChangeWithoutCallbacks begin forgotten=%zu queues=%zu requests=%zu options=%zu", forgotten_easy_http_.size(), easy_http_pack_.size(), requests_.size(), options_.size()); - for (auto& request_kv : requests_) + for (auto &request_kv : requests_) { ezhttp::trace::Writef( "EasyHttpModule", "mapchange forget request=%d callback_id=%d control=%p", static_cast(request_kv.first), request_kv.second.callback_id, - request_kv.second.request_control.get() - ); + request_kv.second.request_control.get()); if (request_kv.second.callback_id != -1) { MF_UnregisterSPForward(request_kv.second.callback_id); @@ -204,7 +302,7 @@ void EasyHttpModule::ResetForMapChangeWithoutCallbacks() request_kv.second.request_control->forgotten.store(true); } - for (auto& forgotten_ez : forgotten_easy_http_) + for (auto &forgotten_ez : forgotten_easy_http_) { if (!forgotten_ez) continue; @@ -213,10 +311,10 @@ void EasyHttpModule::ResetForMapChangeWithoutCallbacks() forgotten_ez->DropCompletedRequestsWithoutCallbacks(); } - for (auto& pack_kv : easy_http_pack_) + for (auto &pack_kv : easy_http_pack_) { - auto& terminating_ez = pack_kv.second.terminating_easy_http; - auto& forgettable_ez = pack_kv.second.forgettable_easy_http; + auto &terminating_ez = pack_kv.second.terminating_easy_http; + auto &forgettable_ez = pack_kv.second.forgettable_easy_http; if (terminating_ez) { @@ -243,10 +341,10 @@ void EasyHttpModule::ResetForMapChangeWithoutCallbacks() void EasyHttpModule::RunFrameEasyHttp() { - for (auto& pack_kv : easy_http_pack_) + for (auto &pack_kv : easy_http_pack_) { - auto& terminating_ez = pack_kv.second.terminating_easy_http; - auto& forgettable_ez = pack_kv.second.forgettable_easy_http; + auto &terminating_ez = pack_kv.second.terminating_easy_http; + auto &forgettable_ez = pack_kv.second.forgettable_easy_http; if (terminating_ez) terminating_ez->RunFrame(); @@ -258,7 +356,7 @@ void EasyHttpModule::RunFrameEasyHttp() void EasyHttpModule::RunCleanupFrameForForgottenEasyHttp() { - for (auto it = forgotten_easy_http_.begin(); it != forgotten_easy_http_.end(); ) + for (auto it = forgotten_easy_http_.begin(); it != forgotten_easy_http_.end();) { it->get()->DropCompletedRequestsWithoutCallbacks(); @@ -272,24 +370,35 @@ void EasyHttpModule::RunCleanupFrameForForgottenEasyHttp() } } -std::unique_ptr& EasyHttpModule::GetEasyHttp(QueueId queue_id, PluginEndBehaviour end_map_behaviour) +std::unique_ptr &EasyHttpModule::GetEasyHttp(QueueId queue_id, PluginEndBehaviour end_map_behaviour) { - EasyHttpPack& easy_http_pack = easy_http_pack_.at(queue_id); + EasyHttpPack &easy_http_pack = easy_http_pack_.at(queue_id); int easy_http_threads = queue_id == QueueId::Main ? kMainQueueThreads : 1; switch (end_map_behaviour) { - default: - case PluginEndBehaviour::CancelRequests: - if (!easy_http_pack.terminating_easy_http) - easy_http_pack.terminating_easy_http = std::make_unique(ca_cert_path_, easy_http_threads); - return easy_http_pack.terminating_easy_http; - - case PluginEndBehaviour::ForgetRequests: - if (!easy_http_pack.forgettable_easy_http) - easy_http_pack.forgettable_easy_http = std::make_unique(ca_cert_path_, easy_http_threads); - return easy_http_pack.forgettable_easy_http; + case PluginEndBehaviour::CancelRequests: + if (!easy_http_pack.terminating_easy_http) + easy_http_pack.terminating_easy_http = std::make_unique(ca_cert_path_, easy_http_threads); + return easy_http_pack.terminating_easy_http; + + case PluginEndBehaviour::ForgetRequests: + if (!easy_http_pack.forgettable_easy_http) + easy_http_pack.forgettable_easy_http = std::make_unique(ca_cert_path_, easy_http_threads); + return easy_http_pack.forgettable_easy_http; } + + ezhttp::trace::Writef( + "EasyHttpModule", + "GetEasyHttp reached unreachable plugin_end_behaviour=%d queue=%d", + static_cast(end_map_behaviour), + static_cast(queue_id)); + assert(false && "GetEasyHttp received an unsupported plugin end behaviour"); + + if (!easy_http_pack.terminating_easy_http) + easy_http_pack.terminating_easy_http = std::make_unique(ca_cert_path_, easy_http_threads); + + return easy_http_pack.terminating_easy_http; } QueueId EasyHttpModule::CreateQueue() diff --git a/src/EasyHttpModule.h b/src/EasyHttpModule.h index b0f16a2..fe79899 100644 --- a/src/EasyHttpModule.h +++ b/src/EasyHttpModule.h @@ -14,20 +14,32 @@ enum class PluginEndBehaviour ForgetRequests }; -enum class OptionsId : int { Null = 0 }; -enum class RequestId : int { Null = 0 }; -enum class QueueId : int { Null = 0, Main = 1 }; +enum class OptionsId : int +{ + Null = 0 +}; +enum class RequestId : int +{ + Null = 0 +}; +enum class QueueId : int +{ + Null = 0, + Main = 1 +}; struct OptionsData { + uint32_t generation = 0; ezhttp::EasyHttpOptionsBuilder options_builder; std::optional> user_data; PluginEndBehaviour plugin_end_behaviour = PluginEndBehaviour::CancelRequests; QueueId queue_id = QueueId::Main; + bool auto_destroy = true; + size_t active_requests = 0; }; - struct RequestData { uint32_t generation = 0; @@ -37,6 +49,8 @@ struct RequestData std::unique_ptr callback_data; int callback_data_len = 0; int callback_id = -1; + OptionsId auto_destroy_options_id = OptionsId::Null; + uint32_t auto_destroy_options_generation = 0; }; struct EasyHttpPack @@ -46,16 +60,16 @@ struct EasyHttpPack EasyHttpPack() = default; - EasyHttpPack(const EasyHttpPack& other) = delete; - EasyHttpPack& operator=(const EasyHttpPack& other) = delete; + EasyHttpPack(const EasyHttpPack &other) = delete; + EasyHttpPack &operator=(const EasyHttpPack &other) = delete; - EasyHttpPack(EasyHttpPack&& other) noexcept + EasyHttpPack(EasyHttpPack &&other) noexcept { forgettable_easy_http = std::move(other.forgettable_easy_http); terminating_easy_http = std::move(other.terminating_easy_http); } - EasyHttpPack& operator=(EasyHttpPack&& other) noexcept + EasyHttpPack &operator=(EasyHttpPack &&other) noexcept { forgettable_easy_http = std::move(other.forgettable_easy_http); terminating_easy_http = std::move(other.terminating_easy_http); @@ -69,6 +83,7 @@ class EasyHttpModule std::string ca_cert_path_; uint32_t next_request_generation_ = 0; + uint32_t next_options_generation_ = 0; std::vector> forgotten_easy_http_; utils::ContainerWithHandles easy_http_pack_; @@ -84,24 +99,24 @@ class EasyHttpModule RequestId SendRequest( ezhttp::RequestMethod method, - const std::string& url, + const std::string &url, OptionsData options, int callback_id = -1, std::unique_ptr callback_data = nullptr, - int callback_data_len = 0 - ); + int callback_data_len = 0, + OptionsId source_options_id = OptionsId::Null); bool DeleteRequest(RequestId handle); [[nodiscard]] bool IsRequestExists(RequestId handle) { return requests_.contains(handle); } - [[nodiscard]] RequestData& GetRequest(RequestId handle) { return requests_.at(handle); } - [[nodiscard]] const RequestData& GetRequest(RequestId handle) const { return requests_.at(handle); } + [[nodiscard]] RequestData &GetRequest(RequestId handle) { return requests_.at(handle); } + [[nodiscard]] const RequestData &GetRequest(RequestId handle) const { return requests_.at(handle); } - OptionsId CreateOptions(); + OptionsId CreateOptions(bool auto_destroy = true); bool DeleteOptions(OptionsId handle); [[nodiscard]] bool IsOptionsExists(OptionsId handle) const { return options_.contains(handle); } - [[nodiscard]] OptionsData& GetOptions(OptionsId handle) { return options_.at(handle); } - [[nodiscard]] const OptionsData& GetOptions(OptionsId handle) const { return options_.at(handle); } - [[nodiscard]] ezhttp::EasyHttpOptionsBuilder& GetOptionsBuilder(OptionsId handle) { return options_.at(handle).options_builder; } + [[nodiscard]] OptionsData &GetOptions(OptionsId handle) { return options_.at(handle); } + [[nodiscard]] const OptionsData &GetOptions(OptionsId handle) const { return options_.at(handle); } + [[nodiscard]] ezhttp::EasyHttpOptionsBuilder &GetOptionsBuilder(OptionsId handle) { return options_.at(handle).options_builder; } [[nodiscard]] OptionsData CreateOptionsSnapshot(OptionsId handle) const { return options_.at(handle); } QueueId CreateQueue(); @@ -110,10 +125,11 @@ class EasyHttpModule private: void FinalizeRequest(RequestId handle); void CleanupCompletedForgottenRequests(); + void TrackAutoDestroyOptions(RequestData &request, OptionsId options_id, uint32_t options_generation); + void ReleaseAutoDestroyOptions(const RequestData &request); void ShutdownWithoutCallbacks(); void ResetForMapChangeWithoutCallbacks(); void RunFrameEasyHttp(); void RunCleanupFrameForForgottenEasyHttp(); - std::unique_ptr& GetEasyHttp(QueueId queue_id, PluginEndBehaviour end_map_behaviour); + std::unique_ptr &GetEasyHttp(QueueId queue_id, PluginEndBehaviour end_map_behaviour); }; - diff --git a/src/easy_http/EasyHttp.cpp b/src/easy_http/EasyHttp.cpp index 812a975..42597cd 100644 --- a/src/easy_http/EasyHttp.cpp +++ b/src/easy_http/EasyHttp.cpp @@ -390,6 +390,14 @@ Response EasyHttp::SendRequest(const std::shared_ptr &request_co Response response; switch (method) { + case RequestMethod::HttpGet: + case RequestMethod::HttpPost: + case RequestMethod::HttpPut: + case RequestMethod::HttpPatch: + case RequestMethod::HttpDelete: + response = SendHttpRequest(*session, request_control, method, url, options); + break; + case RequestMethod::FtpUpload: response = FtpUpload(*session, request_control, url, options); break; @@ -399,7 +407,7 @@ Response EasyHttp::SendRequest(const std::shared_ptr &request_co break; default: - response = SendHttpRequest(*session, request_control, method, url, options); + response = CreateErrorResponse(url, cpr::ErrorCode::INTERNAL_ERROR, "Unsupported request method"); break; } @@ -461,6 +469,10 @@ Response EasyHttp::SendHttpRequest(cpr::Session &session, const std::shared_ptr< Response response; switch (method) { + case RequestMethod::HttpGet: + response = Response(session.Get()); + break; + case RequestMethod::HttpPost: response = Response(session.Post()); break; @@ -478,7 +490,7 @@ Response EasyHttp::SendHttpRequest(cpr::Session &session, const std::shared_ptr< break; default: - response = Response(session.Get()); + response = CreateErrorResponse(url, cpr::ErrorCode::INTERNAL_ERROR, "Unsupported HTTP request method"); break; } diff --git a/src/module.cpp b/src/module.cpp index 4b4876c..68f7117 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -14,22 +14,26 @@ using namespace ezhttp; -bool ValidateOptionsId(AMX* amx, OptionsId options_id); -bool ValidateRequestId(AMX* amx, RequestId request_id); -bool ValidateQueueId(AMX* amx, QueueId queue_id); -template void SetKeyValueOption(AMX* amx, cell* params, TMethod method); -template void SetStringOption(AMX* amx, cell* params, TMethod method); -using OptionsConfigurer = std::function; +bool ValidateOptionsId(AMX *amx, OptionsId options_id); +bool ValidateRequestId(AMX *amx, RequestId request_id); +bool ValidateQueueId(AMX *amx, QueueId queue_id); +bool ValidatePluginEndBehaviour(AMX *amx, PluginEndBehaviour plugin_end_behaviour); +bool ValidateFtpSecurity(AMX *amx, cell security_value); +bool ValidateDispatchOptions(AMX *amx, const OptionsData &options); +template +void SetKeyValueOption(AMX *amx, cell *params, TMethod method); +template +void SetStringOption(AMX *amx, cell *params, TMethod method); +using OptionsConfigurer = std::function; RequestId DispatchRequest( - AMX* amx, + AMX *amx, RequestMethod method, OptionsId options_id, - const std::string& url, - const std::string& callback, + const std::string &url, + const std::string &callback, std::unique_ptr data = nullptr, int data_len = 0, - const OptionsConfigurer& configure = {} -); + const OptionsConfigurer &configure = {}); std::unique_ptr g_EasyHttpModule; std::unique_ptr g_JsonManager; @@ -37,7 +41,7 @@ bool g_MapChangeResetDone = false; namespace { - cvar_t cvar_ezhttp_trace = { "ezhttp_trace_log", "0", FCVAR_SERVER | FCVAR_SPONLY }; + cvar_t cvar_ezhttp_trace = {"ezhttp_trace_log", "0", FCVAR_SERVER | FCVAR_SPONLY}; void RefreshTraceLogSetting() { @@ -65,16 +69,19 @@ void DestroyModules() ezhttp::trace::Shutdown(); } -// native EzHttpOptions:ezhttp_create_options(); -cell AMX_NATIVE_CALL ezhttp_create_options(AMX* amx, cell* params) +// native EzHttpOptions:ezhttp_create_options(bool:auto_destroy = true); +cell AMX_NATIVE_CALL ezhttp_create_options(AMX *amx, cell *params) { - OptionsId options_id = g_EasyHttpModule->CreateOptions(); + const bool auto_destroy = params[0] >= sizeof(cell) + ? static_cast(params[1]) + : true; + OptionsId options_id = g_EasyHttpModule->CreateOptions(auto_destroy); - return (int)options_id; + return (cell)options_id; } // native ezhttp_destroy_options(EzHttpOptions:options_id); -cell AMX_NATIVE_CALL ezhttp_destroy_options(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_destroy_options(AMX *amx, cell *params) { auto options_id = (OptionsId)params[1]; @@ -85,35 +92,35 @@ cell AMX_NATIVE_CALL ezhttp_destroy_options(AMX* amx, cell* params) } // native ezhttp_option_set_user_agent(EzHttpOptions:options_id, const user_agent[]); -cell AMX_NATIVE_CALL ezhttp_option_set_user_agent(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_user_agent(AMX *amx, cell *params) { SetStringOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::SetUserAgent); return 0; } // native ezhttp_option_add_url_parameter(EzHttpOptions:options_id, const key[], const value[]); -cell AMX_NATIVE_CALL ezhttp_option_add_url_parameter(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_add_url_parameter(AMX *amx, cell *params) { SetKeyValueOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::AddUrlParameter); return 0; } // native ezhttp_option_add_form_payload(EzHttpOptions:options_id, const key[], const value[]); -cell AMX_NATIVE_CALL ezhttp_option_add_form_payload(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_add_form_payload(AMX *amx, cell *params) { SetKeyValueOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::AddFormPayload); return 0; } // native ezhttp_option_set_body(EzHttpOptions:options_id, const body[]); -cell AMX_NATIVE_CALL ezhttp_option_set_body(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_body(AMX *amx, cell *params) { SetStringOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::SetBody); return 0; } // native bool:ezhttp_option_set_body_from_json(EzHttpOptions:options_id, EzJSON:json, bool:pretty = false); -cell AMX_NATIVE_CALL ezhttp_option_set_body_from_json(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_body_from_json(AMX *amx, cell *params) { auto options_id = (OptionsId)params[1]; auto json_handle = (JS_Handle)params[2]; @@ -128,7 +135,7 @@ cell AMX_NATIVE_CALL ezhttp_option_set_body_from_json(AMX* amx, cell* params) return 0; } - char* json_str = g_JsonManager->SerialToString(json_handle, pretty); + char *json_str = g_JsonManager->SerialToString(json_handle, pretty); if (json_str == nullptr) return 0; @@ -139,28 +146,28 @@ cell AMX_NATIVE_CALL ezhttp_option_set_body_from_json(AMX* amx, cell* params) } // native ezhttp_option_append_body(EzHttpOptions:options_id, const body[]); -cell AMX_NATIVE_CALL ezhttp_option_append_body(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_append_body(AMX *amx, cell *params) { SetStringOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::AppendBody); return 0; } // native ezhttp_option_set_header(EzHttpOptions:options_id, const key[], const value[]); -cell AMX_NATIVE_CALL ezhttp_option_set_header(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_header(AMX *amx, cell *params) { SetKeyValueOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::SetHeader); return 0; } // native ezhttp_option_set_cookie(EzHttpOptions:options_id, const key[], const value[]); -cell AMX_NATIVE_CALL ezhttp_option_set_cookie(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_cookie(AMX *amx, cell *params) { SetKeyValueOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::SetCookie); return 0; } // native ezhttp_option_set_timeout(EzHttpOptions:options_id, timeout_ms); -cell AMX_NATIVE_CALL ezhttp_option_set_timeout(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_timeout(AMX *amx, cell *params) { auto options_id = (OptionsId)params[1]; cell timeout_ms = params[2]; @@ -173,7 +180,7 @@ cell AMX_NATIVE_CALL ezhttp_option_set_timeout(AMX* amx, cell* params) } // native ezhttp_option_set_connect_timeout(EzHttpOptions:options_id, timeout_ms); -cell AMX_NATIVE_CALL ezhttp_option_set_connect_timeout(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_connect_timeout(AMX *amx, cell *params) { auto options_id = (OptionsId)params[1]; cell timeout_ms = params[2]; @@ -186,31 +193,31 @@ cell AMX_NATIVE_CALL ezhttp_option_set_connect_timeout(AMX* amx, cell* params) } // native ezhttp_option_set_proxy(EzHttpOptions:options_id, const proxy_url[]); -cell AMX_NATIVE_CALL ezhttp_option_set_proxy(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_proxy(AMX *amx, cell *params) { SetStringOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::SetProxy); return 0; } // native ezhttp_option_set_proxy_auth(EzHttpOptions:options_id, const user[], const password[]); -cell AMX_NATIVE_CALL ezhttp_option_set_proxy_auth(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_proxy_auth(AMX *amx, cell *params) { SetKeyValueOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::SetProxyAuth); return 0; } // native ezhttp_option_set_auth(EzHttpOptions:options_id, const user[], const password[]); -cell AMX_NATIVE_CALL ezhttp_option_set_auth(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_auth(AMX *amx, cell *params) { SetKeyValueOption(amx, params, &ezhttp::EasyHttpOptionsBuilder::SetAuth); return 0; } // native ezhttp_option_set_user_data(EzHttpOptions:options_id, const data[], len); -cell AMX_NATIVE_CALL ezhttp_option_set_user_data(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_user_data(AMX *amx, cell *params) { auto options_id = (OptionsId)params[1]; - cell* data_addr = MF_GetAmxAddr(amx, params[2]); + cell *data_addr = MF_GetAmxAddr(amx, params[2]); int data_len = params[3]; if (!ValidateOptionsId(amx, options_id)) @@ -225,7 +232,7 @@ cell AMX_NATIVE_CALL ezhttp_option_set_user_data(AMX* amx, cell* params) } // native ezhttp_option_set_plugin_end_behaviour(EzHttpOptions:options_id, EzHttpPluginEndBehaviour:plugin_end_behaviour); -cell AMX_NATIVE_CALL ezhttp_option_set_plugin_end_behaviour(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_plugin_end_behaviour(AMX *amx, cell *params) { auto options_id = (OptionsId)params[1]; auto plugin_end_behaviour = (PluginEndBehaviour)params[2]; @@ -233,12 +240,15 @@ cell AMX_NATIVE_CALL ezhttp_option_set_plugin_end_behaviour(AMX* amx, cell* para if (!ValidateOptionsId(amx, options_id)) return 0; + if (!ValidatePluginEndBehaviour(amx, plugin_end_behaviour)) + return 0; + g_EasyHttpModule->GetOptions(options_id).plugin_end_behaviour = plugin_end_behaviour; return 0; } // native ezhttp_option_set_queue(EzHttpOptions:options_id, EzHttpQueue:end_map_behaviour); -cell AMX_NATIVE_CALL ezhttp_option_set_queue(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_option_set_queue(AMX *amx, cell *params) { auto options_id = (OptionsId)params[1]; auto queue_id = (QueueId)params[2]; @@ -254,18 +264,26 @@ cell AMX_NATIVE_CALL ezhttp_option_set_queue(AMX* amx, cell* params) } // native EzHttpRequest:ezhttp_get(const url[], const on_complete[], EzHttpOptions:options_id = EzHttpOptions:0); -cell AMX_NATIVE_CALL ezhttp_get(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get(AMX *amx, cell *params) { - enum { arg_count, arg_url, arg_callback, arg_option_id, arg_data, arg_data_len }; + enum + { + arg_count, + arg_url, + arg_callback, + arg_option_id, + arg_data, + arg_data_len + }; int url_len; - char* url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); + char *url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); int callback_len; - char* callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); + char *callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); int data_len = 0; - cell* data = nullptr; + cell *data = nullptr; if (params[arg_count] > 3) { data_len = params[arg_data_len]; @@ -283,18 +301,26 @@ cell AMX_NATIVE_CALL ezhttp_get(AMX* amx, cell* params) } // native EzHttpRequest:ezhttp_post(const url[], const on_complete[], EzHttpOptions:options_id = EzHttpOptions:0); -cell AMX_NATIVE_CALL ezhttp_post(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_post(AMX *amx, cell *params) { - enum { arg_count, arg_url, arg_callback, arg_option_id, arg_data, arg_data_len }; + enum + { + arg_count, + arg_url, + arg_callback, + arg_option_id, + arg_data, + arg_data_len + }; int url_len; - char* url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); + char *url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); int callback_len; - char* callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); + char *callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); int data_len = 0; - cell* data = nullptr; + cell *data = nullptr; if (params[arg_count] > 3) { data_len = params[arg_data_len]; @@ -312,18 +338,26 @@ cell AMX_NATIVE_CALL ezhttp_post(AMX* amx, cell* params) } // native EzHttpRequest:ezhttp_put(const url[], const on_complete[], EzHttpOptions:options_id = EzHttpOptions:0); -cell AMX_NATIVE_CALL ezhttp_put(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_put(AMX *amx, cell *params) { - enum { arg_count, arg_url, arg_callback, arg_option_id, arg_data, arg_data_len }; + enum + { + arg_count, + arg_url, + arg_callback, + arg_option_id, + arg_data, + arg_data_len + }; int url_len; - char* url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); + char *url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); int callback_len; - char* callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); + char *callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); int data_len = 0; - cell* data = nullptr; + cell *data = nullptr; if (params[arg_count] > 3) { data_len = params[arg_data_len]; @@ -341,18 +375,26 @@ cell AMX_NATIVE_CALL ezhttp_put(AMX* amx, cell* params) } // native EzHttpRequest:ezhttp_patch(const url[], const on_complete[], EzHttpOptions:options_id = EzHttpOptions:0); -cell AMX_NATIVE_CALL ezhttp_patch(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_patch(AMX *amx, cell *params) { - enum { arg_count, arg_url, arg_callback, arg_option_id, arg_data, arg_data_len }; + enum + { + arg_count, + arg_url, + arg_callback, + arg_option_id, + arg_data, + arg_data_len + }; int url_len; - char* url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); + char *url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); int callback_len; - char* callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); + char *callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); int data_len = 0; - cell* data = nullptr; + cell *data = nullptr; if (params[arg_count] > 3) { data_len = params[arg_data_len]; @@ -370,18 +412,26 @@ cell AMX_NATIVE_CALL ezhttp_patch(AMX* amx, cell* params) } // native EzHttpRequest:ezhttp_delete(const url[], const on_complete[], EzHttpOptions:options_id = EzHttpOptions:0); -cell AMX_NATIVE_CALL ezhttp_delete(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_delete(AMX *amx, cell *params) { - enum { arg_count, arg_url, arg_callback, arg_option_id, arg_data, arg_data_len }; + enum + { + arg_count, + arg_url, + arg_callback, + arg_option_id, + arg_data, + arg_data_len + }; int url_len; - char* url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); + char *url = MF_GetAmxString(amx, params[arg_url], 0, &url_len); int callback_len; - char* callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); + char *callback = MF_GetAmxString(amx, params[arg_callback], 1, &callback_len); int data_len = 0; - cell* data = nullptr; + cell *data = nullptr; if (params[arg_count] > 3) { data_len = params[arg_data_len]; @@ -399,7 +449,7 @@ cell AMX_NATIVE_CALL ezhttp_delete(AMX* amx, cell* params) } // native ezhttp_is_request_exists(EzHttpRequest:request_id); -cell AMX_NATIVE_CALL ezhttp_is_request_exists(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_is_request_exists(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; @@ -407,31 +457,31 @@ cell AMX_NATIVE_CALL ezhttp_is_request_exists(AMX* amx, cell* params) } // native ezhttp_cancel_request(EzHttpRequest:request_id); -cell AMX_NATIVE_CALL ezhttp_cancel_request(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_cancel_request(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - RequestData& request_data = g_EasyHttpModule->GetRequest(request_id); + RequestData &request_data = g_EasyHttpModule->GetRequest(request_id); request_data.request_control->canceled.store(true); return 0; } // native ezhttp_request_progress(EzHttpRequest:request_id, progress[EzHttpProgress]); -cell AMX_NATIVE_CALL ezhttp_request_progress(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_request_progress(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - auto& request_control = g_EasyHttpModule->GetRequest(request_id).request_control; + auto &request_control = g_EasyHttpModule->GetRequest(request_id).request_control; const auto progress = request_control->GetProgress(); - cell* p = MF_GetAmxAddr(amx, params[2]); + cell *p = MF_GetAmxAddr(amx, params[2]); p[0] = progress.download_now; p[1] = progress.download_total; p[2] = progress.upload_now; @@ -440,19 +490,19 @@ cell AMX_NATIVE_CALL ezhttp_request_progress(AMX* amx, cell* params) return 0; } -cell AMX_NATIVE_CALL ezhttp_get_http_code(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_http_code(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return response.status_code; } -cell AMX_NATIVE_CALL ezhttp_get_data(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_data(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; cell max_len = params[3]; @@ -460,7 +510,7 @@ cell AMX_NATIVE_CALL ezhttp_get_data(AMX* amx, cell* params) if (!ValidateRequestId(amx, request_id) || max_len == 0) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; utils::SetAmxStringUTF8CharSafe(amx, params[2], response.text.c_str(), response.text.length(), max_len); @@ -468,7 +518,7 @@ cell AMX_NATIVE_CALL ezhttp_get_data(AMX* amx, cell* params) } // native EzJSON:ezhttp_parse_json_response(EzHttpRequest:request_id, bool:with_comments = false); -cell AMX_NATIVE_CALL ezhttp_parse_json_response(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_parse_json_response(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; bool with_comments = (bool)params[2]; @@ -476,7 +526,7 @@ cell AMX_NATIVE_CALL ezhttp_parse_json_response(AMX* amx, cell* params) if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; JS_Handle json_handle; bool result = g_JsonManager->Parse(response.text.c_str(), &json_handle, false, with_comments); @@ -484,7 +534,7 @@ cell AMX_NATIVE_CALL ezhttp_parse_json_response(AMX* amx, cell* params) return result ? json_handle : -1; } -cell AMX_NATIVE_CALL ezhttp_get_url(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_url(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; cell max_len = params[3]; @@ -492,24 +542,24 @@ cell AMX_NATIVE_CALL ezhttp_get_url(AMX* amx, cell* params) if (!ValidateRequestId(amx, request_id) || max_len == 0) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; utils::SetAmxStringUTF8CharSafe(amx, params[2], response.url.c_str(), response.url.str().length(), max_len); return 0; } -cell AMX_NATIVE_CALL ezhttp_save_data_to_file(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_save_data_to_file(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; int file_path_len; - char* file_path = MF_GetAmxString(amx, params[2], 0, &file_path_len); + char *file_path = MF_GetAmxString(amx, params[2], 0, &file_path_len); if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; if (response.text.empty()) return 0; @@ -524,47 +574,47 @@ cell AMX_NATIVE_CALL ezhttp_save_data_to_file(AMX* amx, cell* params) return response.text.length(); } -cell AMX_NATIVE_CALL ezhttp_save_data_to_file2(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_save_data_to_file2(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; - FILE* file_handle = (FILE*)params[2]; + FILE *file_handle = (FILE *)params[2]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return std::fwrite(response.text.data(), sizeof(char), response.text.length(), file_handle); } -cell AMX_NATIVE_CALL ezhttp_get_headers_count(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_headers_count(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return response.header.size(); } -cell AMX_NATIVE_CALL ezhttp_get_headers(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_headers(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; int key_len; - char* key = MF_GetAmxString(amx, params[2], 0, &key_len); + char *key = MF_GetAmxString(amx, params[2], 0, &key_len); cell value_max_len = params[4]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; const std::string header_key(key, key_len); if (response.header.count(header_key) == 1) { - const std::string& header_value = response.header.at(header_key); + const std::string &header_value = response.header.at(header_key); utils::SetAmxStringUTF8CharSafe(amx, params[3], header_value.c_str(), header_value.length(), value_max_len); @@ -574,67 +624,67 @@ cell AMX_NATIVE_CALL ezhttp_get_headers(AMX* amx, cell* params) return 0; } -cell AMX_NATIVE_CALL ezhttp_iterate_headers(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_iterate_headers(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; cell iter = params[2]; int key_len; - char* key = MF_GetAmxString(amx, params[3], 0, &key_len); + char *key = MF_GetAmxString(amx, params[3], 0, &key_len); cell value_max_len = params[5]; if (!ValidateRequestId(amx, request_id)) return 0; - const RequestData& request = g_EasyHttpModule->GetRequest(request_id); - const cpr::Header& header = request.response.header; - + const RequestData &request = g_EasyHttpModule->GetRequest(request_id); + const cpr::Header &header = request.response.header; return 0; } -cell AMX_NATIVE_CALL ezhttp_get_elapsed(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_elapsed(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return amx_ftoc(response.elapsed); } -cell AMX_NATIVE_CALL ezhttp_get_cookies_count(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_cookies_count(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; size_t size = response.cookies.end() - response.cookies.begin(); return (cell)size; } -cell AMX_NATIVE_CALL ezhttp_get_cookies(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_cookies(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; int key_len; - char* key = MF_GetAmxString(amx, params[2], 0, &key_len); + char *key = MF_GetAmxString(amx, params[2], 0, &key_len); cell value_max_len = params[4]; if (!ValidateRequestId(amx, request_id)) return 0; - Response& response = g_EasyHttpModule->GetRequest(request_id).response; + Response &response = g_EasyHttpModule->GetRequest(request_id).response; const std::string cookie_key(key, key_len); - auto it = std::find_if(response.cookies.begin(), response.cookies.end(), [&cookie_key](const auto& item) { return item.GetName() == cookie_key; }); + auto it = std::find_if(response.cookies.begin(), response.cookies.end(), [&cookie_key](const auto &item) + { return item.GetName() == cookie_key; }); if (it != response.cookies.end()) { - const std::string& cookie_value = it->GetValue(); + const std::string &cookie_value = it->GetValue(); utils::SetAmxStringUTF8CharSafe(amx, params[3], cookie_value.c_str(), cookie_value.length(), value_max_len); @@ -644,24 +694,24 @@ cell AMX_NATIVE_CALL ezhttp_get_cookies(AMX* amx, cell* params) return 0; } -cell AMX_NATIVE_CALL ezhttp_iterate_cookies(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_iterate_cookies(AMX *amx, cell *params) { return 0; } -cell AMX_NATIVE_CALL ezhttp_get_error_code(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_error_code(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return (cell)response.error.code; } -cell AMX_NATIVE_CALL ezhttp_get_error_message(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_error_message(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; cell max_len = params[3]; @@ -669,58 +719,58 @@ cell AMX_NATIVE_CALL ezhttp_get_error_message(AMX* amx, cell* params) if (!ValidateRequestId(amx, request_id) || max_len == 0) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; utils::SetAmxStringUTF8CharSafe(amx, params[2], response.error.message.c_str(), response.error.message.length(), max_len); return 0; } -cell AMX_NATIVE_CALL ezhttp_get_redirect_count(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_redirect_count(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return response.redirect_count; } -cell AMX_NATIVE_CALL ezhttp_get_uploaded_bytes(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_uploaded_bytes(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return response.uploaded_bytes; } -cell AMX_NATIVE_CALL ezhttp_get_downloaded_bytes(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_downloaded_bytes(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; if (!ValidateRequestId(amx, request_id)) return 0; - const Response& response = g_EasyHttpModule->GetRequest(request_id).response; + const Response &response = g_EasyHttpModule->GetRequest(request_id).response; return response.downloaded_bytes; } -cell AMX_NATIVE_CALL ezhttp_get_user_data(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_get_user_data(AMX *amx, cell *params) { auto request_id = (RequestId)params[1]; - cell* data_addr = MF_GetAmxAddr(amx, params[2]); + cell *data_addr = MF_GetAmxAddr(amx, params[2]); if (!ValidateRequestId(amx, request_id)) return 0; - const std::optional>& user_data = g_EasyHttpModule->GetRequest(request_id).user_data; + const std::optional> &user_data = g_EasyHttpModule->GetRequest(request_id).user_data; if (!user_data) return 0; @@ -729,22 +779,26 @@ cell AMX_NATIVE_CALL ezhttp_get_user_data(AMX* amx, cell* params) return 0; } -cell AMX_NATIVE_CALL ezhttp_ftp_upload(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_ftp_upload(AMX *amx, cell *params) { int len; std::string user(MF_GetAmxString(amx, params[1], 0, &len)); - std::string password (MF_GetAmxString(amx, params[2], 0, &len)); + std::string password(MF_GetAmxString(amx, params[2], 0, &len)); std::string host = MF_GetAmxString(amx, params[3], 0, &len); std::string remote_file = MF_GetAmxString(amx, params[4], 0, &len); int local_file_len; - char* local_file = MF_GetAmxString(amx, params[5], 1, &local_file_len); + char *local_file = MF_GetAmxString(amx, params[5], 1, &local_file_len); std::string callback = MF_GetAmxString(amx, params[6], 0, &len); - bool secure = params[7]; + cell security_value = params[7]; auto options_id = (OptionsId)params[8]; + if (!ValidateFtpSecurity(amx, security_value)) + return 0; + std::string url = utils::ConstructFtpUrl(user, password, host, remote_file); const std::string local_file_path = MF_BuildPathname("%s", local_file); + const bool secure = security_value != 0; return (cell)DispatchRequest( amx, @@ -754,28 +808,32 @@ cell AMX_NATIVE_CALL ezhttp_ftp_upload(AMX* amx, cell* params) callback, nullptr, 0, - [local_file_path, secure](OptionsData& request_options) { + [local_file_path, secure](OptionsData &request_options) + { request_options.options_builder.SetFilePath(local_file_path); request_options.options_builder.SetSecure(secure); - } - ); + }); } -cell AMX_NATIVE_CALL ezhttp_ftp_upload2(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_ftp_upload2(AMX *amx, cell *params) { int url_str_len; - char* url_str = MF_GetAmxString(amx, params[1], 0, &url_str_len); + char *url_str = MF_GetAmxString(amx, params[1], 0, &url_str_len); int local_file_len; - char* local_file = MF_GetAmxString(amx, params[2], 1, &local_file_len); + char *local_file = MF_GetAmxString(amx, params[2], 1, &local_file_len); int callback_len; - char* callback = MF_GetAmxString(amx, params[3], 2, &callback_len); + char *callback = MF_GetAmxString(amx, params[3], 2, &callback_len); - bool secure = params[4]; + cell security_value = params[4]; auto options_id = (OptionsId)params[5]; + if (!ValidateFtpSecurity(amx, security_value)) + return 0; + const std::string local_file_path = MF_BuildPathname("%s", local_file); + const bool secure = security_value != 0; return (cell)DispatchRequest( amx, @@ -785,29 +843,33 @@ cell AMX_NATIVE_CALL ezhttp_ftp_upload2(AMX* amx, cell* params) std::string(callback, callback_len), nullptr, 0, - [local_file_path, secure](OptionsData& request_options) { + [local_file_path, secure](OptionsData &request_options) + { request_options.options_builder.SetFilePath(local_file_path); request_options.options_builder.SetSecure(secure); - } - ); + }); } -cell AMX_NATIVE_CALL ezhttp_ftp_download(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_ftp_download(AMX *amx, cell *params) { int len; std::string user(MF_GetAmxString(amx, params[1], 0, &len)); - std::string password (MF_GetAmxString(amx, params[2], 0, &len)); + std::string password(MF_GetAmxString(amx, params[2], 0, &len)); std::string host = MF_GetAmxString(amx, params[3], 0, &len); std::string remote_file = MF_GetAmxString(amx, params[4], 0, &len); int local_file_len; - char* local_file = MF_GetAmxString(amx, params[5], 1, &local_file_len); + char *local_file = MF_GetAmxString(amx, params[5], 1, &local_file_len); std::string callback = MF_GetAmxString(amx, params[6], 0, &len); - bool secure = params[7]; + cell security_value = params[7]; auto options_id = (OptionsId)params[8]; + if (!ValidateFtpSecurity(amx, security_value)) + return 0; + std::string url = utils::ConstructFtpUrl(user, password, host, remote_file); const std::string local_file_path = MF_BuildPathname("%s", local_file); + const bool secure = security_value != 0; return (cell)DispatchRequest( amx, @@ -817,28 +879,32 @@ cell AMX_NATIVE_CALL ezhttp_ftp_download(AMX* amx, cell* params) callback, nullptr, 0, - [local_file_path, secure](OptionsData& request_options) { + [local_file_path, secure](OptionsData &request_options) + { request_options.options_builder.SetFilePath(local_file_path); request_options.options_builder.SetSecure(secure); - } - ); + }); } -cell AMX_NATIVE_CALL ezhttp_ftp_download2(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_ftp_download2(AMX *amx, cell *params) { int url_str_len; - char* url_str = MF_GetAmxString(amx, params[1], 0, &url_str_len); + char *url_str = MF_GetAmxString(amx, params[1], 0, &url_str_len); int local_file_len; - char* local_file = MF_GetAmxString(amx, params[2], 1, &local_file_len); + char *local_file = MF_GetAmxString(amx, params[2], 1, &local_file_len); int callback_len; - char* callback = MF_GetAmxString(amx, params[3], 2, &callback_len); + char *callback = MF_GetAmxString(amx, params[3], 2, &callback_len); - bool secure = params[4]; + cell security_value = params[4]; auto options_id = (OptionsId)params[5]; + if (!ValidateFtpSecurity(amx, security_value)) + return 0; + const std::string local_file_path = MF_BuildPathname("%s", local_file); + const bool secure = security_value != 0; return (cell)DispatchRequest( amx, @@ -848,24 +914,24 @@ cell AMX_NATIVE_CALL ezhttp_ftp_download2(AMX* amx, cell* params) std::string(callback, callback_len), nullptr, 0, - [local_file_path, secure](OptionsData& request_options) { + [local_file_path, secure](OptionsData &request_options) + { request_options.options_builder.SetFilePath(local_file_path); request_options.options_builder.SetSecure(secure); - } - ); + }); } -cell AMX_NATIVE_CALL ezhttp_create_queue(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_create_queue(AMX *amx, cell *params) { return (cell)g_EasyHttpModule->CreateQueue(); } -cell AMX_NATIVE_CALL ezhttp_steam_to_steam64(AMX* amx, cell* params) +cell AMX_NATIVE_CALL ezhttp_steam_to_steam64(AMX *amx, cell *params) { // doc https://developer.valvesoftware.com/wiki/SteamID int steam_len; - char* steam_str = MF_GetAmxString(amx, params[1], 0, &steam_len); + char *steam_str = MF_GetAmxString(amx, params[1], 0, &steam_len); cell steam64_maxlen = params[3]; std::string steam(steam_str, steam_len); @@ -879,11 +945,11 @@ cell AMX_NATIVE_CALL ezhttp_steam_to_steam64(AMX* amx, cell* params) if (tokens.size() != 3) return 0; - uint32_t account_id = std::atoi(tokens[2].c_str()); // 32 bit + uint32_t account_id = std::atoi(tokens[2].c_str()); // 32 bit account_id = account_id << 1 | std::atoi(tokens[1].c_str()); - uint32_t account_instance = 1; // 20 bit, 1 for individual account - uint8_t account_type = 1; // 4 bit, 1 is individual account - uint8_t universe = 1; // 8 bit, using token[0] produces incorrect results, so use always 1 + uint32_t account_instance = 1; // 20 bit, 1 for individual account + uint8_t account_type = 1; // 4 bit, 1 is individual account + uint8_t universe = 1; // 8 bit, using token[0] produces incorrect results, so use always 1 uint64_t steam64 = (uint64_t)universe << 56 | (uint64_t)account_type << 52 | (uint64_t)account_instance << 32 | account_id; @@ -894,26 +960,37 @@ cell AMX_NATIVE_CALL ezhttp_steam_to_steam64(AMX* amx, cell* params) } RequestId DispatchRequest( - AMX* amx, + AMX *amx, RequestMethod method, OptionsId options_id, - const std::string& url, - const std::string& callback, + const std::string &url, + const std::string &callback, std::unique_ptr data, const int data_len, - const OptionsConfigurer& configure -) + const OptionsConfigurer &configure) { if (options_id != OptionsId::Null && !ValidateOptionsId(amx, options_id)) return RequestId::Null; + OptionsData request_options = options_id == OptionsId::Null + ? OptionsData{} + : g_EasyHttpModule->CreateOptionsSnapshot(options_id); + + if (configure) + configure(request_options); + + if (!ValidateDispatchOptions(amx, request_options)) + return RequestId::Null; + int callback_id = -1; if (!callback.empty()) { if (!data) { callback_id = MF_RegisterSPForwardByName(amx, callback.c_str(), FP_CELL, FP_DONE); - } else { + } + else + { callback_id = MF_RegisterSPForwardByName(amx, callback.c_str(), FP_CELL, FP_ARRAY, FP_DONE); } @@ -924,26 +1001,27 @@ RequestId DispatchRequest( } } - OptionsData request_options = options_id == OptionsId::Null - ? OptionsData{} - : g_EasyHttpModule->CreateOptionsSnapshot(options_id); - - if (configure) - configure(request_options); - RequestId request_id = g_EasyHttpModule->SendRequest( method, url, std::move(request_options), callback_id, std::move(data), - data_len - ); + data_len, + options_id); + + if (request_id == RequestId::Null) + { + if (callback_id != -1) + MF_UnregisterSPForward(callback_id); + + MF_LogError(amx, AMX_ERR_NATIVE, "Failed to dispatch request due to invalid internal state"); + } return request_id; } -bool ValidateOptionsId(AMX* amx, OptionsId options_id) +bool ValidateOptionsId(AMX *amx, OptionsId options_id) { if (!g_EasyHttpModule->IsOptionsExists(options_id)) { @@ -954,7 +1032,7 @@ bool ValidateOptionsId(AMX* amx, OptionsId options_id) return true; } -bool ValidateRequestId(AMX* amx, RequestId request_id) +bool ValidateRequestId(AMX *amx, RequestId request_id) { if (!g_EasyHttpModule->IsRequestExists(request_id)) { @@ -965,7 +1043,7 @@ bool ValidateRequestId(AMX* amx, RequestId request_id) return true; } -bool ValidateQueueId(AMX* amx, QueueId queue_id) +bool ValidateQueueId(AMX *amx, QueueId queue_id) { if (!g_EasyHttpModule->IsQueueExists(queue_id)) { @@ -976,14 +1054,51 @@ bool ValidateQueueId(AMX* amx, QueueId queue_id) return true; } +bool ValidatePluginEndBehaviour(AMX *amx, PluginEndBehaviour plugin_end_behaviour) +{ + switch (plugin_end_behaviour) + { + case PluginEndBehaviour::CancelRequests: + case PluginEndBehaviour::ForgetRequests: + return true; + } + + MF_LogError(amx, AMX_ERR_NATIVE, "Invalid plugin end behaviour %d", static_cast(plugin_end_behaviour)); + return false; +} + +bool ValidateFtpSecurity(AMX *amx, cell security_value) +{ + if (security_value == 0 || security_value == 1) + return true; + + MF_LogError( + amx, + AMX_ERR_NATIVE, + "Invalid FTP security value %d. Expected EZH_UNSECURE (0) or EZH_SECURE_EXPLICIT (1)", + security_value); + return false; +} + +bool ValidateDispatchOptions(AMX *amx, const OptionsData &options) +{ + if (!ValidateQueueId(amx, options.queue_id)) + return false; + + if (!ValidatePluginEndBehaviour(amx, options.plugin_end_behaviour)) + return false; + + return true; +} + template -void SetKeyValueOption(AMX* amx, cell* params, TMethod method) +void SetKeyValueOption(AMX *amx, cell *params, TMethod method) { auto options_id = (OptionsId)params[1]; int key_len; - char* key = MF_GetAmxString(amx, params[2], 0, &key_len); + char *key = MF_GetAmxString(amx, params[2], 0, &key_len); int value_len; - char* value = MF_GetAmxString(amx, params[3], 1, &value_len); + char *value = MF_GetAmxString(amx, params[3], 1, &value_len); if (!ValidateOptionsId(amx, options_id)) return; @@ -992,11 +1107,11 @@ void SetKeyValueOption(AMX* amx, cell* params, TMethod method) } template -void SetStringOption(AMX* amx, cell* params, TMethod method) +void SetStringOption(AMX *amx, cell *params, TMethod method) { auto options_id = (OptionsId)params[1]; int value_len; - char* value = MF_GetAmxString(amx, params[2], 0, &value_len); + char *value = MF_GetAmxString(amx, params[2], 0, &value_len); if (!ValidateOptionsId(amx, options_id)) return; @@ -1005,73 +1120,73 @@ void SetStringOption(AMX* amx, cell* params, TMethod method) } AMX_NATIVE_INFO g_Natives[] = -{ - // options - { "ezhttp_create_options", ezhttp_create_options }, - { "ezhttp_destroy_options", ezhttp_destroy_options }, - { "ezhttp_option_set_user_agent", ezhttp_option_set_user_agent }, - { "ezhttp_option_add_url_parameter", ezhttp_option_add_url_parameter }, - { "ezhttp_option_add_form_payload", ezhttp_option_add_form_payload }, - { "ezhttp_option_set_body", ezhttp_option_set_body }, - { "ezhttp_option_set_body_from_json", ezhttp_option_set_body_from_json }, - { "ezhttp_option_append_body", ezhttp_option_append_body }, - { "ezhttp_option_set_header", ezhttp_option_set_header }, - { "ezhttp_option_set_cookie", ezhttp_option_set_cookie }, - { "ezhttp_option_set_timeout", ezhttp_option_set_timeout }, - { "ezhttp_option_set_connect_timeout", ezhttp_option_set_connect_timeout }, - { "ezhttp_option_set_proxy", ezhttp_option_set_proxy }, - { "ezhttp_option_set_proxy_auth", ezhttp_option_set_proxy_auth }, - { "ezhttp_option_set_auth", ezhttp_option_set_auth }, - { "ezhttp_option_set_user_data", ezhttp_option_set_user_data }, - { "ezhttp_option_set_plugin_end_behaviour", ezhttp_option_set_plugin_end_behaviour }, - { "ezhttp_option_set_queue", ezhttp_option_set_queue }, - - // requests - { "ezhttp_get", ezhttp_get }, - { "ezhttp_post", ezhttp_post }, - { "ezhttp_put", ezhttp_put }, - { "ezhttp_patch", ezhttp_patch }, - { "ezhttp_delete", ezhttp_delete }, - { "ezhttp_is_request_exists", ezhttp_is_request_exists }, - { "ezhttp_cancel_request", ezhttp_cancel_request }, - { "ezhttp_request_progress", ezhttp_request_progress }, - - // response - { "ezhttp_get_http_code", ezhttp_get_http_code }, - { "ezhttp_get_data", ezhttp_get_data }, - { "ezhttp_parse_json_response", ezhttp_parse_json_response }, - { "ezhttp_get_url", ezhttp_get_url }, - { "ezhttp_save_data_to_file", ezhttp_save_data_to_file }, - { "ezhttp_save_data_to_file2", ezhttp_save_data_to_file2 }, - { "ezhttp_get_headers_count", ezhttp_get_headers_count }, - { "ezhttp_get_headers", ezhttp_get_headers }, - { "ezhttp_iterate_headers", ezhttp_iterate_headers }, - { "ezhttp_get_elapsed", ezhttp_get_elapsed }, - { "ezhttp_get_cookies_count", ezhttp_get_cookies_count }, - { "ezhttp_get_cookies", ezhttp_get_cookies }, - { "ezhttp_iterate_cookies", ezhttp_iterate_cookies }, - { "ezhttp_get_error_code", ezhttp_get_error_code }, - { "ezhttp_get_error_message", ezhttp_get_error_message }, - { "ezhttp_get_redirect_count", ezhttp_get_redirect_count }, - { "ezhttp_get_uploaded_bytes", ezhttp_get_uploaded_bytes }, - { "ezhttp_get_downloaded_bytes", ezhttp_get_downloaded_bytes }, - { "ezhttp_get_user_data", ezhttp_get_user_data }, - - // ftp - { "ezhttp_ftp_upload", ezhttp_ftp_upload }, - { "ezhttp_ftp_upload2", ezhttp_ftp_upload2 }, - { "ezhttp_ftp_download", ezhttp_ftp_download }, - { "ezhttp_ftp_download2", ezhttp_ftp_download2 }, - - // queue - { "ezhttp_create_queue", ezhttp_create_queue }, - - // special - { "_ezhttp_steam_to_steam64", ezhttp_steam_to_steam64 }, - { nullptr, nullptr }, + { + // options + {"ezhttp_create_options", ezhttp_create_options}, + {"ezhttp_destroy_options", ezhttp_destroy_options}, + {"ezhttp_option_set_user_agent", ezhttp_option_set_user_agent}, + {"ezhttp_option_add_url_parameter", ezhttp_option_add_url_parameter}, + {"ezhttp_option_add_form_payload", ezhttp_option_add_form_payload}, + {"ezhttp_option_set_body", ezhttp_option_set_body}, + {"ezhttp_option_set_body_from_json", ezhttp_option_set_body_from_json}, + {"ezhttp_option_append_body", ezhttp_option_append_body}, + {"ezhttp_option_set_header", ezhttp_option_set_header}, + {"ezhttp_option_set_cookie", ezhttp_option_set_cookie}, + {"ezhttp_option_set_timeout", ezhttp_option_set_timeout}, + {"ezhttp_option_set_connect_timeout", ezhttp_option_set_connect_timeout}, + {"ezhttp_option_set_proxy", ezhttp_option_set_proxy}, + {"ezhttp_option_set_proxy_auth", ezhttp_option_set_proxy_auth}, + {"ezhttp_option_set_auth", ezhttp_option_set_auth}, + {"ezhttp_option_set_user_data", ezhttp_option_set_user_data}, + {"ezhttp_option_set_plugin_end_behaviour", ezhttp_option_set_plugin_end_behaviour}, + {"ezhttp_option_set_queue", ezhttp_option_set_queue}, + + // requests + {"ezhttp_get", ezhttp_get}, + {"ezhttp_post", ezhttp_post}, + {"ezhttp_put", ezhttp_put}, + {"ezhttp_patch", ezhttp_patch}, + {"ezhttp_delete", ezhttp_delete}, + {"ezhttp_is_request_exists", ezhttp_is_request_exists}, + {"ezhttp_cancel_request", ezhttp_cancel_request}, + {"ezhttp_request_progress", ezhttp_request_progress}, + + // response + {"ezhttp_get_http_code", ezhttp_get_http_code}, + {"ezhttp_get_data", ezhttp_get_data}, + {"ezhttp_parse_json_response", ezhttp_parse_json_response}, + {"ezhttp_get_url", ezhttp_get_url}, + {"ezhttp_save_data_to_file", ezhttp_save_data_to_file}, + {"ezhttp_save_data_to_file2", ezhttp_save_data_to_file2}, + {"ezhttp_get_headers_count", ezhttp_get_headers_count}, + {"ezhttp_get_headers", ezhttp_get_headers}, + {"ezhttp_iterate_headers", ezhttp_iterate_headers}, + {"ezhttp_get_elapsed", ezhttp_get_elapsed}, + {"ezhttp_get_cookies_count", ezhttp_get_cookies_count}, + {"ezhttp_get_cookies", ezhttp_get_cookies}, + {"ezhttp_iterate_cookies", ezhttp_iterate_cookies}, + {"ezhttp_get_error_code", ezhttp_get_error_code}, + {"ezhttp_get_error_message", ezhttp_get_error_message}, + {"ezhttp_get_redirect_count", ezhttp_get_redirect_count}, + {"ezhttp_get_uploaded_bytes", ezhttp_get_uploaded_bytes}, + {"ezhttp_get_downloaded_bytes", ezhttp_get_downloaded_bytes}, + {"ezhttp_get_user_data", ezhttp_get_user_data}, + + // ftp + {"ezhttp_ftp_upload", ezhttp_ftp_upload}, + {"ezhttp_ftp_upload2", ezhttp_ftp_upload2}, + {"ezhttp_ftp_download", ezhttp_ftp_download}, + {"ezhttp_ftp_download2", ezhttp_ftp_download2}, + + // queue + {"ezhttp_create_queue", ezhttp_create_queue}, + + // special + {"_ezhttp_steam_to_steam64", ezhttp_steam_to_steam64}, + {nullptr, nullptr}, }; -cvar_t cvar_ezhttp_version = { "ezhttp_version", MODULE_VERSION, FCVAR_SERVER | FCVAR_SPONLY }; +cvar_t cvar_ezhttp_version = {"ezhttp_version", MODULE_VERSION, FCVAR_SERVER | FCVAR_SPONLY}; void OnAmxxAttach() { @@ -1103,7 +1218,7 @@ void OnPluginsUnloading() ezhttp::trace::Writef("module", "OnPluginsUnloading exit"); } -void ServerActivate(edict_t* /*pEdictList*/, int /*edictCount*/, int /*clientMax*/) +void ServerActivate(edict_t * /*pEdictList*/, int /*edictCount*/, int /*clientMax*/) { g_MapChangeResetDone = false; RefreshTraceLogSetting(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 555fbd2..4e992df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,7 @@ set(TARGET_NAME "AmxxEasyHttp-tests") include(GoogleTest) add_executable(${TARGET_NAME} + easy_http_module_tests.cpp ftp_utils_tests.cpp session_cache_tests.cpp CurlHolderComparer.h diff --git a/tests/easy_http_module_tests.cpp b/tests/easy_http_module_tests.cpp new file mode 100644 index 0000000..8454a71 --- /dev/null +++ b/tests/easy_http_module_tests.cpp @@ -0,0 +1,35 @@ +#include + +#include + +TEST(EasyHttpModuleTest, SendRequestRejectsUnknownQueue) +{ + EasyHttpModule module("test-ca.pem"); + + OptionsData options; + options.queue_id = static_cast(999); + + const RequestId request_id = module.SendRequest( + ezhttp::RequestMethod::HttpGet, + "https://example.com", + options + ); + + EXPECT_EQ(RequestId::Null, request_id); +} + +TEST(EasyHttpModuleTest, SendRequestRejectsInvalidPluginEndBehaviour) +{ + EasyHttpModule module("test-ca.pem"); + + OptionsData options; + options.plugin_end_behaviour = static_cast(999); + + const RequestId request_id = module.SendRequest( + ezhttp::RequestMethod::HttpGet, + "https://example.com", + options + ); + + EXPECT_EQ(RequestId::Null, request_id); +}