diff --git a/CMakeLists.txt b/CMakeLists.txt index eb8004d..5602614 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,17 @@ -cmake_minimum_required (VERSION 2.6) -project(openrouter) -set(CMAKE_CXX_STANDARD 20) - -include_directories(./src/libs/ ./src) - -add_executable( - openrouter - src/openrouter.cpp +cmake_minimum_required (VERSION 2.6) +project(openrouter) +set(CMAKE_CXX_STANDARD 20) + +include_directories(./src/libs/ ./src) + +add_executable( + openrouter + src/openrouter.cpp src/security/cors.cpp - + + src/settings/settings.cpp + src/settings/backup.cpp + src/settings/telnet/enabled.cpp src/settings/telnet/port.cpp @@ -41,7 +44,7 @@ add_executable( src/libs/QR-Code-generator/qrcodegen.cpp src/libs/QR-Code-generator/qrtosvg.cpp -) +) install(TARGETS openrouter DESTINATION /usr/bin) install(DIRECTORY src/web DESTINATION /usr/share/openrouter/web) diff --git a/Makefile b/Makefile index e418295..7ef417e 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ STRIP=strip CXXFLAGS=-Wall -I src/libs -I src -std=c++20 SOURCES=src/openrouter.cpp \ src/security/cors.cpp \ + src/settings/settings.cpp \ + src/settings/backup.cpp \ src/settings/telnet/enabled.cpp \ src/settings/telnet/port.cpp \ src/settings/ssh/status.cpp \ diff --git a/config.json b/config.json index 2774c6d..bfe0702 100644 --- a/config.json +++ b/config.json @@ -1,4 +1,9 @@ { + "auth_service": "http://127.0.0.1:43243", + "server": { + "ip": "0.0.0.0", + "port": 43244 + }, "settings": { "dns": { "enabled": false, @@ -25,17 +30,17 @@ "key1", "key2" ], - "port": 22, + "port": "22", "server": "dropbear" }, "telnet": { "enabled": false, - "port": 23 + "port": "23" }, "wifi": { - "channel": 6, + "channel": "11", "enabled": false, - "name": "OpenRouterWifi", + "name": "home", "password": "OpenRouterPassword" } }, @@ -44,4 +49,4 @@ "js": "../src/web/js", "root": "../src/web" } -} \ No newline at end of file +} diff --git a/config_default.json b/config_default.json new file mode 100644 index 0000000..bfe0702 --- /dev/null +++ b/config_default.json @@ -0,0 +1,52 @@ +{ + "auth_service": "http://127.0.0.1:43243", + "server": { + "ip": "0.0.0.0", + "port": 43244 + }, + "settings": { + "dns": { + "enabled": false, + "localdomains": [ + { + "domain": "localhost", + "ip": "127.0.0.1" + }, + { + "domain": "openrouter.local.com", + "ip": "192.168.200.1" + } + ], + "servers": [ + "8.8.8.8" + ] + }, + "ftp": { + "enabled": false + }, + "ssh": { + "enabled": false, + "keys": [ + "key1", + "key2" + ], + "port": "22", + "server": "dropbear" + }, + "telnet": { + "enabled": false, + "port": "23" + }, + "wifi": { + "channel": "11", + "enabled": false, + "name": "home", + "password": "OpenRouterPassword" + } + }, + "static": { + "css": "../src/web/css", + "js": "../src/web/js", + "root": "../src/web" + } +} diff --git a/meson.build b/meson.build index b8256db..f8de5af 100644 --- a/meson.build +++ b/meson.build @@ -5,6 +5,9 @@ executable( 'src/openrouter.cpp', 'src/security/cors.cpp', + 'src/settings/settings.cpp', + 'src/settings/backup.cpp', + 'src/settings/telnet/enabled.cpp', 'src/settings/telnet/port.cpp', diff --git a/src/OpenRouter/OpenRouter.cpp b/src/OpenRouter/OpenRouter.cpp index 5bfd107..588a9dc 100644 --- a/src/OpenRouter/OpenRouter.cpp +++ b/src/OpenRouter/OpenRouter.cpp @@ -8,39 +8,14 @@ OpenRouter::OpenRouter() { - this->srv.Post("/api/settings/telnet/get", api::settings::telnet::get_telnet); - this->srv.Post("/api/settings/telnet/set", api::settings::telnet::set_telnet); - this->srv.Post("/api/settings/telnet/port/get", api::settings::telnet::get_telnet_port); - this->srv.Post("/api/settings/telnet/port/set", api::settings::telnet::set_telnet_port); - - this->srv.Post("/api/settings/ssh/status/get", api::settings::ssh::get_ssh_status); - this->srv.Post("/api/settings/ssh/status/set", api::settings::ssh::set_ssh_status); - this->srv.Post("/api/settings/ssh/keys/get", api::settings::ssh::get_ssh_keys); - this->srv.Post("/api/settings/ssh/keys/set", api::settings::ssh::set_ssh_keys); - this->srv.Post("/api/settings/ssh/port/get", api::settings::ssh::get_ssh_port); - this->srv.Post("/api/settings/ssh/port/set", api::settings::ssh::set_ssh_port); - this->srv.Post("/api/settings/ssh/server/get", api::settings::ssh::get_ssh_server_http); - this->srv.Post("/api/settings/ssh/server/set", api::settings::ssh::set_ssh_server_http); - - this->srv.Post("/api/settings/dns/get", api::settings::dns::get_dns); - this->srv.Post("/api/settings/dns/set", api::settings::dns::set_dns); - this->srv.Post("/api/settings/dns/servers/get", api::settings::dns::get_dns_servers); - this->srv.Post("/api/settings/dns/servers/set", api::settings::dns::set_dns_servers); - this->srv.Post("/api/settings/dns/domains/get", api::settings::dns::get_local_domains); - this->srv.Post("/api/settings/dns/domains/set", api::settings::dns::set_local_domains); - - this->srv.Post("/api/settings/ftp/get", api::settings::ftp::get_ftp); - this->srv.Post("/api/settings/ftp/set", api::settings::ftp::set_ftp); - - this->srv.Post("/api/settings/wifi/status/get", api::settings::wifi::get_wifi_status); - this->srv.Post("/api/settings/wifi/status/set", api::settings::wifi::set_wifi_status); - this->srv.Post("/api/settings/wifi/name/get", api::settings::wifi::get_wifi_name); - this->srv.Post("/api/settings/wifi/name/set", api::settings::wifi::set_wifi_name); - this->srv.Post("/api/settings/wifi/password/get", api::settings::wifi::get_wifi_password); this->srv.Post("/api/settings/wifi/password/set", api::settings::wifi::set_wifi_password); this->srv.Post("/api/settings/wifi/qr/get", api::settings::wifi::get_wifi_qr); - this->srv.Post("/api/settings/wifi/channel/get", api::settings::wifi::get_wifi_channel); - this->srv.Post("/api/settings/wifi/channel/set", api::settings::wifi::set_wifi_channel); + + this->srv.Post("/api/settings/get", api::settings::get); + this->srv.Post("/api/settings/set", api::settings::set); + this->srv.Get("/api/settings/backup", api::settings::backup); + this->srv.Post("/api/settings/restore", api::settings::restore); + this->srv.Post("/api/settings/restore/defaults", api::settings::restore_defaults); this->srv.Post("/api/reboot", api::system::reboot); diff --git a/src/auth/auth.cpp b/src/auth/auth.cpp index 9540906..fff8eb5 100644 --- a/src/auth/auth.cpp +++ b/src/auth/auth.cpp @@ -1,12 +1,12 @@ #include #include -#include #include #include +#include bool authenticate(const std::string &token) { - httplib::Client cli(AUTH_SERVICE); + httplib::Client cli(config::config.getConfig()["auth_service"]); std::string body = R"({"token":")" + token + R"("})"; // body << R"({"token":")" << token << R"("})"; diff --git a/src/config/config.hpp b/src/config/config.hpp index dd29eea..171f7a4 100644 --- a/src/config/config.hpp +++ b/src/config/config.hpp @@ -3,4 +3,4 @@ namespace config { static ConfigManager config(std::string("../config.json")); -} \ No newline at end of file +} diff --git a/src/libs/ConfigManager.hpp b/src/libs/ConfigManager.hpp index 571a298..087d55b 100644 --- a/src/libs/ConfigManager.hpp +++ b/src/libs/ConfigManager.hpp @@ -38,6 +38,9 @@ public: this->config = nullptr; this->configfile.close(); } + std::string getConfigFileName() { + return this->configFileName; + } nlohmann::json getConfig() { return this->config; diff --git a/src/libs/httplib.h b/src/libs/httplib.h index f2c01d5..6bbf2ce 100755 --- a/src/libs/httplib.h +++ b/src/libs/httplib.h @@ -1,14 +1,14 @@ // // httplib.h // -// Copyright (c) 2024 Yuji Hirose. All rights reserved. +// Copyright (c) 2025 Yuji Hirose. All rights reserved. // MIT License // #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.16.0" +#define CPPHTTPLIB_VERSION "0.19.0" /* * Configuration @@ -18,8 +18,12 @@ #define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 #endif +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000 +#endif + #ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT -#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100 #endif #ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND @@ -30,20 +34,40 @@ #define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 #endif -#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND -#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5 #endif -#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND -#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0 #endif -#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND -#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5 #endif -#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND -#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND +#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0 #endif #ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND @@ -90,6 +114,10 @@ #define CPPHTTPLIB_TCP_NODELAY false #endif +#ifndef CPPHTTPLIB_IPV6_V6ONLY +#define CPPHTTPLIB_IPV6_V6ONLY false +#endif + #ifndef CPPHTTPLIB_RECV_BUFSIZ #define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) #endif @@ -115,6 +143,12 @@ #ifndef CPPHTTPLIB_LISTEN_BACKLOG #define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +#if !defined(CPPHTTPLIB_USE_POLL) && !defined(CPPHTTPLIB_USE_SELECT) +#define CPPHTTPLIB_USE_POLL +#elif defined(CPPHTTPLIB_USE_POLL) && defined(CPPHTTPLIB_USE_SELECT) +#error "CPPHTTPLIB_USE_POLL and CPPHTTPLIB_USE_SELECT are mutually exclusive" #endif /* @@ -165,6 +199,7 @@ using ssize_t = long; #endif using socket_t = SOCKET; +using socklen_t = int; #ifdef CPPHTTPLIB_USE_POLL #define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) #endif @@ -194,7 +229,9 @@ using socket_t = SOCKET; #include #include #include +#ifndef __VMS #include +#endif #include #include #include @@ -269,7 +306,12 @@ using socket_t = int; #include #include -#if OPENSSL_VERSION_NUMBER < 0x30000000L +#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER < 0x1010107f +#error Please use OpenSSL or a current version of BoringSSL +#endif +#define SSL_get1_peer_certificate SSL_get_peer_certificate +#elif OPENSSL_VERSION_NUMBER < 0x30000000L #error Sorry, OpenSSL versions prior to 3.0.0 are not supported #endif @@ -316,16 +358,68 @@ namespace httplib return std::unique_ptr(new RT[n]); } - struct ci { - bool operator()(const std::string& s1, const std::string& s2) const + namespace case_ignore + { + + inline unsigned char to_lower(int c) { - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), - s2.end(), - [](unsigned char c1, unsigned char c2) { - return ::tolower(c1) < ::tolower(c2); - }); + const static unsigned char table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, + 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, + 122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, + 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, + 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, + 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 224, 225, 226, + 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 215, 248, 249, 250, 251, 252, 253, 254, 223, 224, + 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, + 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, + 255, + }; + return table[(unsigned char)(char)c]; } - }; + + inline bool equal(const std::string& a, const std::string& b) + { + return a.size() == b.size() && + std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { + return to_lower(ca) == to_lower(cb); + }); + } + + struct equal_to { + bool operator()(const std::string& a, const std::string& b) const + { + return equal(a, b); + } + }; + + struct hash { + size_t operator()(const std::string& key) const + { + return hash_core(key.data(), key.size(), 0); + } + + size_t hash_core(const char* s, size_t l, size_t h) const + { + return (l == 0) ? h + : hash_core(s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no + // overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(to_lower(*s))); + } + }; + + } // namespace case_ignore // This is based on // "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". @@ -334,7 +428,7 @@ namespace httplib explicit scope_exit(std::function&& f) : exit_function(std::move(f)), execute_on_destruction{ true } { - } +} scope_exit(scope_exit&& rhs) noexcept : exit_function(std::move(rhs.exit_function)), @@ -361,6 +455,15 @@ namespace httplib } // namespace detail + enum SSLVerifierResponse { + // no decision has been made, use the built-in certificate verifier + NoDecisionMade, + // connection certificate is verified and accepted + CertificateAccepted, + // connection certificate was processed but is rejected + CertificateRejected + }; + enum StatusCode { // Information responses Continue_100 = 100, @@ -436,7 +539,9 @@ namespace httplib NetworkAuthenticationRequired_511 = 511, }; - using Headers = std::multimap; + using Headers = + std::unordered_multimap; using Params = std::multimap; using Match = std::smatch; @@ -525,7 +630,7 @@ namespace httplib : reader_(std::move(reader)), multipart_reader_(std::move(multipart_reader)) { - } +} bool operator()(MultipartContentHeader header, ContentReceiver receiver) const @@ -548,6 +653,7 @@ namespace httplib struct Request { std::string method; std::string path; + Params params; Headers headers; std::string body; @@ -559,11 +665,11 @@ namespace httplib // for server std::string version; std::string target; - Params params; MultipartFormDataMap files; Ranges ranges; Match matches; std::unordered_map path_params; + std::function is_connection_closed = []() { return true; }; // for client ResponseHandler response_handler; @@ -574,8 +680,10 @@ namespace httplib #endif bool has_header(const std::string& key) const; - std::string get_header_value(const std::string& key, size_t id = 0) const; - uint64_t get_header_value_u64(const std::string& key, size_t id = 0) const; + std::string get_header_value(const std::string& key, const char* def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string& key, uint64_t def = 0, + size_t id = 0) const; size_t get_header_value_count(const std::string& key) const; void set_header(const std::string& key, const std::string& val); @@ -595,6 +703,8 @@ namespace httplib ContentProvider content_provider_; bool is_chunked_content_provider_ = false; size_t authorization_count_ = 0; + std::chrono::time_point start_time_ = + (std::chrono::steady_clock::time_point::min)(); }; struct Response { @@ -606,8 +716,10 @@ namespace httplib std::string location; // Redirect location bool has_header(const std::string& key) const; - std::string get_header_value(const std::string& key, size_t id = 0) const; - uint64_t get_header_value_u64(const std::string& key, size_t id = 0) const; + std::string get_header_value(const std::string& key, const char* def = "", + size_t id = 0) const; + uint64_t get_header_value_u64(const std::string& key, uint64_t def = 0, + size_t id = 0) const; size_t get_header_value_count(const std::string& key) const; void set_header(const std::string& key, const std::string& val); @@ -628,6 +740,10 @@ namespace httplib const std::string& content_type, ContentProviderWithoutLength provider, ContentProviderResourceReleaser resource_releaser = nullptr); + void set_file_content(const std::string& path, + const std::string& content_type); + void set_file_content(const std::string& path); + Response() = default; Response(const Response&) = default; Response& operator=(const Response&) = default; @@ -646,6 +762,8 @@ namespace httplib ContentProviderResourceReleaser content_provider_resource_releaser_; bool is_chunked_content_provider_ = false; bool content_provider_success_ = false; + std::string file_content_path_; + std::string file_content_content_type_; }; class Stream { @@ -661,8 +779,8 @@ namespace httplib virtual void get_local_ip_and_port(std::string& ip, int& port) const = 0; virtual socket_t socket() const = 0; - template - ssize_t write_format(const char* fmt, const Args &...args); + virtual time_t duration() const = 0; + ssize_t write(const char* ptr); ssize_t write(const std::string& s); }; @@ -746,7 +864,8 @@ namespace httplib fn(); } -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \ + !defined(LIBRESSL_VERSION_NUMBER) OPENSSL_thread_stop(); #endif } @@ -769,6 +888,17 @@ namespace httplib using SocketOptions = std::function; + namespace detail + { + + bool set_socket_opt_impl(socket_t sock, int level, int optname, + const void* optval, socklen_t optlen); + bool set_socket_opt(socket_t sock, int level, int optname, int opt); + bool set_socket_opt_time(socket_t sock, int level, int optname, time_t sec, + time_t usec); + + } // namespace detail + void default_socket_options(socket_t sock); const char* status_message(int status); @@ -790,7 +920,7 @@ namespace httplib * Captures parameters in request path and stores them in Request::path_params * * Capture name is a substring of a pattern from : to /. - * The rest of the pattern is matched agains the request path directly + * The rest of the pattern is matched against the request path directly * Parameters are captured starting from the next character after * the end of the last matched static pattern fragment until the next /. * @@ -811,7 +941,6 @@ namespace httplib bool match(Request& request) const override; private: - static constexpr char marker = ':'; // Treat segment separators as the end of path parameter capture // Does not need to handle query parameters as they are parsed before path // matching @@ -912,6 +1041,7 @@ namespace httplib Server& set_address_family(int family); Server& set_tcp_nodelay(bool on); + Server& set_ipv6_v6only(bool on); Server& set_socket_options(SocketOptions socket_options); Server& set_default_headers(Headers headers); @@ -944,21 +1074,24 @@ namespace httplib bool is_running() const; void wait_until_ready() const; void stop(); + void decommission(); std::function new_task_queue; protected: - bool process_request(Stream& strm, bool close_connection, + bool process_request(Stream& strm, const std::string& remote_addr, + int remote_port, const std::string& local_addr, + int local_port, bool close_connection, bool& connection_closed, const std::function& setup_request); std::atomic svr_sock_{ INVALID_SOCKET }; size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; - time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; - time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; - time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; - time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND; time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; @@ -1018,7 +1151,7 @@ namespace httplib virtual bool process_and_close_socket(socket_t sock); std::atomic is_running_{ false }; - std::atomic done_{ false }; + std::atomic is_decommissioned{ false }; struct MountPointEntry { std::string mount_point; @@ -1051,6 +1184,7 @@ namespace httplib int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; SocketOptions socket_options_ = default_socket_options; Headers default_headers_; @@ -1070,6 +1204,7 @@ namespace httplib SSLConnection, SSLLoadingCerts, SSLServerVerification, + SSLServerHostnameVerification, UnsupportedMultipartBoundaryChars, Compression, ConnectionTimeout, @@ -1109,9 +1244,10 @@ namespace httplib // Request Headers bool has_request_header(const std::string& key) const; std::string get_request_header_value(const std::string& key, + const char* def = "", size_t id = 0) const; uint64_t get_request_header_value_u64(const std::string& key, - size_t id = 0) const; + uint64_t def = 0, size_t id = 0) const; size_t get_request_header_value_count(const std::string& key) const; private: @@ -1334,6 +1470,7 @@ namespace httplib void set_address_family(int family); void set_tcp_nodelay(bool on); + void set_ipv6_v6only(bool on); void set_socket_options(SocketOptions socket_options); void set_connection_timeout(time_t sec, time_t usec = 0); @@ -1349,6 +1486,10 @@ namespace httplib template void set_write_timeout(const std::chrono::duration& duration); + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration& duration); + void set_basic_auth(const std::string& username, const std::string& password); void set_bearer_token_auth(const std::string& token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1385,6 +1526,9 @@ namespace httplib #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier( + std::function verifier); #endif void set_logger(Logger logger); @@ -1451,10 +1595,11 @@ namespace httplib time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; - time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; - time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; - time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; - time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; + time_t max_timeout_msec_ = CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND; std::string basic_auth_username_; std::string basic_auth_password_; @@ -1471,6 +1616,7 @@ namespace httplib int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; SocketOptions socket_options_ = nullptr; bool compress_ = false; @@ -1498,6 +1644,8 @@ namespace httplib #ifdef CPPHTTPLIB_OPENSSL_SUPPORT bool server_certificate_verification_ = true; + bool server_hostname_verification_ = true; + std::function server_certificate_verifier_; #endif Logger logger_; @@ -1531,8 +1679,10 @@ namespace httplib std::string adjust_host_string(const std::string& host) const; - virtual bool process_socket(const Socket& socket, - std::function callback); + virtual bool + process_socket(const Socket& socket, + std::chrono::time_point start_time, + std::function callback); virtual bool is_ssl() const; }; @@ -1774,6 +1924,10 @@ namespace httplib template void set_write_timeout(const std::chrono::duration& duration); + void set_max_timeout(time_t msec); + template + void set_max_timeout(const std::chrono::duration& duration); + void set_basic_auth(const std::string& username, const std::string& password); void set_bearer_token_auth(const std::string& token); #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1803,6 +1957,9 @@ namespace httplib #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); + void set_server_certificate_verifier( + std::function verifier); #endif void set_logger(Logger logger); @@ -1889,12 +2046,16 @@ namespace httplib void shutdown_ssl(Socket& socket, bool shutdown_gracefully) override; void shutdown_ssl_impl(Socket& socket, bool shutdown_gracefully); - bool process_socket(const Socket& socket, - std::function callback) override; + bool + process_socket(const Socket& socket, + std::chrono::time_point start_time, + std::function callback) override; bool is_ssl() const override; - bool connect_with_proxy(Socket& sock, Response& res, bool& success, - Error& error); + bool connect_with_proxy( + Socket& sock, + std::chrono::time_point start_time, + Response& res, bool& success, Error& error); bool initialize_ssl(Socket& socket, Error& error); bool load_certs(); @@ -1933,76 +2094,96 @@ namespace httplib callback(static_cast(sec), static_cast(usec)); } - inline uint64_t get_header_value_u64(const Headers& headers, - const std::string& key, size_t id, - uint64_t def) + inline bool is_numeric(const std::string& str) { + return !str.empty() && std::all_of(str.begin(), str.end(), ::isdigit); + } + + inline uint64_t get_header_value_u64(const Headers& headers, + const std::string& key, uint64_t def, + size_t id, bool& is_invalid_value) + { + is_invalid_value = false; auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); if (it != rng.second) { - return std::strtoull(it->second.data(), nullptr, 10); + if (is_numeric(it->second)) { + return std::strtoull(it->second.data(), nullptr, 10); + } + else { + is_invalid_value = true; + } } return def; } + inline uint64_t get_header_value_u64(const Headers& headers, + const std::string& key, uint64_t def, + size_t id) + { + bool dummy = false; + return get_header_value_u64(headers, key, def, id, dummy); + } + } // namespace detail inline uint64_t Request::get_header_value_u64(const std::string& key, - size_t id) const + uint64_t def, size_t id) const { - return detail::get_header_value_u64(headers, key, id, 0); + return detail::get_header_value_u64(headers, key, def, id); } inline uint64_t Response::get_header_value_u64(const std::string& key, - size_t id) const + uint64_t def, size_t id) const { - return detail::get_header_value_u64(headers, key, id, 0); + return detail::get_header_value_u64(headers, key, def, id); } - template - inline ssize_t Stream::write_format(const char* fmt, const Args &...args) + namespace detail { - const auto bufsiz = 2048; - std::array buf{}; - auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); - if (sn <= 0) { return sn; } - - auto n = static_cast(sn); - - if (n >= buf.size() - 1) { - std::vector glowable_buf(buf.size()); - - while (n >= glowable_buf.size() - 1) { - glowable_buf.resize(glowable_buf.size() * 2); - n = static_cast( - snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); - } - return write(&glowable_buf[0], n); + inline bool set_socket_opt_impl(socket_t sock, int level, int optname, + const void* optval, socklen_t optlen) + { + return setsockopt(sock, level, optname, +#ifdef _WIN32 + reinterpret_cast(optval), +#else + optval, +#endif + optlen) == 0; } - else { - return write(buf.data(), n); + + inline bool set_socket_opt(socket_t sock, int level, int optname, int optval) + { + return set_socket_opt_impl(sock, level, optname, &optval, sizeof(optval)); } - } + + inline bool set_socket_opt_time(socket_t sock, int level, int optname, + time_t sec, time_t usec) + { +#ifdef _WIN32 + auto timeout = static_cast(sec * 1000 + usec / 1000); +#else + timeval timeout; + timeout.tv_sec = static_cast(sec); + timeout.tv_usec = static_cast(usec); +#endif + return set_socket_opt_impl(sock, level, optname, &timeout, sizeof(timeout)); + } + + } // namespace detail inline void default_socket_options(socket_t sock) { - int yes = 1; -#ifdef _WIN32 - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - reinterpret_cast(&yes), sizeof(yes)); - setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - reinterpret_cast(&yes), sizeof(yes)); -#else + detail::set_socket_opt(sock, SOL_SOCKET, #ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, - reinterpret_cast(&yes), sizeof(yes)); + SO_REUSEPORT, #else - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, - reinterpret_cast(&yes), sizeof(yes)); -#endif + SO_REUSEADDR, #endif + 1); } inline const char* status_message(int status) @@ -2132,6 +2313,8 @@ namespace httplib case Error::SSLConnection: return "SSL connection failed"; case Error::SSLLoadingCerts: return "SSL certificate loading failed"; case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::SSLServerHostnameVerification: + return "SSL server hostname verification failed"; case Error::UnsupportedMultipartBoundaryChars: return "Unsupported HTTP multipart boundary characters"; case Error::Compression: return "Compression failed"; @@ -2152,9 +2335,10 @@ namespace httplib } inline uint64_t Result::get_request_header_value_u64(const std::string& key, + uint64_t def, size_t id) const { - return detail::get_header_value_u64(request_headers_, key, id, 0); + return detail::get_header_value_u64(request_headers_, key, def, id); } template @@ -2182,6 +2366,15 @@ namespace httplib duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); } + template + inline void ClientImpl::set_max_timeout( + const std::chrono::duration& duration) + { + auto msec = + std::chrono::duration_cast(duration).count(); + set_max_timeout(msec); + } + template inline void Client::set_connection_timeout( const std::chrono::duration& duration) @@ -2203,6 +2396,13 @@ namespace httplib cli_->set_write_timeout(duration); } + template + inline void + Client::set_max_timeout(const std::chrono::duration& duration) + { + cli_->set_max_timeout(duration); + } + /* * Forward declarations and types that will be part of the .h file if split into * .h + .cc. @@ -2224,6 +2424,37 @@ namespace httplib namespace detail { +#if defined(_WIN32) + inline std::wstring u8string_to_wstring(const char* s) + { + std::wstring ws; + auto len = static_cast(strlen(s)); + auto wlen = ::MultiByteToWideChar(CP_UTF8, 0, s, len, nullptr, 0); + if (wlen > 0) { + ws.resize(wlen); + wlen = ::MultiByteToWideChar( + CP_UTF8, 0, s, len, + const_cast(reinterpret_cast(ws.data())), wlen); + if (wlen != static_cast(ws.size())) { ws.clear(); } + } + return ws; + } +#endif + + struct FileStat { + FileStat(const std::string& path); + bool is_file() const; + bool is_dir() const; + + private: +#if defined(_WIN32) + struct _stat st_; +#else + struct stat st_; +#endif + int ret_ = -1; + }; + std::string encode_query_param(const std::string& value); std::string decode_url(const std::string& s, bool convert_plus_to_space); @@ -2248,20 +2479,25 @@ namespace httplib void split(const char* b, const char* e, char d, size_t m, std::function fn); - bool process_client_socket(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, + bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, std::function callback); - socket_t create_client_socket( - const std::string& host, const std::string& ip, int port, - int address_family, bool tcp_nodelay, SocketOptions socket_options, - time_t connection_timeout_sec, time_t connection_timeout_usec, - time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, const std::string& intf, Error& error); + socket_t create_client_socket(const std::string& host, const std::string& ip, + int port, int address_family, bool tcp_nodelay, + bool ipv6_v6only, SocketOptions socket_options, + time_t connection_timeout_sec, + time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + const std::string& intf, Error& error); const char* get_header_value(const Headers& headers, const std::string& key, - size_t id = 0, const char* def = nullptr); + const char* def, size_t id); std::string params_to_query_str(const Params& params); @@ -2296,6 +2532,7 @@ namespace httplib void get_remote_ip_and_port(std::string& ip, int& port) const override; void get_local_ip_and_port(std::string& ip, int& port) const override; socket_t socket() const override; + time_t duration() const override; const std::string& get_buffer() const; @@ -2409,7 +2646,7 @@ namespace httplib char* fixed_buffer_; const size_t fixed_buffer_size_; size_t fixed_buffer_used_size_ = 0; - std::string glowable_buffer_; + std::string growable_buffer_; }; class mmap { @@ -2426,15 +2663,77 @@ namespace httplib private: #if defined(_WIN32) - HANDLE hFile_; - HANDLE hMapping_; + HANDLE hFile_ = NULL; + HANDLE hMapping_ = NULL; #else - int fd_; + int fd_ = -1; #endif - size_t size_; - void* addr_; + size_t size_ = 0; + void* addr_ = nullptr; + bool is_open_empty_file = false; }; + // NOTE: https://www.rfc-editor.org/rfc/rfc9110#section-5 + namespace fields + { + + inline bool is_token_char(char c) + { + return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' || + c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || + c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~'; + } + + inline bool is_token(const std::string& s) + { + if (s.empty()) { return false; } + for (auto c : s) { + if (!is_token_char(c)) { return false; } + } + return true; + } + + inline bool is_field_name(const std::string& s) { return is_token(s); } + + inline bool is_vchar(char c) { return c >= 33 && c <= 126; } + + inline bool is_obs_text(char c) { return 128 <= static_cast(c); } + + inline bool is_field_vchar(char c) { return is_vchar(c) || is_obs_text(c); } + + inline bool is_field_content(const std::string& s) + { + if (s.empty()) { return true; } + + if (s.size() == 1) { + return is_field_vchar(s[0]); + } + else if (s.size() == 2) { + return is_field_vchar(s[0]) && is_field_vchar(s[1]); + } + else { + size_t i = 0; + + if (!is_field_vchar(s[i])) { return false; } + i++; + + while (i < s.size() - 1) { + auto c = s[i++]; + if (c == ' ' || c == '\t' || is_field_vchar(c)) { + } + else { + return false; + } + } + + return is_field_vchar(s[i]); + } + } + + inline bool is_field_value(const std::string& s) { return is_field_content(s); } + + } // namespace fields + } // namespace detail // ---------------------------------------------------------------------------- @@ -2562,22 +2861,6 @@ namespace httplib return out; } - inline bool is_file(const std::string& path) - { -#ifdef _WIN32 - return _access_s(path.c_str(), 0) == 0; -#else - struct stat st; - return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); -#endif - } - - inline bool is_dir(const std::string& path) - { - struct stat st; - return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); - } - inline bool is_valid_path(const std::string& path) { size_t level = 0; @@ -2624,6 +2907,24 @@ namespace httplib return true; } + inline FileStat::FileStat(const std::string& path) + { +#if defined(_WIN32) + auto wpath = u8string_to_wstring(path.c_str()); + ret_ = _wstat(wpath.c_str(), &st_); +#else + ret_ = stat(path.c_str(), &st_); +#endif + } + inline bool FileStat::is_file() const + { + return ret_ >= 0 && S_ISREG(st_.st_mode); + } + inline bool FileStat::is_dir() const + { + return ret_ >= 0 && S_ISDIR(st_.st_mode); + } + inline std::string encode_query_param(const std::string& value) { std::ostringstream escaped; @@ -2831,21 +3132,21 @@ namespace httplib inline const char* stream_line_reader::ptr() const { - if (glowable_buffer_.empty()) { + if (growable_buffer_.empty()) { return fixed_buffer_; } else { - return glowable_buffer_.data(); + return growable_buffer_.data(); } } inline size_t stream_line_reader::size() const { - if (glowable_buffer_.empty()) { + if (growable_buffer_.empty()) { return fixed_buffer_used_size_; } else { - return glowable_buffer_.size(); + return growable_buffer_.size(); } } @@ -2858,7 +3159,11 @@ namespace httplib inline bool stream_line_reader::getline() { fixed_buffer_used_size_ = 0; - glowable_buffer_.clear(); + growable_buffer_.clear(); + +#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + char prev_byte = 0; +#endif for (size_t i = 0;; i++) { char byte; @@ -2878,7 +3183,12 @@ namespace httplib append(byte); +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR if (byte == '\n') { break; } +#else + if (prev_byte == '\r' && byte == '\n') { break; } + prev_byte = byte; +#endif } return true; @@ -2891,25 +3201,15 @@ namespace httplib fixed_buffer_[fixed_buffer_used_size_] = '\0'; } else { - if (glowable_buffer_.empty()) { + if (growable_buffer_.empty()) { assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); - glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + growable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); } - glowable_buffer_ += c; + growable_buffer_ += c; } } - inline mmap::mmap(const char* path) -#if defined(_WIN32) - : hFile_(NULL), hMapping_(NULL) -#else - : fd_(-1) -#endif - , - size_(0), addr_(nullptr) - { - open(path); - } + inline mmap::mmap(const char* path) { open(path); } inline mmap::~mmap() { close(); } @@ -2918,12 +3218,10 @@ namespace httplib close(); #if defined(_WIN32) - std::wstring wpath; - for (size_t i = 0; i < strlen(path); i++) { - wpath += path[i]; - } + auto wpath = u8string_to_wstring(path); + if (wpath.empty()) { return false; } -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES) && (_WIN32_WINNT >= _WIN32_WINNT_WIN8) +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, NULL); #else @@ -2933,37 +3231,47 @@ namespace httplib if (hFile_ == INVALID_HANDLE_VALUE) { return false; } -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM | WINAPI_PARTITION_GAMES) LARGE_INTEGER size{}; if (!::GetFileSizeEx(hFile_, &size)) { return false; } + // If the following line doesn't compile due to QuadPart, update Windows SDK. + // See: + // https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721 + if (static_cast(size.QuadPart) > + (std::numeric_limits::max)()) { + // `size_t` might be 32-bits, on 32-bits Windows. + return false; + } size_ = static_cast(size.QuadPart); -#else - DWORD sizeHigh; - DWORD sizeLow; - sizeLow = ::GetFileSize(hFile_, &sizeHigh); - if (sizeLow == INVALID_FILE_SIZE) { return false; } - size_ = (static_cast(sizeHigh) << (sizeof(DWORD) * 8)) | sizeLow; -#endif -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && (_WIN32_WINNT >= _WIN32_WINNT_WIN8) +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 hMapping_ = ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); #else - hMapping_ = - ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, size.HighPart, - size.LowPart, NULL); + hMapping_ = ::CreateFileMappingW(hFile_, NULL, PAGE_READONLY, 0, 0, NULL); #endif + // Special treatment for an empty file... + if (hMapping_ == NULL && size_ == 0) { + close(); + is_open_empty_file = true; + return true; + } + if (hMapping_ == NULL) { close(); return false; } -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && (_WIN32_WINNT >= _WIN32_WINNT_WIN8) +#if _WIN32_WINNT >= _WIN32_WINNT_WIN8 addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); #else addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0); #endif + + if (addr_ == nullptr) { + close(); + return false; + } #else fd_ = ::open(path, O_RDONLY); if (fd_ == -1) { return false; } @@ -2976,23 +3284,28 @@ namespace httplib size_ = static_cast(sb.st_size); addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); -#endif - if (addr_ == nullptr) { + // Special treatment for an empty file... + if (addr_ == MAP_FAILED && size_ == 0) { close(); + is_open_empty_file = true; return false; } +#endif return true; } - inline bool mmap::is_open() const { return addr_ != nullptr; } + inline bool mmap::is_open() const + { + return is_open_empty_file ? true : addr_ != nullptr; + } inline size_t mmap::size() const { return size_; } inline const char* mmap::data() const { - return static_cast(addr_); + return is_open_empty_file ? "" : static_cast(addr_); } inline void mmap::close() @@ -3012,6 +3325,8 @@ namespace httplib ::CloseHandle(hFile_); hFile_ = INVALID_HANDLE_VALUE; } + + is_open_empty_file = false; #else if (addr_ != nullptr) { munmap(addr_, size_); @@ -3039,7 +3354,10 @@ namespace httplib ssize_t res = 0; while (true) { res = fn(); - if (res < 0 && errno == EINTR) { continue; } + if (res < 0 && errno == EINTR) { + std::this_thread::sleep_for(std::chrono::microseconds{ 1 }); + continue; + } break; } return res; @@ -3072,62 +3390,46 @@ namespace httplib }); } - inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) + template + inline ssize_t select_impl(socket_t sock, time_t sec, time_t usec) { #ifdef CPPHTTPLIB_USE_POLL - struct pollfd pfd_read; - pfd_read.fd = sock; - pfd_read.events = POLLIN; + struct pollfd pfd; + pfd.fd = sock; + pfd.events = (Read ? POLLIN : POLLOUT); auto timeout = static_cast(sec * 1000 + usec / 1000); - return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + return handle_EINTR([&]() { return poll(&pfd, 1, timeout); }); #else #ifndef _WIN32 if (sock >= FD_SETSIZE) { return -1; } #endif - fd_set fds; + fd_set fds, * rfds, * wfds; FD_ZERO(&fds); FD_SET(sock, &fds); + rfds = (Read ? &fds : nullptr); + wfds = (Read ? nullptr : &fds); timeval tv; tv.tv_sec = static_cast(sec); tv.tv_usec = static_cast(usec); return handle_EINTR([&]() { - return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + return select(static_cast(sock + 1), rfds, wfds, nullptr, &tv); }); #endif } + inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) + { + return select_impl(sock, sec, usec); + } + inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { -#ifdef CPPHTTPLIB_USE_POLL - struct pollfd pfd_read; - pfd_read.fd = sock; - pfd_read.events = POLLOUT; - - auto timeout = static_cast(sec * 1000 + usec / 1000); - - return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); -#else -#ifndef _WIN32 - if (sock >= FD_SETSIZE) { return -1; } -#endif - - fd_set fds; - FD_ZERO(&fds); - FD_SET(sock, &fds); - - timeval tv; - tv.tv_sec = static_cast(sec); - tv.tv_usec = static_cast(usec); - - return handle_EINTR([&]() { - return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); - }); -#endif + return select_impl(sock, sec, usec); } inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, @@ -3204,7 +3506,10 @@ namespace httplib class SocketStream final : public Stream { public: SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, - time_t write_timeout_sec, time_t write_timeout_usec); + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + (std::chrono::steady_clock::time_point::min)()); ~SocketStream() override; bool is_readable() const override; @@ -3214,6 +3519,7 @@ namespace httplib void get_remote_ip_and_port(std::string& ip, int& port) const override; void get_local_ip_and_port(std::string& ip, int& port) const override; socket_t socket() const override; + time_t duration() const override; private: socket_t sock_; @@ -3221,6 +3527,8 @@ namespace httplib time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time; std::vector read_buff_; size_t read_buff_off_ = 0; @@ -3232,9 +3540,12 @@ namespace httplib #ifdef CPPHTTPLIB_OPENSSL_SUPPORT class SSLSocketStream final : public Stream { public: - SSLSocketStream(socket_t sock, SSL* ssl, time_t read_timeout_sec, + SSLSocketStream( + socket_t sock, SSL* ssl, time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec); + time_t write_timeout_usec, time_t max_timeout_msec = 0, + std::chrono::time_point start_time = + (std::chrono::steady_clock::time_point::min)()); ~SSLSocketStream() override; bool is_readable() const override; @@ -3244,6 +3555,7 @@ namespace httplib void get_remote_ip_and_port(std::string& ip, int& port) const override; void get_local_ip_and_port(std::string& ip, int& port) const override; socket_t socket() const override; + time_t duration() const override; private: socket_t sock_; @@ -3252,29 +3564,45 @@ namespace httplib time_t read_timeout_usec_; time_t write_timeout_sec_; time_t write_timeout_usec_; + time_t max_timeout_msec_; + const std::chrono::time_point start_time; }; #endif - inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) + inline bool keep_alive(const std::atomic& svr_sock, socket_t sock, + time_t keep_alive_timeout_sec) { using namespace std::chrono; - auto start = steady_clock::now(); + + const auto interval_usec = + CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND; + + // Avoid expensive `steady_clock::now()` call for the first time + if (select_read(sock, 0, interval_usec) > 0) { return true; } + + const auto start = steady_clock::now() - microseconds{ interval_usec }; + const auto timeout = seconds{ keep_alive_timeout_sec }; + while (true) { - auto val = select_read(sock, 0, 10000); + if (svr_sock == INVALID_SOCKET) { + break; // Server socket is closed + } + + auto val = select_read(sock, 0, interval_usec); if (val < 0) { - return false; + break; // Ssocket error } else if (val == 0) { - auto current = steady_clock::now(); - auto duration = duration_cast(current - start); - auto timeout = keep_alive_timeout_sec * 1000; - if (duration.count() > timeout) { return false; } - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + if (steady_clock::now() - start > timeout) { + break; // Timeout + } } else { - return true; + return true; // Ready for read } } + + return false; } template @@ -3286,8 +3614,7 @@ namespace httplib assert(keep_alive_max_count > 0); auto ret = false; auto count = keep_alive_max_count; - while (svr_sock != INVALID_SOCKET && count > 0 && - keep_alive(sock, keep_alive_timeout_sec)) { + while (count > 0 && keep_alive(svr_sock, sock, keep_alive_timeout_sec)) { auto close_connection = count == 1; auto connection_closed = false; ret = callback(close_connection, connection_closed); @@ -3314,14 +3641,16 @@ namespace httplib }); } - inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, - time_t write_timeout_sec, - time_t write_timeout_usec, + inline bool process_client_socket( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, std::function callback) { SocketStream strm(sock, read_timeout_sec, read_timeout_usec, - write_timeout_sec, write_timeout_usec); + write_timeout_sec, write_timeout_usec, max_timeout_msec, + start_time); return callback(strm); } @@ -3334,10 +3663,31 @@ namespace httplib #endif } + inline std::string escape_abstract_namespace_unix_domain(const std::string& s) + { + if (s.size() > 1 && s[0] == '\0') { + auto ret = s; + ret[0] = '@'; + return ret; + } + return s; + } + + inline std::string + unescape_abstract_namespace_unix_domain(const std::string& s) + { + if (s.size() > 1 && s[0] == '@') { + auto ret = s; + ret[0] = '\0'; + return ret; + } + return s; + } + template socket_t create_socket(const std::string& host, const std::string& ip, int port, int address_family, int socket_flags, bool tcp_nodelay, - SocketOptions socket_options, + bool ipv6_v6only, SocketOptions socket_options, BindOrConnect bind_or_connect) { // Get address info @@ -3347,7 +3697,7 @@ namespace httplib memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; + hints.ai_protocol = IPPROTO_IP; if (!ip.empty()) { node = ip.c_str(); @@ -3366,20 +3716,32 @@ namespace httplib const auto addrlen = host.length(); if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } +#ifdef SOCK_CLOEXEC + auto sock = socket(hints.ai_family, hints.ai_socktype | SOCK_CLOEXEC, + hints.ai_protocol); +#else auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); +#endif + if (sock != INVALID_SOCKET) { sockaddr_un addr{}; addr.sun_family = AF_UNIX; - std::copy(host.begin(), host.end(), addr.sun_path); + + auto unescaped_host = unescape_abstract_namespace_unix_domain(host); + std::copy(unescaped_host.begin(), unescaped_host.end(), addr.sun_path); hints.ai_addr = reinterpret_cast(&addr); hints.ai_addrlen = static_cast( sizeof(addr) - sizeof(addr.sun_path) + addrlen); +#ifndef SOCK_CLOEXEC fcntl(sock, F_SETFD, FD_CLOEXEC); +#endif + if (socket_options) { socket_options(sock); } - if (!bind_or_connect(sock, hints)) { + bool dummy; + if (!bind_or_connect(sock, hints, dummy)) { close_socket(sock); sock = INVALID_SOCKET; } @@ -3396,6 +3758,7 @@ namespace httplib #endif return INVALID_SOCKET; } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); for (auto rp = result; rp; rp = rp->ai_next) { // Create a socket @@ -3421,51 +3784,41 @@ namespace httplib sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); } #else + +#ifdef SOCK_CLOEXEC + auto sock = + socket(rp->ai_family, rp->ai_socktype | SOCK_CLOEXEC, rp->ai_protocol); +#else auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + #endif if (sock == INVALID_SOCKET) { continue; } -#ifndef _WIN32 +#if !defined _WIN32 && !defined SOCK_CLOEXEC if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { close_socket(sock); continue; } #endif - if (tcp_nodelay) { - auto yes = 1; -#ifdef _WIN32 - setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - reinterpret_cast(&yes), sizeof(yes)); -#else - setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, - reinterpret_cast(&yes), sizeof(yes)); -#endif + if (tcp_nodelay) { set_socket_opt(sock, IPPROTO_TCP, TCP_NODELAY, 1); } + + if (rp->ai_family == AF_INET6) { + set_socket_opt(sock, IPPROTO_IPV6, IPV6_V6ONLY, ipv6_v6only ? 1 : 0); } if (socket_options) { socket_options(sock); } - if (rp->ai_family == AF_INET6) { - auto no = 0; -#ifdef _WIN32 - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, - reinterpret_cast(&no), sizeof(no)); -#else - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, - reinterpret_cast(&no), sizeof(no)); -#endif - } - // bind or connect - if (bind_or_connect(sock, *rp)) { - freeaddrinfo(result); - return sock; - } + auto quit = false; + if (bind_or_connect(sock, *rp, quit)) { return sock; } close_socket(sock); + + if (quit) { break; } } - freeaddrinfo(result); return INVALID_SOCKET; } @@ -3501,6 +3854,7 @@ namespace httplib hints.ai_protocol = 0; if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); auto ret = false; for (auto rp = result; rp; rp = rp->ai_next) { @@ -3511,7 +3865,6 @@ namespace httplib } } - freeaddrinfo(result); return ret; } @@ -3524,6 +3877,8 @@ namespace httplib { struct ifaddrs* ifap; getifaddrs(&ifap); + auto se = detail::scope_exit([&] { freeifaddrs(ifap); }); + std::string addr_candidate; for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { if (ifa->ifa_addr && ifn == ifa->ifa_name && @@ -3533,7 +3888,6 @@ namespace httplib auto sa = reinterpret_cast(ifa->ifa_addr); char buf[INET_ADDRSTRLEN]; if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { - freeifaddrs(ifap); return std::string(buf, INET_ADDRSTRLEN); } } @@ -3548,7 +3902,6 @@ namespace httplib addr_candidate = std::string(buf, INET6_ADDRSTRLEN); } else { - freeifaddrs(ifap); return std::string(buf, INET6_ADDRSTRLEN); } } @@ -3556,21 +3909,22 @@ namespace httplib } } } - freeifaddrs(ifap); return addr_candidate; } #endif inline socket_t create_client_socket( const std::string& host, const std::string& ip, int port, - int address_family, bool tcp_nodelay, SocketOptions socket_options, - time_t connection_timeout_sec, time_t connection_timeout_usec, - time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + int address_family, bool tcp_nodelay, bool ipv6_v6only, + SocketOptions socket_options, time_t connection_timeout_sec, + time_t connection_timeout_usec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, time_t write_timeout_usec, const std::string& intf, Error& error) { auto sock = create_socket( - host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), - [&](socket_t sock2, struct addrinfo& ai) -> bool { + host, ip, port, address_family, 0, tcp_nodelay, ipv6_v6only, + std::move(socket_options), + [&](socket_t sock2, struct addrinfo& ai, bool& quit) -> bool { if (!intf.empty()) { #ifdef USE_IF2IP auto ip_from_if = if2ip(address_family, intf); @@ -3594,40 +3948,17 @@ namespace httplib } error = wait_until_socket_is_ready(sock2, connection_timeout_sec, connection_timeout_usec); - if (error != Error::Success) { return false; } + if (error != Error::Success) { + if (error == Error::ConnectionTimeout) { quit = true; } + return false; + } } set_nonblocking(sock2, false); - - { -#ifdef _WIN32 - auto timeout = static_cast(read_timeout_sec * 1000 + - read_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(read_timeout_sec); - tv.tv_usec = static_cast(read_timeout_usec); - setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } - { - -#ifdef _WIN32 - auto timeout = static_cast(write_timeout_sec * 1000 + - write_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(write_timeout_sec); - tv.tv_usec = static_cast(write_timeout_usec); - setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } + set_socket_opt_time(sock2, SOL_SOCKET, SO_RCVTIMEO, read_timeout_sec, + read_timeout_usec); + set_socket_opt_time(sock2, SOL_SOCKET, SO_SNDTIMEO, write_timeout_sec, + write_timeout_usec); error = Error::Success; return true; @@ -3728,7 +4059,7 @@ namespace httplib namespace udl { - inline constexpr unsigned int operator"" _t(const char* s, size_t l) + inline constexpr unsigned int operator""_t(const char* s, size_t l) { return str2tag_core(s, l, 0); } @@ -3816,8 +4147,9 @@ namespace httplib case "application/protobuf"_t: case "application/xhtml+xml"_t: return true; - default: - return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + case "text/event-stream"_t: return false; + + default: return !content_type.rfind("text/", 0); } } @@ -4068,8 +4400,8 @@ namespace httplib } inline const char* get_header_value(const Headers& headers, - const std::string& key, size_t id, - const char* def) + const std::string& key, const char* def, + size_t id) { auto rng = headers.equal_range(key); auto it = rng.first; @@ -4078,15 +4410,6 @@ namespace httplib return def; } - inline bool compare_case_ignore(const std::string& a, const std::string& b) - { - if (a.size() != b.size()) { return false; } - for (size_t i = 0; i < b.size(); i++) { - if (::tolower(a[i]) != ::tolower(b[i])) { return false; } - } - return true; - } - template inline bool parse_header(const char* beg, const char* end, T fn) { @@ -4110,15 +4433,23 @@ namespace httplib p++; } - if (p < end) { + if (p <= end) { auto key_len = key_end - beg; if (!key_len) { return false; } auto key = std::string(beg, key_end); - auto val = compare_case_ignore(key, "Location") - ? std::string(p, end) - : decode_url(std::string(p, end), false); - fn(key, val); + auto val = std::string(p, end); + + if (!detail::fields::is_field_value(val)) { return false; } + + if (case_ignore::equal(key, "Location") || + case_ignore::equal(key, "Referer")) { + fn(key, val); + } + else { + fn(key, decode_url(val, false)); + } + return true; } @@ -4139,29 +4470,28 @@ namespace httplib if (line_reader.end_with_crlf()) { // Blank line indicates end of headers. if (line_reader.size() == 2) { break; } -#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR } else { +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR // Blank line indicates end of headers. if (line_reader.size() == 1) { break; } line_terminator_len = 1; - } #else - } - else { continue; // Skip invalid line. - } #endif + } if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } // Exclude line terminator auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; - parse_header(line_reader.ptr(), end, + if (!parse_header(line_reader.ptr(), end, [&](const std::string& key, const std::string& val) { headers.emplace(key, val); - }); + })) { + return false; + } } return true; @@ -4209,7 +4539,7 @@ namespace httplib uint64_t r = 0; for (;;) { auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); - if (n <= 0) { return true; } + if (n <= 0) { return false; } if (!out(buf, static_cast(n), r, 0)) { return false; } r += static_cast(n); @@ -4253,8 +4583,19 @@ namespace httplib assert(chunk_len == 0); - // Trailer - if (!line_reader.getline()) { return false; } + // NOTE: In RFC 9112, '7.1 Chunked Transfer Coding' mentions "The chunked + // transfer coding is complete when a chunk with a chunk-size of zero is + // received, possibly followed by a trailer section, and finally terminated by + // an empty line". https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1 + // + // In '7.1.3. Decoding Chunked', however, the pseudo-code in the section + // does't care for the existence of the final CRLF. In other words, it seems + // to be ok whether the final CRLF exists or not in the chunked data. + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.1.3 + // + // According to the reference code in RFC 9112, cpp-httplib now allows + // chunked transfer coding data without the final CRLF. + if (!line_reader.getline()) { return true; } while (strcmp(line_reader.ptr(), "\r\n") != 0) { if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } @@ -4276,8 +4617,8 @@ namespace httplib inline bool is_chunked_transfer_encoding(const Headers& headers) { - return compare_case_ignore( - get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked"); + return case_ignore::equal( + get_header_value(headers, "Transfer-Encoding", "", 0), "chunked"); } template @@ -4349,8 +4690,15 @@ namespace httplib ret = read_content_without_length(strm, out); } else { - auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0); - if (len > payload_max_length) { + auto is_invalid_value = false; + auto len = get_header_value_u64( + x.headers, "Content-Length", + (std::numeric_limits::max)(), 0, is_invalid_value); + + if (is_invalid_value) { + ret = false; + } + else if (len > payload_max_length) { exceed_payload_max_length = true; skip_content_with_length(strm, len); ret = false; @@ -4366,14 +4714,39 @@ namespace httplib } return ret; }); - } // namespace detail + } + + inline ssize_t write_request_line(Stream& strm, const std::string& method, + const std::string& path) + { + std::string s = method; + s += " "; + s += path; + s += " HTTP/1.1\r\n"; + return strm.write(s.data(), s.size()); + } + + inline ssize_t write_response_line(Stream& strm, int status) + { + std::string s = "HTTP/1.1 "; + s += std::to_string(status); + s += " "; + s += httplib::status_message(status); + s += "\r\n"; + return strm.write(s.data(), s.size()); + } inline ssize_t write_headers(Stream& strm, const Headers& headers) { ssize_t write_len = 0; for (const auto& x : headers) { - auto len = - strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + std::string s; + s = x.first; + s += ": "; + s += x.second; + s += "\r\n"; + + auto len = strm.write(s.data(), s.size()); if (len < 0) { return len; } write_len += len; } @@ -4850,7 +5223,7 @@ namespace httplib it = params.find("filename*"); if (it != params.end()) { - // Only allow UTF-8 enconnding... + // Only allow UTF-8 encoding... static const std::regex re_rfc5987_encoding( R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); @@ -4933,7 +5306,9 @@ namespace httplib { if (a.size() < b.size()) { return false; } for (size_t i = 0; i < b.size(); i++) { - if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + if (case_ignore::to_lower(a[i]) != case_ignore::to_lower(b[i])) { + return false; + } } return true; } @@ -5020,17 +5395,6 @@ namespace httplib size_t buf_epos_ = 0; }; - inline std::string to_lower(const char* beg, const char* end) - { - std::string out; - auto it = beg; - while (it != end) { - out += static_cast(::tolower(*it)); - it++; - } - return out; - } - inline std::string random_string(size_t length) { static const char data[] = @@ -5124,7 +5488,7 @@ namespace httplib inline bool range_error(Request& req, Response& res) { if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { - ssize_t contant_len = static_cast( + ssize_t content_len = static_cast( res.content_length_ ? res.content_length_ : res.body.size()); ssize_t prev_first_pos = -1; @@ -5144,19 +5508,30 @@ namespace httplib if (first_pos == -1 && last_pos == -1) { first_pos = 0; - last_pos = contant_len; + last_pos = content_len; } if (first_pos == -1) { - first_pos = contant_len - last_pos; - last_pos = contant_len - 1; + first_pos = content_len - last_pos; + last_pos = content_len - 1; } - if (last_pos == -1) { last_pos = contant_len - 1; } + // NOTE: RFC-9110 '14.1.2. Byte Ranges': + // A client can limit the number of bytes requested without knowing the + // size of the selected representation. If the last-pos value is absent, + // or if the value is greater than or equal to the current length of the + // representation data, the byte range is interpreted as the remainder of + // the representation (i.e., the server replaces the value of last-pos + // with a value that is one less than the current length of the selected + // representation). + // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-6 + if (last_pos == -1 || last_pos >= content_len) { + last_pos = content_len - 1; + } // Range must be within content length if (!(0 <= first_pos && first_pos <= last_pos && - last_pos <= contant_len - 1)) { + last_pos <= content_len - 1)) { return true; } @@ -5297,10 +5672,14 @@ namespace httplib inline bool expect_content(const Request& req) { if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || - req.method == "PRI" || req.method == "DELETE") { + req.method == "DELETE") { return true; } - // TODO: check if Content-Length is set + if (req.has_header("Content-Length") && + req.get_header_value_u64("Content-Length") > 0) { + return true; + } + if (is_chunked_transfer_encoding(req.headers)) { return true; } return false; } @@ -5350,9 +5729,81 @@ namespace httplib { return message_digest(s, EVP_sha512()); } -#endif -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + inline std::pair make_digest_authentication_header( + const Request& req, const std::map& auth, + size_t cnonce_count, const std::string& cnonce, const std::string& username, + const std::string& password, bool is_proxy = false) + { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } + else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } + else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } + else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); + } + + inline bool is_ssl_peer_could_be_closed(SSL* ssl, socket_t sock) + { + detail::set_nonblocking(sock, true); + auto se = detail::scope_exit([&]() { detail::set_nonblocking(sock, false); }); + + char buf[1]; + return !SSL_peek(ssl, buf, 1) && + SSL_get_error(ssl, 0) == SSL_ERROR_ZERO_RETURN; + } + #ifdef _WIN32 // NOTE: This code came up with the following stackoverflow post: // https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store @@ -5499,72 +5950,6 @@ namespace httplib static WSInit wsinit_; #endif -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - inline std::pair make_digest_authentication_header( - const Request& req, const std::map& auth, - size_t cnonce_count, const std::string& cnonce, const std::string& username, - const std::string& password, bool is_proxy = false) - { - std::string nc; - { - std::stringstream ss; - ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; - nc = ss.str(); - } - - std::string qop; - if (auth.find("qop") != auth.end()) { - qop = auth.at("qop"); - if (qop.find("auth-int") != std::string::npos) { - qop = "auth-int"; - } - else if (qop.find("auth") != std::string::npos) { - qop = "auth"; - } - else { - qop.clear(); - } - } - - std::string algo = "MD5"; - if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } - - std::string response; - { - auto H = algo == "SHA-256" ? detail::SHA_256 - : algo == "SHA-512" ? detail::SHA_512 - : detail::MD5; - - auto A1 = username + ":" + auth.at("realm") + ":" + password; - - auto A2 = req.method + ":" + req.path; - if (qop == "auth-int") { A2 += ":" + H(req.body); } - - if (qop.empty()) { - response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); - } - else { - response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + - ":" + qop + ":" + H(A2)); - } - } - - auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; - - auto field = "Digest username=\"" + username + "\", realm=\"" + - auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + - "\", uri=\"" + req.path + "\", algorithm=" + algo + - (qop.empty() ? ", response=\"" - : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + - cnonce + "\", response=\"") + - response + "\"" + - (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); - - auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; - return std::make_pair(key, field); - } -#endif - inline bool parse_www_authenticate(const Response& res, std::map& auth, bool is_proxy) @@ -5644,6 +6029,7 @@ namespace httplib #endif return; } + auto se = detail::scope_exit([&] { freeaddrinfo(result); }); for (auto rp = result; rp; rp = rp->ai_next) { const auto& addr = @@ -5655,8 +6041,6 @@ namespace httplib addrs.push_back(ip); } } - - freeaddrinfo(result); } inline std::string append_query_params(const std::string& path, @@ -5710,9 +6094,9 @@ namespace httplib } inline std::string Request::get_header_value(const std::string& key, - size_t id) const + const char* def, size_t id) const { - return detail::get_header_value(headers, key, id, ""); + return detail::get_header_value(headers, key, def, id); } inline size_t Request::get_header_value_count(const std::string& key) const @@ -5724,7 +6108,8 @@ namespace httplib inline void Request::set_header(const std::string& key, const std::string& val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { headers.emplace(key, val); } } @@ -5786,9 +6171,10 @@ namespace httplib } inline std::string Response::get_header_value(const std::string& key, + const char* def, size_t id) const { - return detail::get_header_value(headers, key, id, ""); + return detail::get_header_value(headers, key, def, id); } inline size_t Response::get_header_value_count(const std::string& key) const @@ -5800,14 +6186,15 @@ namespace httplib inline void Response::set_header(const std::string& key, const std::string& val) { - if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + if (detail::fields::is_field_name(key) && + detail::fields::is_field_value(val)) { headers.emplace(key, val); } } inline void Response::set_redirect(const std::string& url, int stat) { - if (!detail::has_crlf(url)) { + if (detail::fields::is_field_value(url)) { set_header("Location", url); if (300 <= stat && stat < 400) { this->status = stat; @@ -5877,6 +6264,18 @@ namespace httplib is_chunked_content_provider_ = true; } + inline void Response::set_file_content(const std::string& path, + const std::string& content_type) + { + file_content_path_ = path; + file_content_content_type_ = content_type; + } + + inline void Response::set_file_content(const std::string& path) + { + file_content_path_ = path; + } + // Result implementation inline bool Result::has_request_header(const std::string& key) const { @@ -5884,9 +6283,10 @@ namespace httplib } inline std::string Result::get_request_header_value(const std::string& key, + const char* def, size_t id) const { - return detail::get_header_value(request_headers_, key, id, ""); + return detail::get_header_value(request_headers_, key, def, id); } inline size_t @@ -5910,15 +6310,32 @@ namespace httplib namespace detail { + inline void calc_actual_timeout(time_t max_timeout_msec, time_t duration_msec, + time_t timeout_sec, time_t timeout_usec, + time_t& actual_timeout_sec, + time_t& actual_timeout_usec) + { + auto timeout_msec = (timeout_sec * 1000) + (timeout_usec / 1000); + + auto actual_timeout_msec = + (std::min)(max_timeout_msec - duration_msec, timeout_msec); + + actual_timeout_sec = actual_timeout_msec / 1000; + actual_timeout_usec = (actual_timeout_msec % 1000) * 1000; + } + // Socket stream implementation - inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, - time_t write_timeout_sec, - time_t write_timeout_usec) + inline SocketStream::SocketStream( + socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) : sock_(sock), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), - write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time(start_time), + read_buff_(read_buff_size_, 0) { } @@ -5926,7 +6343,16 @@ namespace httplib inline bool SocketStream::is_readable() const { - return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; } inline bool SocketStream::is_writable() const @@ -6012,6 +6438,13 @@ namespace httplib inline socket_t SocketStream::socket() const { return sock_; } + inline time_t SocketStream::duration() const + { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time) + .count(); + } + // Buffer stream implementation inline bool BufferStream::is_readable() const { return true; } @@ -6042,14 +6475,18 @@ namespace httplib inline void BufferStream::get_local_ip_and_port(std::string& /*ip*/, int& /*port*/) const { - } +} inline socket_t BufferStream::socket() const { return 0; } + inline time_t BufferStream::duration() const { return 0; } + inline const std::string& BufferStream::get_buffer() const { return buffer; } inline PathParamsMatcher::PathParamsMatcher(const std::string& pattern) { + static constexpr char marker[] = "/:"; + // One past the last ending position of a path param substring std::size_t last_param_end = 0; @@ -6062,13 +6499,14 @@ namespace httplib #endif while (true) { - const auto marker_pos = pattern.find(marker, last_param_end); + const auto marker_pos = pattern.find( + marker, last_param_end == 0 ? last_param_end : last_param_end - 1); if (marker_pos == std::string::npos) { break; } static_fragments_.push_back( - pattern.substr(last_param_end, marker_pos - last_param_end)); + pattern.substr(last_param_end, marker_pos - last_param_end + 1)); - const auto param_name_start = marker_pos + 1; + const auto param_name_start = marker_pos + 2; auto sep_pos = pattern.find(separator, param_name_start); if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } @@ -6131,7 +6569,7 @@ namespace httplib request.path_params.emplace( param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); - // Mark everythin up to '/' as matched + // Mark everything up to '/' as matched starting_pos = sep_pos + 1; } // Returns false if the path is longer than the pattern @@ -6246,7 +6684,8 @@ namespace httplib inline bool Server::set_mount_point(const std::string& mount_point, const std::string& dir, Headers headers) { - if (detail::is_dir(dir)) { + detail::FileStat stat(dir); + if (stat.is_dir()) { std::string mnt = !mount_point.empty() ? mount_point : "/"; if (!mnt.empty() && mnt[0] == '/') { base_dirs_.push_back({ mnt, dir, std::move(headers) }); @@ -6347,6 +6786,12 @@ namespace httplib return *this; } + inline Server& Server::set_ipv6_v6only(bool on) + { + ipv6_v6only_ = on; + return *this; + } + inline Server& Server::set_socket_options(SocketOptions socket_options) { socket_options_ = std::move(socket_options); @@ -6408,23 +6853,22 @@ namespace httplib inline bool Server::bind_to_port(const std::string& host, int port, int socket_flags) { - return bind_internal(host, port, socket_flags) >= 0; + auto ret = bind_internal(host, port, socket_flags); + if (ret == -1) { is_decommissioned = true; } + return ret >= 0; } inline int Server::bind_to_any_port(const std::string& host, int socket_flags) { - return bind_internal(host, 0, socket_flags); + auto ret = bind_internal(host, 0, socket_flags); + if (ret == -1) { is_decommissioned = true; } + return ret; } - inline bool Server::listen_after_bind() - { - auto se = detail::scope_exit([&]() { done_ = true; }); - return listen_internal(); - } + inline bool Server::listen_after_bind() { return listen_internal(); } inline bool Server::listen(const std::string& host, int port, int socket_flags) { - auto se = detail::scope_exit([&]() { done_ = true; }); return bind_to_port(host, port, socket_flags) && listen_internal(); } @@ -6432,7 +6876,7 @@ namespace httplib inline void Server::wait_until_ready() const { - while (!is_running() && !done_) { + while (!is_running_ && !is_decommissioned) { std::this_thread::sleep_for(std::chrono::milliseconds{ 1 }); } } @@ -6445,8 +6889,11 @@ namespace httplib detail::shutdown_socket(sock); detail::close_socket(sock); } + is_decommissioned = false; } + inline void Server::decommission() { is_decommissioned = true; } + inline bool Server::parse_request_line(const char* s, Request& req) const { auto len = strlen(s); @@ -6535,23 +6982,24 @@ namespace httplib res.set_header("Connection", "close"); } else { - std::stringstream ss; - ss << "timeout=" << keep_alive_timeout_sec_ - << ", max=" << keep_alive_max_count_; - res.set_header("Keep-Alive", ss.str()); + std::string s = "timeout="; + s += std::to_string(keep_alive_timeout_sec_); + s += ", max="; + s += std::to_string(keep_alive_max_count_); + res.set_header("Keep-Alive", s); } - if (!res.has_header("Content-Type") && - (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + if ((!res.body.empty() || res.content_length_ > 0 || res.content_provider_) && + !res.has_header("Content-Type")) { res.set_header("Content-Type", "text/plain"); } - if (!res.has_header("Content-Length") && res.body.empty() && - !res.content_length_ && !res.content_provider_) { + if (res.body.empty() && !res.content_length_ && !res.content_provider_ && + !res.has_header("Content-Length")) { res.set_header("Content-Length", "0"); } - if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + if (req.method == "HEAD" && !res.has_header("Accept-Ranges")) { res.set_header("Accept-Ranges", "bytes"); } @@ -6560,12 +7008,7 @@ namespace httplib // Response line and headers { detail::BufferStream bstrm; - - if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, - status_message(res.status))) { - return false; - } - + if (!detail::write_response_line(bstrm, res.status)) { return false; } if (!header_writer_(bstrm, res.headers)) { return false; } // Flush buffer @@ -6773,7 +7216,14 @@ namespace httplib auto path = entry.base_dir + sub_path; if (path.back() == '/') { path += "index.html"; } - if (detail::is_file(path)) { + detail::FileStat stat(path); + + if (stat.is_dir()) { + res.set_redirect(sub_path + "/", StatusCode::MovedPermanently_301); + return true; + } + + if (stat.is_file()) { for (const auto& kv : entry.headers) { res.set_header(kv.first, kv.second); } @@ -6809,8 +7259,8 @@ namespace httplib { return detail::create_socket( host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, - std::move(socket_options), - [](socket_t sock, struct addrinfo& ai) -> bool { + ipv6_v6only_, std::move(socket_options), + [](socket_t sock, struct addrinfo& ai, bool& /*quit*/) -> bool { if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { return false; } @@ -6822,6 +7272,8 @@ namespace httplib inline int Server::bind_internal(const std::string& host, int port, int socket_flags) { + if (is_decommissioned) { return -1; } + if (!is_valid()) { return -1; } svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); @@ -6851,6 +7303,8 @@ namespace httplib inline bool Server::listen_internal() { + if (is_decommissioned) { return false; } + auto ret = true; is_running_ = true; auto se = detail::scope_exit([&]() { is_running_ = false; }); @@ -6871,13 +7325,22 @@ namespace httplib #ifndef _WIN32 } #endif + +#if defined _WIN32 + // sockets connected via WASAccept inherit flags NO_HANDLE_INHERIT, + // OVERLAPPED + socket_t sock = WSAAccept(svr_sock_, nullptr, nullptr, nullptr, 0); +#elif defined SOCK_CLOEXEC + socket_t sock = accept4(svr_sock_, nullptr, nullptr, SOCK_CLOEXEC); +#else socket_t sock = accept(svr_sock_, nullptr, nullptr); +#endif if (sock == INVALID_SOCKET) { if (errno == EMFILE) { // The per-process limit of open file descriptors has been reached. // Try to accept new connections after a short sleep. - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for(std::chrono::microseconds{ 1 }); continue; } else if (errno == EINTR || errno == EAGAIN) { @@ -6893,35 +7356,10 @@ namespace httplib break; } - { -#ifdef _WIN32 - auto timeout = static_cast(read_timeout_sec_ * 1000 + - read_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(read_timeout_sec_); - tv.tv_usec = static_cast(read_timeout_usec_); - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } - { - -#ifdef _WIN32 - auto timeout = static_cast(write_timeout_sec_ * 1000 + - write_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&timeout), sizeof(timeout)); -#else - timeval tv; - tv.tv_sec = static_cast(write_timeout_sec_); - tv.tv_usec = static_cast(write_timeout_usec_); - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, - reinterpret_cast(&tv), sizeof(tv)); -#endif - } + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_RCVTIMEO, + read_timeout_sec_, read_timeout_usec_); + detail::set_socket_opt_time(sock, SOL_SOCKET, SO_SNDTIMEO, + write_timeout_sec_, write_timeout_usec_); if (!task_queue->enqueue( [this, sock]() { process_and_close_socket(sock); })) { @@ -6933,6 +7371,7 @@ namespace httplib task_queue->shutdown(); } + is_decommissioned = !ret; return ret; } @@ -7168,7 +7607,9 @@ namespace httplib } inline bool - Server::process_request(Stream& strm, bool close_connection, + Server::process_request(Stream& strm, const std::string& remote_addr, + int remote_port, const std::string& local_addr, + int local_port, bool close_connection, bool& connection_closed, const std::function& setup_request) { @@ -7199,14 +7640,6 @@ namespace httplib #endif #endif - // Check if the request URI doesn't exceed the limit - if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { - Headers dummy; - detail::read_headers(strm, dummy); - res.status = StatusCode::UriTooLong_414; - return write_response(strm, close_connection, req, res); - } - // Request line and headers if (!parse_request_line(line_reader.ptr(), req) || !detail::read_headers(strm, req.headers)) { @@ -7214,6 +7647,14 @@ namespace httplib return write_response(strm, close_connection, req, res); } + // Check if the request URI doesn't exceed the limit + if (req.target.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::UriTooLong_414; + return write_response(strm, close_connection, req, res); + } + if (req.get_header_value("Connection") == "close") { connection_closed = true; } @@ -7223,11 +7664,13 @@ namespace httplib connection_closed = true; } - strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.remote_addr = remote_addr; + req.remote_port = remote_port; req.set_header("REMOTE_ADDR", req.remote_addr); req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); - strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.local_addr = local_addr; + req.local_port = local_port; req.set_header("LOCAL_ADDR", req.local_addr); req.set_header("LOCAL_PORT", std::to_string(req.local_port)); @@ -7249,13 +7692,20 @@ namespace httplib switch (status) { case StatusCode::Continue_100: case StatusCode::ExpectationFailed_417: - strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, - status_message(status)); + detail::write_response_line(strm, status); + strm.write("\r\n"); break; - default: return write_response(strm, close_connection, req, res); + default: + connection_closed = true; + return write_response(strm, true, req, res); } } + // Setup `is_connection_closed` method + req.is_connection_closed = [&]() { + return !detail::is_socket_alive(strm.socket()); + }; + // Routing auto routed = false; #ifdef CPPHTTPLIB_NO_EXCEPTIONS @@ -7302,6 +7752,32 @@ namespace httplib : StatusCode::PartialContent_206; } + // Serve file content by using a content provider + if (!res.file_content_path_.empty()) { + const auto& path = res.file_content_path_; + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::NotFound_404; + return write_response(strm, close_connection, req, res); + } + + auto content_type = res.file_content_content_type_; + if (content_type.empty()) { + content_type = detail::find_content_type( + path, file_extension_and_mimetype_map_, default_file_mimetype_); + } + + res.set_content_provider( + mm->size(), content_type, + [mm](size_t offset, size_t length, DataSink& sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + } + if (detail::range_error(req, res)) { res.body.clear(); res.content_length_ = 0; @@ -7323,12 +7799,21 @@ namespace httplib inline bool Server::process_and_close_socket(socket_t sock) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + auto ret = detail::process_server_socket( svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, - [this](Stream& strm, bool close_connection, bool& connection_closed) { - return process_request(strm, close_connection, connection_closed, + [&](Stream& strm, bool close_connection, bool& connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, connection_closed, nullptr); }); @@ -7341,7 +7826,7 @@ namespace httplib inline ClientImpl::ClientImpl(const std::string& host) : ClientImpl(host, 80, std::string(), std::string()) { - } +} inline ClientImpl::ClientImpl(const std::string& host, int port) : ClientImpl(host, port, std::string(), std::string()) @@ -7351,8 +7836,8 @@ namespace httplib inline ClientImpl::ClientImpl(const std::string& host, int port, const std::string& client_cert_path, const std::string& client_key_path) - : host_(host), port_(port), - host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port), + host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)), client_cert_path_(client_cert_path), client_key_path_(client_key_path) { } @@ -7375,6 +7860,7 @@ namespace httplib read_timeout_usec_ = rhs.read_timeout_usec_; write_timeout_sec_ = rhs.write_timeout_sec_; write_timeout_usec_ = rhs.write_timeout_usec_; + max_timeout_msec_ = rhs.max_timeout_msec_; basic_auth_username_ = rhs.basic_auth_username_; basic_auth_password_ = rhs.basic_auth_password_; bearer_token_auth_token_ = rhs.bearer_token_auth_token_; @@ -7387,6 +7873,7 @@ namespace httplib url_encode_ = rhs.url_encode_; address_family_ = rhs.address_family_; tcp_nodelay_ = rhs.tcp_nodelay_; + ipv6_v6only_ = rhs.ipv6_v6only_; socket_options_ = rhs.socket_options_; compress_ = rhs.compress_; decompress_ = rhs.decompress_; @@ -7407,6 +7894,8 @@ namespace httplib #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT server_certificate_verification_ = rhs.server_certificate_verification_; + server_hostname_verification_ = rhs.server_hostname_verification_; + server_certificate_verifier_ = rhs.server_certificate_verifier_; #endif logger_ = rhs.logger_; } @@ -7416,9 +7905,9 @@ namespace httplib if (!proxy_host_.empty() && proxy_port_ != -1) { return detail::create_client_socket( proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, - socket_options_, connection_timeout_sec_, connection_timeout_usec_, - read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, interface_, error); + ipv6_v6only_, socket_options_, connection_timeout_sec_, + connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, interface_, error); } // Check is custom IP specified for host_ @@ -7427,10 +7916,10 @@ namespace httplib if (it != addr_map_.end()) { ip = it->second; } return detail::create_client_socket( - host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, - connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, - read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, - error); + host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); } inline bool ClientImpl::create_and_connect_socket(Socket& socket, @@ -7537,8 +8026,17 @@ namespace httplib auto is_alive = false; if (socket_.is_open()) { is_alive = detail::is_socket_alive(socket_.sock); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_alive && is_ssl()) { + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { + is_alive = false; + } + } +#endif + if (!is_alive) { - // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // Attempt to avoid sigpipe by shutting down non-gracefully if it seems // like the other side has already closed the connection Also, there // cannot be any requests in flight from other threads since we locked // request_mutex_, so safe to close everything immediately @@ -7558,7 +8056,8 @@ namespace httplib auto& scli = static_cast(*this); if (!proxy_host_.empty() && proxy_port_ != -1) { auto success = false; - if (!scli.connect_with_proxy(socket_, res, success, error)) { + if (!scli.connect_with_proxy(socket_, req.start_time_, res, success, + error)) { return success; } } @@ -7604,7 +8103,7 @@ namespace httplib } }); - ret = process_socket(socket_, [&](Stream& strm) { + ret = process_socket(socket_, req.start_time_, [&](Stream& strm) { return handle_request(strm, req, res, close_connection, error); }); @@ -7828,12 +8327,26 @@ namespace httplib if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } -#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT - if (!req.has_header("User-Agent")) { - auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; - req.set_header("User-Agent", agent); - } + if (!req.content_receiver) { + if (!req.has_header("Accept-Encoding")) { + std::string accept_encoding; +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + accept_encoding = "br"; #endif +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (!accept_encoding.empty()) { accept_encoding += ", "; } + accept_encoding += "gzip, deflate"; +#endif + req.set_header("Accept-Encoding", accept_encoding); + } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif + }; if (req.body.empty()) { if (req.content_provider_) { @@ -7895,8 +8408,14 @@ namespace httplib { detail::BufferStream bstrm; - const auto& path = url_encode_ ? detail::encode_url(req.path) : req.path; - bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + const auto& path_with_query = + req.params.empty() ? req.path + : append_query_params(req.path, req.params); + + const auto& path = + url_encode_ ? detail::encode_url(path_with_query) : path_with_query; + + detail::write_request_line(bstrm, req.method, path); header_writer_(bstrm, req.headers); @@ -8017,6 +8536,9 @@ namespace httplib req.headers = headers; req.path = path; req.progress = progress; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } auto error = Error::Success; @@ -8045,9 +8567,7 @@ namespace httplib if (is_ssl()) { auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; if (!is_proxy_enabled) { - char buf[1]; - if (SSL_peek(socket_.ssl, buf, 1) == 0 && - SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + if (detail::is_ssl_peer_could_be_closed(socket_.ssl, socket_.sock)) { error = Error::SSLPeerCouldBeClosed_; return false; } @@ -8065,7 +8585,9 @@ namespace httplib // Body if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" && req.method != "CONNECT") { - auto redirect = 300 < res.status && res.status < 400 && follow_location_; + auto redirect = 300 < res.status && res.status < 400 && + res.status != StatusCode::NotModified_304 && + follow_location_; if (req.response_handler && !redirect) { if (!req.response_handler(res)) { @@ -8086,9 +8608,7 @@ namespace httplib : static_cast( [&](const char* buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { - if (res.body.size() + n > res.body.max_size()) { - return false; - } + assert(res.body.size() + n <= res.body.max_size()); res.body.append(buf, n); return true; }); @@ -8100,12 +8620,25 @@ namespace httplib return ret; }; - int dummy_status; - if (!detail::read_content(strm, res, (std::numeric_limits::max)(), - dummy_status, std::move(progress), std::move(out), - decompress_)) { - if (error != Error::Canceled) { error = Error::Read; } - return false; + if (res.has_header("Content-Length")) { + if (!req.content_receiver) { + auto len = res.get_header_value_u64("Content-Length"); + if (len > res.body.max_size()) { + error = Error::Read; + return false; + } + res.body.reserve(static_cast(len)); + } + } + + if (res.status != StatusCode::NotModified_304) { + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), + std::move(out), decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } } } @@ -8162,13 +8695,14 @@ namespace httplib }; } - inline bool - ClientImpl::process_socket(const Socket& socket, - std::function callback) + inline bool ClientImpl::process_socket( + const Socket& socket, + std::chrono::time_point start_time, + std::function callback) { return detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, std::move(callback)); + write_timeout_usec_, max_timeout_msec_, start_time, std::move(callback)); } inline bool ClientImpl::is_ssl() const { return false; } @@ -8196,6 +8730,9 @@ namespace httplib req.path = path; req.headers = headers; req.progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } @@ -8269,6 +8806,9 @@ namespace httplib return content_receiver(data, data_length); }; req.progress = std::move(progress); + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } @@ -8319,6 +8859,9 @@ namespace httplib req.method = "HEAD"; req.headers = headers; req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } @@ -8692,7 +9235,8 @@ namespace httplib inline Result ClientImpl::Patch(const std::string& path, const std::string& body, - const std::string& content_type, Progress progress) + const std::string& content_type, + Progress progress) { return Patch(path, Headers(), body, content_type, progress); } @@ -8793,6 +9337,9 @@ namespace httplib req.headers = headers; req.path = path; req.progress = progress; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } if (!content_type.empty()) { req.set_header("Content-Type", content_type); } req.body.assign(body, content_length); @@ -8846,6 +9393,9 @@ namespace httplib req.method = "OPTIONS"; req.headers = headers; req.path = path; + if (max_timeout_msec_ > 0) { + req.start_time_ = std::chrono::steady_clock::now(); + } return send_(std::move(req)); } @@ -8904,6 +9454,11 @@ namespace httplib write_timeout_usec_ = usec; } + inline void ClientImpl::set_max_timeout(time_t msec) + { + max_timeout_msec_ = msec; + } + inline void ClientImpl::set_basic_auth(const std::string& username, const std::string& password) { @@ -8955,6 +9510,8 @@ namespace httplib inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + inline void ClientImpl::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; } + inline void ClientImpl::set_socket_options(SocketOptions socket_options) { socket_options_ = std::move(socket_options); @@ -9013,13 +9570,11 @@ namespace httplib std::size_t size) const { auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + auto se = detail::scope_exit([&] { BIO_free_all(mem); }); if (!mem) { return nullptr; } auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); - if (!inf) { - BIO_free_all(mem); - return nullptr; - } + if (!inf) { return nullptr; } auto cts = X509_STORE_new(); if (cts) { @@ -9033,7 +9588,6 @@ namespace httplib } sk_X509_INFO_pop_free(inf, X509_INFO_free); - BIO_free_all(mem); return cts; } @@ -9041,6 +9595,17 @@ namespace httplib { server_certificate_verification_ = enabled; } + + inline void ClientImpl::enable_server_hostname_verification(bool enabled) + { + server_hostname_verification_ = enabled; + } + + inline void ClientImpl::set_server_certificate_verifier( + std::function verifier) + { + server_certificate_verifier_ = verifier; + } #endif inline void ClientImpl::set_logger(Logger logger) @@ -9087,14 +9652,22 @@ namespace httplib return ssl; } - inline void ssl_delete(std::mutex& ctx_mutex, SSL* ssl, + inline void ssl_delete(std::mutex& ctx_mutex, SSL* ssl, socket_t sock, bool shutdown_gracefully) { // sometimes we may want to skip this to try to avoid SIGPIPE if we know // the remote has closed the network connection // Note that it is not always possible to avoid SIGPIPE, this is merely a // best-efforts. - if (shutdown_gracefully) { SSL_shutdown(ssl); } + if (shutdown_gracefully) { + (void)(sock); + // SSL_shutdown() returns 0 on first call (indicating close_notify alert + // sent) and 1 on subsequent call (indicating close_notify alert received) + if (SSL_shutdown(ssl) == 0) { + // Expected to return 1, but even if it doesn't, we free ssl + SSL_shutdown(ssl); + } + } std::lock_guard guard(ctx_mutex); SSL_free(ssl); @@ -9140,13 +9713,15 @@ namespace httplib } template - inline bool - process_client_socket_ssl(SSL* ssl, socket_t sock, time_t read_timeout_sec, - time_t read_timeout_usec, time_t write_timeout_sec, - time_t write_timeout_usec, T callback) + inline bool process_client_socket_ssl( + SSL* ssl, socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time, T callback) { SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, - write_timeout_sec, write_timeout_usec); + write_timeout_sec, write_timeout_usec, max_timeout_msec, + start_time); return callback(strm); } @@ -9160,15 +9735,16 @@ namespace httplib }; // SSL socket stream implementation - inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL* ssl, - time_t read_timeout_sec, - time_t read_timeout_usec, - time_t write_timeout_sec, - time_t write_timeout_usec) + inline SSLSocketStream::SSLSocketStream( + socket_t sock, SSL* ssl, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec, + time_t max_timeout_msec, + std::chrono::time_point start_time) : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), read_timeout_usec_(read_timeout_usec), write_timeout_sec_(write_timeout_sec), - write_timeout_usec_(write_timeout_usec) + write_timeout_usec_(write_timeout_usec), + max_timeout_msec_(max_timeout_msec), start_time(start_time) { SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); } @@ -9177,13 +9753,22 @@ namespace httplib inline bool SSLSocketStream::is_readable() const { - return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + if (max_timeout_msec_ <= 0) { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; + } + + time_t read_timeout_sec; + time_t read_timeout_usec; + calc_actual_timeout(max_timeout_msec_, duration(), read_timeout_sec_, + read_timeout_usec_, read_timeout_sec, read_timeout_usec); + + return select_read(sock_, read_timeout_sec, read_timeout_usec) > 0; } inline bool SSLSocketStream::is_writable() const { return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && - is_socket_alive(sock_); + is_socket_alive(sock_) && !is_ssl_peer_could_be_closed(ssl_, sock_); } inline ssize_t SSLSocketStream::read(char* ptr, size_t size) @@ -9207,7 +9792,7 @@ namespace httplib return SSL_read(ssl_, ptr, static_cast(size)); } else if (is_readable()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for(std::chrono::microseconds{ 10 }); ret = SSL_read(ssl_, ptr, static_cast(size)); if (ret >= 0) { return ret; } err = SSL_get_error(ssl_, ret); @@ -9219,7 +9804,9 @@ namespace httplib } return ret; } - return -1; + else { + return -1; + } } inline ssize_t SSLSocketStream::write(const char* ptr, size_t size) @@ -9240,7 +9827,7 @@ namespace httplib while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { #endif if (is_writable()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for(std::chrono::microseconds{ 10 }); ret = SSL_write(ssl_, ptr, static_cast(handle_size)); if (ret >= 0) { return ret; } err = SSL_get_error(ssl_, ret); @@ -9269,6 +9856,13 @@ namespace httplib inline socket_t SSLSocketStream::socket() const { return sock_; } + inline time_t SSLSocketStream::duration() const + { + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start_time) + .count(); + } + static SSLInit sslinit_; } // namespace detail @@ -9296,7 +9890,8 @@ namespace httplib if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != - 1) { + 1 || + SSL_CTX_check_private_key(ctx_) != 1) { SSL_CTX_free(ctx_); ctx_ = nullptr; } @@ -9383,20 +9978,29 @@ namespace httplib auto ret = false; if (ssl) { + std::string remote_addr; + int remote_port = 0; + detail::get_remote_ip_and_port(sock, remote_addr, remote_port); + + std::string local_addr; + int local_port = 0; + detail::get_local_ip_and_port(sock, local_addr, local_port); + ret = detail::process_server_socket_ssl( svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, - [this, ssl](Stream& strm, bool close_connection, - bool& connection_closed) { - return process_request(strm, close_connection, connection_closed, - [&](Request& req) { req.ssl = ssl; }); + [&](Stream& strm, bool close_connection, bool& connection_closed) { + return process_request(strm, remote_addr, remote_port, local_addr, + local_port, close_connection, + connection_closed, + [&](Request& req) { req.ssl = ssl; }); }); // Shutdown gracefully if the result seemed successful, non-gracefully if // the connection appeared to be closed. const bool shutdown_gracefully = ret; - detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + detail::ssl_delete(ctx_mutex_, ssl, sock, shutdown_gracefully); } detail::shutdown_socket(sock); @@ -9408,7 +10012,7 @@ namespace httplib inline SSLClient::SSLClient(const std::string & host) : SSLClient(host, 443, std::string(), std::string()) { - } +} inline SSLClient::SSLClient(const std::string & host, int port) : SSLClient(host, port, std::string(), std::string()) @@ -9423,6 +10027,8 @@ namespace httplib { ctx_ = SSL_CTX_new(TLS_client_method()); + SSL_CTX_set_min_proto_version(ctx_, TLS1_2_VERSION); + detail::split(&host_[0], &host_[host_.size()], '.', [&](const char* b, const char* e) { host_components_.emplace_back(b, e); @@ -9517,17 +10123,23 @@ namespace httplib } // Assumes that socket_mutex_ is locked and that there are no requests in flight - inline bool SSLClient::connect_with_proxy(Socket & socket, Response & res, - bool& success, Error & error) + inline bool SSLClient::connect_with_proxy( + Socket & socket, + std::chrono::time_point start_time, + Response & res, bool& success, Error & error) { success = true; Response proxy_res; if (!detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, [&](Stream& strm) { + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream& strm) { Request req2; req2.method = "CONNECT"; req2.path = host_and_port_; + if (max_timeout_msec_ > 0) { + req2.start_time_ = std::chrono::steady_clock::now(); + } return process_request(strm, req2, proxy_res, false, error); })) { // Thread-safe to close everything because we are assuming there are no @@ -9547,7 +10159,8 @@ namespace httplib proxy_res = Response(); if (!detail::process_client_socket( socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, [&](Stream& strm) { + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, + start_time, [&](Stream& strm) { Request req3; req3.method = "CONNECT"; req3.path = host_and_port_; @@ -9555,6 +10168,9 @@ namespace httplib req3, auth, 1, detail::random_string(10), proxy_digest_auth_username_, proxy_digest_auth_password_, true)); + if (max_timeout_msec_ > 0) { + req3.start_time_ = std::chrono::steady_clock::now(); + } return process_request(strm, req3, proxy_res, false, error); })) { // Thread-safe to close everything because we are assuming there are @@ -9642,36 +10258,53 @@ namespace httplib } if (server_certificate_verification_) { - verify_result_ = SSL_get_verify_result(ssl2); + auto verification_status = SSLVerifierResponse::NoDecisionMade; - if (verify_result_ != X509_V_OK) { + if (server_certificate_verifier_) { + verification_status = server_certificate_verifier_(ssl2); + } + + if (verification_status == SSLVerifierResponse::CertificateRejected) { error = Error::SSLServerVerification; return false; } - auto server_cert = SSL_get1_peer_certificate(ssl2); + if (verification_status == SSLVerifierResponse::NoDecisionMade) { + verify_result_ = SSL_get_verify_result(ssl2); - if (server_cert == nullptr) { - error = Error::SSLServerVerification; - return false; - } + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } - if (!verify_host(server_cert)) { - X509_free(server_cert); - error = Error::SSLServerVerification; - return false; + auto server_cert = SSL_get1_peer_certificate(ssl2); + auto se = detail::scope_exit([&] { X509_free(server_cert); }); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (server_hostname_verification_) { + if (!verify_host(server_cert)) { + error = Error::SSLServerHostnameVerification; + return false; + } + } } - X509_free(server_cert); } return true; }, [&](SSL* ssl2) { +#if defined(OPENSSL_IS_BORINGSSL) + SSL_set_tlsext_host_name(ssl2, host_.c_str()); +#else // NOTE: Direct call instead of using the OpenSSL macro to suppress // -Wold-style-cast warning - // SSL_set_tlsext_host_name(ssl2, host_.c_str()); SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, static_cast(const_cast(host_.c_str()))); +#endif return true; }); @@ -9698,20 +10331,23 @@ namespace httplib return; } if (socket.ssl) { - detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + detail::ssl_delete(ctx_mutex_, socket.ssl, socket.sock, + shutdown_gracefully); socket.ssl = nullptr; } assert(socket.ssl == nullptr); } - inline bool - SSLClient::process_socket(const Socket & socket, - std::function callback) + inline bool SSLClient::process_socket( + const Socket & socket, + std::chrono::time_point start_time, + std::function callback) { assert(socket.ssl); return detail::process_client_socket_ssl( socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, - write_timeout_sec_, write_timeout_usec_, std::move(callback)); + write_timeout_sec_, write_timeout_usec_, max_timeout_msec_, start_time, + std::move(callback)); } inline bool SSLClient::is_ssl() const { return true; } @@ -9750,8 +10386,8 @@ namespace httplib auto type = GEN_DNS; - struct in6_addr addr6 {}; - struct in_addr addr {}; + struct in6_addr addr6 = {}; + struct in_addr addr = {}; size_t addr_len = 0; #ifndef __MINGW32__ @@ -9897,14 +10533,14 @@ namespace httplib cli_ = detail::make_unique(host, port, client_cert_path, client_key_path); } - } + } else { // NOTE: Update TEST(UniversalClientImplTest, Ipv6LiteralAddress) // if port param below changes. cli_ = detail::make_unique(scheme_host_port, 80, client_cert_path, client_key_path); } - } + } // namespace detail inline Client::Client(const std::string & host, int port) : cli_(detail::make_unique(host, port)) @@ -10243,7 +10879,8 @@ namespace httplib } inline Result Client::Patch(const std::string & path, const char* body, size_t content_length, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { return cli_->Patch(path, body, content_length, content_type, progress); } @@ -10255,9 +10892,11 @@ namespace httplib } inline Result Client::Patch(const std::string & path, const Headers & headers, const char* body, size_t content_length, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { - return cli_->Patch(path, headers, body, content_length, content_type, progress); + return cli_->Patch(path, headers, body, content_length, content_type, + progress); } inline Result Client::Patch(const std::string & path, const std::string & body, const std::string & content_type) @@ -10265,7 +10904,8 @@ namespace httplib return cli_->Patch(path, body, content_type); } inline Result Client::Patch(const std::string & path, const std::string & body, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { return cli_->Patch(path, body, content_type, progress); } @@ -10277,7 +10917,8 @@ namespace httplib } inline Result Client::Patch(const std::string & path, const Headers & headers, const std::string & body, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { return cli_->Patch(path, headers, body, content_type, progress); } @@ -10324,7 +10965,8 @@ namespace httplib } inline Result Client::Delete(const std::string & path, const char* body, size_t content_length, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { return cli_->Delete(path, body, content_length, content_type, progress); } @@ -10336,9 +10978,11 @@ namespace httplib } inline Result Client::Delete(const std::string & path, const Headers & headers, const char* body, size_t content_length, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { - return cli_->Delete(path, headers, body, content_length, content_type, progress); + return cli_->Delete(path, headers, body, content_length, content_type, + progress); } inline Result Client::Delete(const std::string & path, const std::string & body, const std::string & content_type) @@ -10346,7 +10990,8 @@ namespace httplib return cli_->Delete(path, body, content_type); } inline Result Client::Delete(const std::string & path, const std::string & body, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { return cli_->Delete(path, body, content_type, progress); } @@ -10358,7 +11003,8 @@ namespace httplib } inline Result Client::Delete(const std::string & path, const Headers & headers, const std::string & body, - const std::string & content_type, Progress progress) + const std::string & content_type, + Progress progress) { return cli_->Delete(path, headers, body, content_type, progress); } @@ -10492,6 +11138,17 @@ namespace httplib { cli_->enable_server_certificate_verification(enabled); } + + inline void Client::enable_server_hostname_verification(bool enabled) + { + cli_->enable_server_hostname_verification(enabled); + } + + inline void Client::set_server_certificate_verifier( + std::function verifier) + { + cli_->set_server_certificate_verifier(verifier); + } #endif inline void Client::set_logger(Logger logger) @@ -10538,7 +11195,7 @@ namespace httplib // ---------------------------------------------------------------------------- -} // namespace httplib + } // namespace httplib #if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) #undef poll diff --git a/src/openrouter.cpp b/src/openrouter.cpp index c47ec2c..04aef39 100644 --- a/src/openrouter.cpp +++ b/src/openrouter.cpp @@ -1,4 +1,5 @@ #include +#include int main() { @@ -6,6 +7,6 @@ int main() #ifdef TEST return 0; #else - return router.serve("0.0.0.0", 43244); + return router.serve(config::config.getConfig()["server"]["ip"], config::config.getConfig()["server"]["port"]); #endif } diff --git a/src/routes.hpp b/src/routes.hpp deleted file mode 100644 index ef16532..0000000 --- a/src/routes.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef ROUTES_HPP -#define ROUTES_HPP - -#define AUTH_SERVICE "http://localhost:43243" - -#endif // ROUTES_HPP diff --git a/src/settings/backup.cpp b/src/settings/backup.cpp new file mode 100644 index 0000000..f1088b5 --- /dev/null +++ b/src/settings/backup.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include + +namespace api +{ + namespace settings + { + void backup(const httplib::Request& request, httplib::Response& response) + { + std::ifstream backup_file(config::config.getConfigFileName()); + std::stringstream backup_file_buffer; + backup_file_buffer << backup_file.rdbuf(); + backup_file.close(); + response.set_content(backup_file_buffer.str(), JSON_TYPE); + } + + void restore(const httplib::Request& request, httplib::Response& response) + { + std::size_t size = request.files.size(); + std::cout << "file size::" << size << std::endl; + bool ret = request.has_file("restore_settings"); + std::cout << "file ret::" << ret << std::endl; + const httplib::MultipartFormData& file = request.get_file_value("restore_settings"); + if (file.content_type == JSON_TYPE) { + nlohmann::json new_config = nlohmann::json::parse(file.content); + config::config.setConfig(new_config); + config::config.saveConfig(); + set_all_settings(config::config.getConfig()); + response.set_content(R"({"status":true})", JSON_TYPE); + system("rc restart auth_service"); + system("rc restart openrouter"); + return; + } + response.set_content(R"({"status":false})", JSON_TYPE); + } + + void restore_defaults(const httplib::Request& request, httplib::Response& response) + { + std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; + response.set_header("Access-Control-Allow-Origin", "*"); + if (!check_body(request)) + { + response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); + return; + } + if (std::filesystem::exists(config::config.getConfigFileName())) { + std::filesystem::remove(config::config.getConfigFileName()); + std::filesystem::copy("../config_default.json", config::config.getConfigFileName()); + usleep(300); + config::config.setConfig(config::config.getConfigFileName()); + set_all_settings(config::config.getConfig()); + response.set_content(R"({"status":true})", JSON_TYPE); + system("rc restart auth_service"); + system("rc restart openrouter"); + return; + } + response.set_content(R"({"status":false})", JSON_TYPE); + } + } +} diff --git a/src/settings/dns/dns.hpp b/src/settings/dns/dns.hpp index 4e2af17..91227d0 100644 --- a/src/settings/dns/dns.hpp +++ b/src/settings/dns/dns.hpp @@ -1,7 +1,7 @@ #ifndef DNS_HPP #define DNS_HPP -#include +#include namespace api { @@ -9,14 +9,9 @@ namespace api { namespace dns { - void get_dns(const httplib::Request& request, httplib::Response& response); - void set_dns(const httplib::Request& request, httplib::Response& response); - - void get_dns_servers(const httplib::Request& request, httplib::Response& response); - void set_dns_servers(const httplib::Request& request, httplib::Response& response); - - void get_local_domains(const httplib::Request& request, httplib::Response& response); - void set_local_domains(const httplib::Request& request, httplib::Response& response); + void set_dns(bool new_dns_enabled); + void set_dns_servers(nlohmann::json servers); + void set_local_domains(nlohmann::json domains); } } } diff --git a/src/settings/dns/enabled.cpp b/src/settings/dns/enabled.cpp index db90db6..2c19ffe 100644 --- a/src/settings/dns/enabled.cpp +++ b/src/settings/dns/enabled.cpp @@ -1,13 +1,5 @@ #include -#include -#include -#include #include -#include -#include -#include - -#define DNS_REQUIRED_STRING R"({"required":"[token,enabled]"})" namespace api { @@ -15,52 +7,8 @@ namespace api { namespace dns { - void get_dns(const httplib::Request& request, httplib::Response& response) + void set_dns(bool new_dns_enabled) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int dns_enabled = config::config.getConfig()["settings"]["dns"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"enabled":")" << dns_enabled << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_dns(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["enabled"] == nullptr) - { - response.set_content(DNS_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - bool new_dns_enabled = json_body["enabled"]; if (!new_dns_enabled) { system("rc stop dnsmasq"); @@ -73,15 +21,6 @@ namespace api system("rc add dnsmasq"); syslog(LOG_INFO, "%s", "DNS is enabled"); } - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["dns"]["enabled"] = new_dns_enabled; - config::config.setConfig(new_config); - config::config.saveConfig(); - int dns_enabled = config::config.getConfig()["settings"]["dns"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"enabled":")" << dns_enabled << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/dns/local_domains.cpp b/src/settings/dns/local_domains.cpp index 08af734..7e889b9 100644 --- a/src/settings/dns/local_domains.cpp +++ b/src/settings/dns/local_domains.cpp @@ -1,11 +1,6 @@ #include -#include -#include -#include #include -#include -#include -#include +#include namespace api { @@ -13,48 +8,9 @@ namespace api { namespace dns { - void get_local_domains(const httplib::Request& request, httplib::Response& response) + void set_local_domains(nlohmann::json domains) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - std::stringstream responsedata; - responsedata << R"({"domains":)"; - responsedata << config::config.getConfig()["settings"]["dns"]["localdomains"]; - responsedata << "}"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_local_domains(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } std::ofstream domainsfile("/etc/dnsmasq.conf.d/hosts"); - nlohmann::json domains = json_body["domains"]; for (nlohmann::json_abi_v3_11_3::json domain : domains) { std::string ip = domain["ip"]; @@ -62,15 +18,8 @@ namespace api domainsfile << ip << " " << dom << "\n"; } domainsfile.close(); - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["dns"]["localdomains"] = json_body["domains"]; - config::config.setConfig(new_config); - config::config.saveConfig(); system("rc restart dnsmasq"); syslog(LOG_INFO, "%s", "DNS local domains changed"); - std::string responsedata = R"({"success":"true"})"; - response.set_content(responsedata, JSON_TYPE); } } } diff --git a/src/settings/dns/servers.cpp b/src/settings/dns/servers.cpp index 35e50f9..a3347f5 100644 --- a/src/settings/dns/servers.cpp +++ b/src/settings/dns/servers.cpp @@ -1,11 +1,6 @@ #include -#include -#include -#include #include -#include -#include -#include +#include namespace api { @@ -13,48 +8,9 @@ namespace api { namespace dns { - void get_dns_servers(const httplib::Request& request, httplib::Response& response) + void set_dns_servers(nlohmann::json servers) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - std::stringstream responsedata; - responsedata << R"({"servers":)"; - responsedata << config::config.getConfig()["settings"]["dns"]["servers"]; - responsedata << "}"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_dns_servers(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } std::ofstream dnsserversfile("/etc/dnsmasq.conf.d/resolv.conf"); - nlohmann::json servers = json_body["servers"]; for (const nlohmann::json_abi_v3_11_3::json& server : servers) { std::string srv = server; @@ -65,15 +21,8 @@ namespace api dnsserversfile << "nameserver " << srv << "\n"; } dnsserversfile.close(); - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["dns"]["servers"] = json_body["servers"]; - config::config.setConfig(new_config); - config::config.saveConfig(); system("rc restart dnsmasq"); syslog(LOG_INFO, "%s", "DNS servers changed"); - std::string responsedata = R"({"success":"true"})"; - response.set_content(responsedata, JSON_TYPE); } } } diff --git a/src/settings/ftp/ftp.cpp b/src/settings/ftp/ftp.cpp index 50a7271..33c0026 100644 --- a/src/settings/ftp/ftp.cpp +++ b/src/settings/ftp/ftp.cpp @@ -1,11 +1,6 @@ #include -#include -#include -#include #include -#include -#include -#include +#include #define FTP_REQUIRED_STRING R"({"required":"[token,enabled]"})" @@ -15,52 +10,8 @@ namespace api { namespace ftp { - void get_ftp(const httplib::Request& request, httplib::Response& response) + void set_ftp(bool new_ftp_enabled) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int ftp_enabled = config::config.getConfig()["settings"]["ftp"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"enabled":")" << ftp_enabled << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_ftp(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["enabled"] == nullptr) - { - response.set_content(FTP_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - bool new_ftp_enabled = json_body["enabled"]; if (!new_ftp_enabled) { system("rc stop ftp"); @@ -73,15 +24,6 @@ namespace api system("rc add ftp"); syslog(LOG_INFO, "%s", "Ftp is enabled"); } - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["ftp"]["enabled"] = new_ftp_enabled; - config::config.setConfig(new_config); - config::config.saveConfig(); - int ftp_enabled = config::config.getConfig()["settings"]["ftp"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"enabled":")" << ftp_enabled << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/ftp/ftp.hpp b/src/settings/ftp/ftp.hpp index d04e9b7..a7daca2 100644 --- a/src/settings/ftp/ftp.hpp +++ b/src/settings/ftp/ftp.hpp @@ -1,16 +1,13 @@ #ifndef FTP_HPP #define FTP_HPP -#include - namespace api { namespace settings { namespace ftp { - void get_ftp(const httplib::Request& request, httplib::Response& response); - void set_ftp(const httplib::Request& request, httplib::Response& response); + void set_ftp(bool new_ftp_enabled); } } } diff --git a/src/settings/settings.cpp b/src/settings/settings.cpp new file mode 100644 index 0000000..0485fc3 --- /dev/null +++ b/src/settings/settings.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define SETTINGS_REQUIRED_STRING R"({"required":"[settings]"})" + +namespace api +{ + namespace settings + { + void set_all_settings(nlohmann::json json_body) + { + api::settings::wifi::set_wifi_status(json_body["settings"]["wifi"]["enabled"]); + std::string wifichannel = json_body["settings"]["wifi"]["channel"]; + api::settings::wifi::set_wifi_channel(std::stoi(wifichannel)); + api::settings::wifi::set_wifi_name(json_body["settings"]["wifi"]["name"]); + + api::settings::telnet::set_telnet(json_body["settings"]["telnet"]["enabled"]); + std::string telnetport = json_body["settings"]["telnet"]["port"]; + api::settings::telnet::set_telnet_port(std::stoi(telnetport)); + + api::settings::ssh::set_ssh_status(json_body["settings"]["ssh"]["enabled"]); + api::settings::ssh::set_ssh_server(json_body["settings"]["ssh"]["server"]); + std::string sshport = json_body["settings"]["ssh"]["port"]; + api::settings::ssh::set_ssh_port(std::stoi(sshport)); + api::settings::ssh::set_ssh_keys(json_body["settings"]["ssh"]["keys"]); + + api::settings::ftp::set_ftp(json_body["settings"]["ftp"]["enabled"]); + + api::settings::dns::set_dns(json_body["settings"]["dns"]["enabled"]); + api::settings::dns::set_dns_servers(json_body["settings"]["dns"]["servers"]); + api::settings::dns::set_local_domains(json_body["settings"]["dns"]["localdomains"]); + } + + void get(const httplib::Request& request, httplib::Response& response) + { + std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; + response.set_header("Access-Control-Allow-Origin", "*"); + if (!check_body(request)) + { + response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); + return; + } + nlohmann::json json_body = nlohmann::json::parse(request.body); + + if (!authenticate(json_body["token"])) + { + response.set_content(R"({"access":"reject"})", JSON_TYPE); + return; + } + config::config.setConfig(config::config.getConfigFileName()); + std::string responsedata = R"({"settings":)" + config::config.getConfig()["settings"].dump() + R"(})"; + response.set_content(responsedata, JSON_TYPE); + } + + void set(const httplib::Request& request, httplib::Response& response) + { + std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; + response.set_header("Access-Control-Allow-Origin", "*"); + if (!check_body(request)) + { + response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); + return; + } + nlohmann::json json_body = nlohmann::json::parse(request.body); + + if (json_body["settings"] == nullptr) + { + response.set_content(SETTINGS_REQUIRED_STRING, JSON_TYPE); + return; + } + + if (!authenticate(json_body["token"])) + { + response.set_content(R"({"access":"reject"})", JSON_TYPE); + return; + } + usleep(300); + config::config.setConfig(config::config.getConfigFileName()); + nlohmann::json new_config = config::config.getConfig(); + new_config["settings"] = json_body["settings"]; + config::config.setConfig(new_config); + config::config.saveConfig(); + + set_all_settings(json_body); + + // api::settings::wifi::set_wifi_status(json_body["settings"]["wifi"]["enabled"]); + // std::string wifichannel = json_body["settings"]["wifi"]["channel"]; + // api::settings::wifi::set_wifi_channel(std::stoi(wifichannel)); + // api::settings::wifi::set_wifi_name(json_body["settings"]["wifi"]["name"]); + + // api::settings::telnet::set_telnet(json_body["settings"]["telnet"]["enabled"]); + // std::string telnetport = json_body["settings"]["telnet"]["port"]; + // api::settings::telnet::set_telnet_port(std::stoi(telnetport)); + + // api::settings::ssh::set_ssh_status(json_body["settings"]["ssh"]["enabled"]); + // api::settings::ssh::set_ssh_server(json_body["settings"]["ssh"]["server"]); + // std::string sshport = json_body["settings"]["ssh"]["port"]; + // api::settings::ssh::set_ssh_port(std::stoi(sshport)); + // api::settings::ssh::set_ssh_keys(json_body["settings"]["ssh"]["keys"]); + + // api::settings::ftp::set_ftp(json_body["settings"]["ftp"]["enabled"]); + + // api::settings::dns::set_dns(json_body["settings"]["dns"]["enabled"]); + // api::settings::dns::set_dns_servers(json_body["settings"]["dns"]["servers"]); + // api::settings::dns::set_local_domains(json_body["settings"]["dns"]["localdomains"]); + + std::string responsedata = R"({"settings":)" + config::config.getConfig()["settings"].dump() + R"(})"; + response.set_content(responsedata, JSON_TYPE); + } + } +} diff --git a/src/settings/settings.hpp b/src/settings/settings.hpp index 678166c..473037e 100644 --- a/src/settings/settings.hpp +++ b/src/settings/settings.hpp @@ -7,4 +7,17 @@ #include #include +namespace api +{ + namespace settings + { + void set_all_settings(nlohmann::json json_body); + void backup(const httplib::Request& request, httplib::Response& response); + void restore(const httplib::Request& request, httplib::Response& response); + void restore_defaults(const httplib::Request& request, httplib::Response& response); + void get(const httplib::Request& request, httplib::Response& response); + void set(const httplib::Request& request, httplib::Response& response); + } +} + #endif // SETTINGS_HPP diff --git a/src/settings/ssh/keys.cpp b/src/settings/ssh/keys.cpp index f602a6e..5afe324 100644 --- a/src/settings/ssh/keys.cpp +++ b/src/settings/ssh/keys.cpp @@ -1,10 +1,8 @@ #include #include #include -#include #include #include -#include #include #define KEYS_PATH "~/.ssh/authorized_keys" @@ -15,62 +13,16 @@ namespace api { namespace ssh { - void get_ssh_keys(const httplib::Request& request, httplib::Response& response) + void set_ssh_keys(nlohmann::json keys) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - std::stringstream responsedata; - responsedata << R"({"keys":)"; - responsedata << config::config.getConfig()["settings"]["ssh"]["keys"]; - responsedata << "}"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_ssh_keys(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } std::ofstream keysfile(KEYS_PATH); - nlohmann::json keys = json_body["keys"]; for (std::string key : keys) { keysfile << key << "\n"; } keysfile.close(); - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["ssh"]["keys"] = json_body["keys"]; - config::config.setConfig(new_config); - config::config.saveConfig(); system("rc restart ssh"); syslog(LOG_INFO, "%s", "SSH keys changed"); - std::string responsedata = R"({"success":"true"})"; - response.set_content(responsedata, JSON_TYPE); } } } diff --git a/src/settings/ssh/port.cpp b/src/settings/ssh/port.cpp index c8c6ccc..ef21eb6 100644 --- a/src/settings/ssh/port.cpp +++ b/src/settings/ssh/port.cpp @@ -1,18 +1,14 @@ #include -#include -#include -#include #include #include #include #include #include -#include -#include #include #define SSH_PORT_REQUIRED_STRING R"({"required":"[token,port]"})" -#define SSH_PORT_PATH "/etc/openrouter/ssh/port" +#define DROPBEAR_DEFAULT_FILE "/etc/default/dropbear" +#define SSHD_CONFIG_FILE "/etc/ssh/sshd_config" namespace api { @@ -20,67 +16,17 @@ namespace api { namespace ssh { - void get_ssh_port(const httplib::Request& request, httplib::Response& response) + void set_ssh_port(int new_ssh_port) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int ssh_port = config::config.getConfig()["settings"]["ssh"]["port"]; - std::stringstream responsedata; - responsedata << R"({"port":")" << ssh_port << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_ssh_port(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["port"] == nullptr) - { - response.set_content(SSH_PORT_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - std::ifstream oldsshportfile(SSH_PORT_PATH); - int old_ssh_port; - oldsshportfile >> old_ssh_port; - oldsshportfile.close(); - std::ofstream sshportfileout(SSH_PORT_PATH); - std::string new_ssh_port = json_body["port"]; - sshportfileout << stoi(new_ssh_port); - sshportfileout.close(); + std::string old_ssh_port = config::config.getConfig()["settings"]["ssh"]["port"]; std::stringstream systemrequest; if (get_ssh_server() == DROPBEAR_SSH) { - systemrequest << "sed -i \"s/DROPBEAR_PORT=" << old_ssh_port << "/DROPBEAR_PORT=" << new_ssh_port << "/g\" /etc/default/dropbear"; + systemrequest << "sed -i \"s/DROPBEAR_PORT=" << old_ssh_port << "/DROPBEAR_PORT=" << new_ssh_port << "/g\" " << DROPBEAR_DEFAULT_FILE; } else if (get_ssh_server() == OPENSSH_SSH) { - systemrequest << "sed -i \"/Port " << old_ssh_port << "/s//Port " << new_ssh_port << "/\" /etc/ssh/sshd_config"; + systemrequest << "sed -i \"/Port " << old_ssh_port << "/s//Port " << new_ssh_port << "/\" " << SSHD_CONFIG_FILE; } std::cout << systemrequest.str() << std::endl; system(systemrequest.str().c_str()); @@ -94,15 +40,6 @@ namespace api } std::string syslogstring = "SSH port changed to " + new_ssh_port; syslog(LOG_INFO, "%s", syslogstring.c_str()); - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["ssh"]["port"] = std::stoi(new_ssh_port); - config::config.setConfig(new_config); - config::config.saveConfig(); - int ssh_port = config::config.getConfig()["settings"]["ssh"]["port"]; - std::stringstream responsedata; - responsedata << R"({"port":")" << ssh_port << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/ssh/server.cpp b/src/settings/ssh/server.cpp index 8f19c34..8989bda 100644 --- a/src/settings/ssh/server.cpp +++ b/src/settings/ssh/server.cpp @@ -1,10 +1,4 @@ #include -#include -#include -#include -#include -#include -#include #include #define SSH_SERVER_REQUIRED_STRING R"({"required":"[token,server]"})" @@ -17,65 +11,12 @@ namespace api { std::string get_ssh_server() { - config::config.setConfig(std::string("../config.json")); + config::config.setConfig(config::config.getConfigFileName()); return config::config.getConfig()["settings"]["ssh"]["server"]; } - std::string set_ssh_server(std::string& server) + void set_ssh_server(std::string new_server) { - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["ssh"]["server"] = server; - config::config.setConfig(new_config); - config::config.saveConfig(); - return config::config.getConfig()["settings"]["ssh"]["server"]; - } - - void get_ssh_server_http(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - std::stringstream responsedata; - responsedata << R"({"server":")" << get_ssh_server() << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - void set_ssh_server_http(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["server"] == nullptr) - { - response.set_content(SSH_SERVER_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - std::stringstream responsedata; - std::string server = json_body["server"]; - std::string new_server = set_ssh_server(server); if (new_server == DROPBEAR_SSH) { system("rc stop ssh"); @@ -89,8 +30,6 @@ namespace api system("rc start ssh"); system("rc add ssh"); } - responsedata << R"({"server":")" << new_server << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/ssh/ssh.hpp b/src/settings/ssh/ssh.hpp index 4d9a23b..2326685 100644 --- a/src/settings/ssh/ssh.hpp +++ b/src/settings/ssh/ssh.hpp @@ -1,7 +1,7 @@ #ifndef SSH_HPP #define SSH_HPP -#include +#include #define DROPBEAR_SSH "dropbear" #define OPENSSH_SSH "openssh" @@ -13,19 +13,10 @@ namespace api namespace ssh { std::string get_ssh_server(); - std::string set_ssh_server(std::string& server); - - void get_ssh_server_http(const httplib::Request& request, httplib::Response& response); - void set_ssh_server_http(const httplib::Request& request, httplib::Response& response); - - void get_ssh_status(const httplib::Request& request, httplib::Response& response); - void set_ssh_status(const httplib::Request& request, httplib::Response& response); - - void get_ssh_keys(const httplib::Request& request, httplib::Response& response); - void set_ssh_keys(const httplib::Request& request, httplib::Response& response); - - void get_ssh_port(const httplib::Request& request, httplib::Response& response); - void set_ssh_port(const httplib::Request& request, httplib::Response& response); + void set_ssh_server(std::string new_server); + void set_ssh_status(bool new_ssh_status); + void set_ssh_keys(nlohmann::json keys); + void set_ssh_port(int port); } } } diff --git a/src/settings/ssh/status.cpp b/src/settings/ssh/status.cpp index f8dd807..2f2024f 100644 --- a/src/settings/ssh/status.cpp +++ b/src/settings/ssh/status.cpp @@ -1,11 +1,5 @@ #include -#include -#include -#include #include -#include -#include -#include #define SSH_REQUIRED_STRING R"({"required":"[token,status]"})" @@ -15,52 +9,8 @@ namespace api { namespace ssh { - void get_ssh_status(const httplib::Request& request, httplib::Response& response) + void set_ssh_status(bool new_ssh_status) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int ssh_status = config::config.getConfig()["settings"]["ssh"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"status":")" << ssh_status << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_ssh_status(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["status"] == nullptr) - { - response.set_content(SSH_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - bool new_ssh_status = json_body["status"]; if (!new_ssh_status) { if (get_ssh_server() == DROPBEAR_SSH) @@ -87,15 +37,6 @@ namespace api } syslog(LOG_INFO, "%s", "SSH is enabled"); } - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["ssh"]["enabled"] = new_ssh_status; - config::config.setConfig(new_config); - config::config.saveConfig(); - int ssh_status = config::config.getConfig()["settings"]["ssh"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"status":")" << ssh_status << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/telnet/enabled.cpp b/src/settings/telnet/enabled.cpp index 811e6c6..eadfb80 100644 --- a/src/settings/telnet/enabled.cpp +++ b/src/settings/telnet/enabled.cpp @@ -1,15 +1,6 @@ #include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include +#include #define TELNET_REQUIRED_STRING R"({"required":"[token,enabled]"})" @@ -19,52 +10,8 @@ namespace api { namespace telnet { - void get_telnet(const httplib::Request& request, httplib::Response& response) + void set_telnet(bool new_telnet_enabled) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int telnet_enabled = config::config.getConfig()["settings"]["telnet"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"enabled":")" << telnet_enabled << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_telnet(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["enabled"] == nullptr) - { - response.set_content(TELNET_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - bool new_telnet_enabled = json_body["enabled"]; if (!new_telnet_enabled) { system("rc stop telnet"); @@ -77,15 +24,6 @@ namespace api system("rc add telnet"); syslog(LOG_INFO, "%s", "Telnet is enabled"); } - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["telnet"]["enabled"] = new_telnet_enabled; - config::config.setConfig(new_config); - config::config.saveConfig(); - int telnet_enabled = config::config.getConfig()["settings"]["telnet"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"enabled":")" << telnet_enabled << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/telnet/port.cpp b/src/settings/telnet/port.cpp index a55b7fd..a4e6154 100644 --- a/src/settings/telnet/port.cpp +++ b/src/settings/telnet/port.cpp @@ -1,17 +1,10 @@ #include -#include -#include -#include +#include #include #include -#include -#include -#include -#include -#include #include -#define TELNET_PORT_REQUIRED_STRING R"({"required":"[token,port]"})" +#define TELNET_PORT_FILE "/etc/default/telnet" namespace api { @@ -19,64 +12,16 @@ namespace api { namespace telnet { - void get_telnet_port(const httplib::Request& request, httplib::Response& response) + void set_telnet_port(int new_telnet_port) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int telnet_port = config::config.getConfig()["settings"]["telnet"]["port"]; - std::stringstream responsedata; - responsedata << R"({"port":")" << telnet_port << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_telnet_port(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["port"] == nullptr) - { - response.set_content(TELNET_PORT_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - std::string new_telnet_port = json_body["port"]; + std::string old_telnet_port = config::config.getConfig()["settings"]["telnet"]["port"]; + std::stringstream systemrequest; + systemrequest << "sed -i \"s/TELNET_PORT=" << old_telnet_port << "/TELNET_PORT=" << new_telnet_port << "/g\" " << TELNET_PORT_FILE; + std::cout << systemrequest.str() << std::endl; + system(systemrequest.str().c_str()); system("rc restart telnet"); std::string syslogstring = "Telnet port changed to " + new_telnet_port; syslog(LOG_INFO, "%s", syslogstring.c_str()); - std::stringstream responsedata; - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["telnet"]["port"] = stoi(new_telnet_port); - config::config.setConfig(new_config); - config::config.saveConfig(); - int telnet_port = config::config.getConfig()["settings"]["telnet"]["port"]; - responsedata << R"({"port":")" << telnet_port << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/telnet/telnet.hpp b/src/settings/telnet/telnet.hpp index cd3041c..e59d085 100644 --- a/src/settings/telnet/telnet.hpp +++ b/src/settings/telnet/telnet.hpp @@ -1,19 +1,14 @@ #ifndef SETTINGS_TELNET_HPP #define SETTINGS_TELNET_HPP -#include - namespace api { namespace settings { namespace telnet { - void get_telnet(const httplib::Request& request, httplib::Response& response); - void set_telnet(const httplib::Request& request, httplib::Response& response); - - void get_telnet_port(const httplib::Request& request, httplib::Response& response); - void set_telnet_port(const httplib::Request& request, httplib::Response& response); + void set_telnet(bool new_telnet_enabled); + void set_telnet_port(int new_telnet_port); } } } diff --git a/src/settings/wifi/channel.cpp b/src/settings/wifi/channel.cpp index 7b7056f..001d40b 100644 --- a/src/settings/wifi/channel.cpp +++ b/src/settings/wifi/channel.cpp @@ -1,15 +1,8 @@ #include -#include -#include -#include #include #include #include -#include #include -#include -#include -#include #define WIFI_CHANNEL_REQUIRED_STRING R"({"required":"[token,channel]"})" @@ -19,51 +12,8 @@ namespace api { namespace wifi { - void get_wifi_channel(const httplib::Request& request, httplib::Response& response) + void set_wifi_channel(int new_channel) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int wifi_channel = config::config.getConfig()["settings"]["wifi"]["channel"]; - std::stringstream responsedata; - responsedata << R"({"channel":")" << wifi_channel << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_wifi_channel(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["channel"] == nullptr) - { - response.set_content(WIFI_CHANNEL_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } std::ifstream wifisettingsfile(WIFI_SETTINGS_PATH); std::string old_channel; std::string wifisettingsbuffer; @@ -77,7 +27,6 @@ namespace api } } wifisettingsfile.close(); - std::string new_channel = json_body["channel"]; std::stringstream systemrequest; systemrequest << "sed -i \"/channel=" << old_channel << "/s//channel=" << new_channel << "/\" /etc/hostapd/hostapd.conf"; std::cout << systemrequest.str() << std::endl; @@ -85,16 +34,6 @@ namespace api system("rc restart hostapd"); std::string syslogstring = "WIFI channel changed to " + new_channel; syslog(LOG_INFO, "%s", syslogstring.c_str()); - sleep(1); - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["wifi"]["channel"] = std::stoi(new_channel); - config::config.setConfig(new_config); - config::config.saveConfig(); - int wifi_channel = config::config.getConfig()["settings"]["wifi"]["channel"]; - std::stringstream responsedata; - responsedata << R"({"channel":")" << wifi_channel << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/wifi/name.cpp b/src/settings/wifi/name.cpp index d6827d3..c386f65 100644 --- a/src/settings/wifi/name.cpp +++ b/src/settings/wifi/name.cpp @@ -1,15 +1,8 @@ #include -#include -#include -#include #include #include #include -#include #include -#include -#include -#include #define WIFI_NAME_REQUIRED_STRING R"({"required":"[token,name]"})" @@ -19,51 +12,8 @@ namespace api { namespace wifi { - void get_wifi_name(const httplib::Request& request, httplib::Response& response) + void set_wifi_name(std::string new_wifi_name) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - std::string wifi_name = config::config.getConfig()["settings"]["wifi"]["name"]; - std::stringstream responsedata; - responsedata << R"({"name":")" << wifi_name << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_wifi_name(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["name"] == nullptr) - { - response.set_content(WIFI_NAME_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } std::ifstream wifisettingsfile(WIFI_SETTINGS_PATH); std::string old_wifi_name; std::string wifisettingsbuffer; @@ -77,7 +27,6 @@ namespace api } } wifisettingsfile.close(); - std::string new_wifi_name = json_body["name"]; std::stringstream systemrequest; systemrequest << "sed -i \"/ssid=" << old_wifi_name << "/s//ssid=" << new_wifi_name << "/\" /etc/hostapd/hostapd.conf"; std::cout << systemrequest.str() << std::endl; @@ -85,29 +34,6 @@ namespace api system("rc restart hostapd"); std::string syslogstring = "WIFI name changed to " + new_wifi_name; syslog(LOG_INFO, "%s", syslogstring.c_str()); - - std::ifstream changedwifisettingsfile(WIFI_SETTINGS_PATH); - std::string wifi_name; - std::string changedwifisettingsbuffer; - while (std::getline(changedwifisettingsfile, changedwifisettingsbuffer)) - { - if (changedwifisettingsbuffer.find("ssid=") == 0) - { - wifi_name = changedwifisettingsbuffer; - wifi_name.erase(0, 5); - break; - } - } - changedwifisettingsfile.close(); - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["wifi"]["name"] = new_wifi_name; - config::config.setConfig(new_config); - config::config.saveConfig(); - wifi_name = config::config.getConfig()["settings"]["wifi"]["name"]; - std::stringstream responsedata; - responsedata << R"({"name":")" << wifi_name << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/wifi/password.cpp b/src/settings/wifi/password.cpp index 2c2e3f1..2a18af9 100644 --- a/src/settings/wifi/password.cpp +++ b/src/settings/wifi/password.cpp @@ -19,29 +19,6 @@ namespace api { namespace wifi { - void get_wifi_password(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - std::string wifi_password = config::config.getConfig()["settings"]["wifi"]["password"]; - std::stringstream responsedata; - responsedata << R"({"password":")" << wifi_password << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - void set_wifi_password(const httplib::Request& request, httplib::Response& response) { std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; @@ -84,8 +61,8 @@ namespace api system("rc restart hostapd"); std::string syslogstring = "WIFI password changed"; syslog(LOG_INFO, "%s", syslogstring.c_str()); - sleep(1); - config::config.setConfig(std::string("../config.json")); + usleep(500); + config::config.setConfig(config::config.getConfigFileName()); nlohmann::json new_config = config::config.getConfig(); new_config["settings"]["wifi"]["password"] = new_wifi_password; config::config.setConfig(new_config); diff --git a/src/settings/wifi/qr.cpp b/src/settings/wifi/qr.cpp index e060c4b..9796448 100644 --- a/src/settings/wifi/qr.cpp +++ b/src/settings/wifi/qr.cpp @@ -30,7 +30,7 @@ namespace api response.set_content(R"({"access":"reject"})", JSON_TYPE); return; } - config::config.setConfig(std::string("../config.json")); + config::config.setConfig(config::config.getConfigFileName()); std::string wifi_password = config::config.getConfig()["settings"]["wifi"]["password"]; std::string wifi_name = config::config.getConfig()["settings"]["wifi"]["name"]; std::stringstream toqrstring; diff --git a/src/settings/wifi/status.cpp b/src/settings/wifi/status.cpp index 83f836c..8011fd0 100644 --- a/src/settings/wifi/status.cpp +++ b/src/settings/wifi/status.cpp @@ -1,11 +1,5 @@ #include -#include -#include -#include #include -#include -#include -#include #define WIFI_REQUIRED_STRING R"({"required":"[token,status]"})" @@ -15,52 +9,8 @@ namespace api { namespace wifi { - void get_wifi_status(const httplib::Request& request, httplib::Response& response) + void set_wifi_status(bool new_wifi_status) { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - config::config.setConfig(std::string("../config.json")); - int wifi_status = config::config.getConfig()["settings"]["wifi"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"status":")" << wifi_status << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); - } - - void set_wifi_status(const httplib::Request& request, httplib::Response& response) - { - std::cout << GREEN << request.path << RESET << " " << request.method << std::endl; - response.set_header("Access-Control-Allow-Origin", "*"); - if (!check_body(request)) - { - response.set_content(ACCESS_REQUIRED_STRING, JSON_TYPE); - return; - } - nlohmann::json json_body = nlohmann::json::parse(request.body); - - if (json_body["status"] == nullptr) - { - response.set_content(WIFI_REQUIRED_STRING, JSON_TYPE); - return; - } - - if (!authenticate(json_body["token"])) - { - response.set_content(R"({"access":"reject"})", JSON_TYPE); - return; - } - bool new_wifi_status = json_body["status"]; if (!new_wifi_status) { system("rc stop hostapd"); @@ -73,16 +23,6 @@ namespace api system("rc add hostapd"); syslog(LOG_INFO, "%s", "WIFI is enabled"); } - sleep(1); - config::config.setConfig(std::string("../config.json")); - nlohmann::json new_config = config::config.getConfig(); - new_config["settings"]["wifi"]["enabled"] = new_wifi_status; - config::config.setConfig(new_config); - config::config.saveConfig(); - int wifi_status = config::config.getConfig()["settings"]["wifi"]["enabled"]; - std::stringstream responsedata; - responsedata << R"({"status":")" << wifi_status << R"("})"; - response.set_content(responsedata.str(), JSON_TYPE); } } } diff --git a/src/settings/wifi/wifi.hpp b/src/settings/wifi/wifi.hpp index 0d6b343..12e06a2 100644 --- a/src/settings/wifi/wifi.hpp +++ b/src/settings/wifi/wifi.hpp @@ -11,19 +11,11 @@ namespace api { namespace wifi { - void get_wifi_status(const httplib::Request& request, httplib::Response& response); - void set_wifi_status(const httplib::Request& request, httplib::Response& response); - - void get_wifi_name(const httplib::Request& request, httplib::Response& response); - void set_wifi_name(const httplib::Request& request, httplib::Response& response); - - void get_wifi_password(const httplib::Request& request, httplib::Response& response); + void set_wifi_status(bool new_wifi_status); + void set_wifi_name(std::string new_wifi_name); void set_wifi_password(const httplib::Request& request, httplib::Response& response); - void get_wifi_qr(const httplib::Request& request, httplib::Response& response); - - void get_wifi_channel(const httplib::Request& request, httplib::Response& response); - void set_wifi_channel(const httplib::Request& request, httplib::Response& response); + void set_wifi_channel(int new_channel); } } } diff --git a/src/web/css/form.css b/src/web/css/form.css index adff24a..53723a2 100644 --- a/src/web/css/form.css +++ b/src/web/css/form.css @@ -48,7 +48,7 @@ .forminputbody { width: 50%; display: flex; - flex-direction: column; + justify-content: space-between; } .forminputbody.confirmform { @@ -92,6 +92,7 @@ border: none; cursor: pointer; transition: 0.3s; + width: 100%; } .formbutton:hover { @@ -110,6 +111,13 @@ } } + +@media screen and (max-width: 600px) { + .forminputbody { + flex-direction: column; + } +} + @media screen and (max-width: 500px) { .formbody { width: 100%; diff --git a/src/web/css/header.css b/src/web/css/header.css index aab2e97..a421f5f 100644 --- a/src/web/css/header.css +++ b/src/web/css/header.css @@ -2,7 +2,6 @@ header { width: 100vw; height: 50px; padding: 10px; - margin: 5px; display: grid; grid-template-columns: 32% 32% 32%; align-items: center; diff --git a/src/web/css/index.css b/src/web/css/index.css index 65e1f04..dd7f3a3 100644 --- a/src/web/css/index.css +++ b/src/web/css/index.css @@ -9,17 +9,21 @@ } body { - width: 100vw; - height: 100vh; + position: absolute; + width: 100%; + height: 100%; } .adminpanel { position: relative; width: 100%; - height: 100%; + height: 100vh; transform: scale(0.5); opacity: 0; transition: 0.3s; + display: flex; + flex-direction: column; + gap: 10px; } .adminpanel.active { @@ -29,6 +33,8 @@ body { .section { position: relative; + display: block; overflow: hidden; - height: calc(100vh - 50px); + height: 100%; + width: 100%; } diff --git a/src/web/index.html b/src/web/index.html index e8a14fa..f9a29b6 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -192,6 +192,22 @@ + +
+
+

Backup settings

+
+
+ + + + + + + +
+
+
@@ -209,7 +225,7 @@
-

0.1.1

+

0.1.1

@@ -241,17 +257,6 @@
- -
-
-

Confirm DNS settings?

-
- - -
-
-
-

Reboting

@@ -265,13 +270,13 @@ - + - + @@ -279,4 +284,4 @@ - \ No newline at end of file + diff --git a/src/web/js/header.js b/src/web/js/header.js index 70e487f..49a2cf4 100644 --- a/src/web/js/header.js +++ b/src/web/js/header.js @@ -4,10 +4,8 @@ const syslogsection = document.querySelector('.syslogsection') const syslogbutton = document.querySelector('#syslogbutton') const infobutton = document.querySelector('#infobutton') const infosection = document.querySelector('.infosection') - const headermenubutton = document.querySelector('#headermenubutton') const headerlinks = document.querySelector('.links') -// const header = document.querySelector('.header') let uptimeintervalid @@ -47,9 +45,3 @@ infobutton.addEventListener('click', () => { infosection.classList.add('active') uptimeintervalid = setInterval(get_uptime, 1000) }) - -// if (window.screenX > 600) { -// header.removeChild(headermenubutton) -// } else { -// header.prepend(headermenubutton) -// } diff --git a/src/web/js/login.js b/src/web/js/login.js index b415053..ee0b795 100644 --- a/src/web/js/login.js +++ b/src/web/js/login.js @@ -49,7 +49,7 @@ const login = async (username, password) => { notification("Успешно", "success") loginform.classList.remove("active") localStorage.setItem("token", response.token) - get_all_settings() + await get_all_settings() adminpanelsection.classList.add('active') await get_user_data() loginusername.value = "" diff --git a/src/web/js/routes.js b/src/web/js/routes.js index a328433..499c56c 100644 --- a/src/web/js/routes.js +++ b/src/web/js/routes.js @@ -2,44 +2,19 @@ const host = window.location.origin const prefix = 'api' const routes = { - login: () => [host, prefix, 'token'].join('/'), - user: () => [host, prefix, 'users', 'user'].join('/'), + login: () => ["http://localhost:43243", prefix, 'token'].join('/'), + user: () => ["http://localhost:43243", prefix, 'users', 'user'].join('/'), - telnet_status: () => [host, prefix, 'settings', 'telnet', 'get'].join('/'), - telnet_status_set: () => [host, prefix, 'settings', 'telnet', 'set'].join('/'), - telnet_port_get: () => [host, prefix, 'settings', 'telnet', 'port', 'get'].join('/'), - telnet_port_set: () => [host, prefix, 'settings', 'telnet', 'port', 'set'].join('/'), - - ssh_status_get: () => [host, prefix, 'settings', 'ssh', 'status', 'get'].join('/'), - ssh_status_set: () => [host, prefix, 'settings', 'ssh', 'status', 'set'].join('/'), - ssh_keys_get: () => [host, prefix, 'settings', 'ssh', 'keys', 'get'].join('/'), - ssh_keys_set: () => [host, prefix, 'settings', 'ssh', 'keys', 'set'].join('/'), - ssh_port_get: () => [host, prefix, 'settings', 'ssh', 'port', 'get'].join('/'), - ssh_port_set: () => [host, prefix, 'settings', 'ssh', 'port', 'set'].join('/'), - ssh_server_get: () => [host, prefix, 'settings', 'ssh', 'server', 'get'].join('/'), - ssh_server_set: () => [host, prefix, 'settings', 'ssh', 'server', 'set'].join('/'), - - dns_status: () => [host, prefix, 'settings', 'dns', 'get'].join('/'), - dns_status_set: () => [host, prefix, 'settings', 'dns', 'set'].join('/'), - dns_servers_get: () => [host, prefix, 'settings', 'dns', 'servers', 'get'].join('/'), - dns_servers_set: () => [host, prefix, 'settings', 'dns', 'servers', 'set'].join('/'), - dns_local_domains_get: () => [host, prefix, 'settings', 'dns', 'domains', 'get'].join('/'), - dns_local_domains_set: () => [host, prefix, 'settings', 'dns', 'domains', 'set'].join('/'), - - ftp_status: () => [host, prefix, 'settings', 'ftp', 'get'].join('/'), - ftp_status_set: () => [host, prefix, 'settings', 'ftp', 'set'].join('/'), - - wifi_status_get: () => [host, prefix, 'settings', 'wifi', 'status', 'get'].join('/'), - wifi_status_set: () => [host, prefix, 'settings', 'wifi', 'status', 'set'].join('/'), - wifi_name_get: () => [host, prefix, 'settings', 'wifi', 'name', 'get'].join('/'), - wifi_name_set: () => [host, prefix, 'settings', 'wifi', 'name', 'set'].join('/'), - wifi_password_get: () => [host, prefix, 'settings', 'wifi', 'password', 'get'].join('/'), wifi_password_set: () => [host, prefix, 'settings', 'wifi', 'password', 'set'].join('/'), wifi_qr_get: () => [host, prefix, 'settings', 'wifi', 'qr', 'get'].join('/'), - wifi_channel_get: () => [host, prefix, 'settings', 'wifi', 'channel', 'get'].join('/'), - wifi_channel_set: () => [host, prefix, 'settings', 'wifi', 'channel', 'set'].join('/'), + + settings_get: () => [host, prefix, 'settings', 'get'].join('/'), + settings_set: () => [host, prefix, 'settings', 'set'].join('/'), + settings_backup: () => [host, prefix, 'settings', 'backup'].join('/'), + settings_restore: () => [host, prefix, 'settings', 'restore'].join('/'), + restore_defaults: () => [host, prefix, 'settings', 'restore', 'defaults'].join('/'), reboot: () => [host, prefix, 'reboot'].join('/'), syslog: () => [host, prefix, 'syslog'].join('/'), uptime: () => [host, prefix, 'uptime'].join('/'), -} \ No newline at end of file +} diff --git a/src/web/js/settings/backup.js b/src/web/js/settings/backup.js new file mode 100644 index 0000000..1b69918 --- /dev/null +++ b/src/web/js/settings/backup.js @@ -0,0 +1,49 @@ +const backup_settings_button = document.querySelector('#backup_settings') +const restore_setings_button = document.querySelector('#restore_settings') +const restore_settings_input = document.querySelector('#restore_settings_input') +const start_restore_settings = document.querySelector('#start_restore_settings') +const restore_default_settings = document.querySelector('#restore_default_settings') + +backup_settings_button.href = routes.settings_backup() + +restore_setings_button.addEventListener('click', () => restore_settings_input.click()) + +start_restore_settings.addEventListener('click', () => { + let restore_file = restore_settings_input.files[0] + console.log(restore_file) + let formData = new FormData() + formData.append('restore_settings', restore_file) + fetch(routes.settings_restore(), {method:"POST", body: formData}) + .then(data => data.json()) + .then(jsondata => { + console.log(jsondata) + if (jsondata.status) { + notification('Настройки применены', 'success') + get_all_settings() + } else { + notification('Некорректный файл', 'error') + } + }).catch((error) => { + notification(`Ошибка на сервере: ${error}`, "error") + }) +}) + +restore_default_settings.addEventListener('click', () => { + fetch(routes.restore_defaults(), { + method: 'POST', + body: `{"token":"${localStorage.getItem("token")}"}` + }).then(data => data.json()).then(jsondata => { + if (jsondata.status) { + notification(`Настройки успешно сохранены`, "success"); + get_all_settings() + } else { + notification(`Неизвестная ошибка`, "error"); + } + }).catch((error) => { + notification(`Ошибка на сервере: ${error}`, "error") + }) +}) + +// restore_settings_input.addEventListener('click', () => { + +// }) diff --git a/src/web/js/settings/confirm.js b/src/web/js/settings/confirm.js deleted file mode 100644 index 197ea86..0000000 --- a/src/web/js/settings/confirm.js +++ /dev/null @@ -1,33 +0,0 @@ -const confirmform = document.querySelector('#confirmform') -const confirmformheader = document.querySelector('#confirmformheader') -const cancelbutton = document.querySelector('#cancel') -const confirmformbuttons = document.querySelector('#confirmformbuttons') - -const defaultcancelreqfunc = () => { - confirmform.classList.remove('active') -} - -const get_confirm_form = (header, reqfunc = defaultcancelreqfunc, cancelreqfunc = defaultcancelreqfunc) => { - confirmformheader.innerHTML = header - confirmform.classList.add('active') - - var confirmbutton = document.createElement('button') - confirmbutton.classList.add('formbutton') - confirmbutton.id = "confirm" - confirmbutton.innerHTML = "Confirm" - confirmbutton.addEventListener('click', () => { - reqfunc() - confirmform.classList.remove('active') - }) - confirmformbuttons.replaceChild(confirmbutton, document.querySelector('#confirm')) - - var cancelbutton = document.createElement('button') - cancelbutton.classList.add('formbutton') - cancelbutton.id = "cancel" - cancelbutton.innerHTML = "Cancel" - cancelbutton.addEventListener('click', () => { - cancelreqfunc() - confirmform.classList.remove('active') - }) - confirmformbuttons.replaceChild(cancelbutton, document.querySelector('#cancel')) -} diff --git a/src/web/js/settings/dns.js b/src/web/js/settings/dns.js index a202fab..26a0b34 100644 --- a/src/web/js/settings/dns.js +++ b/src/web/js/settings/dns.js @@ -2,9 +2,7 @@ const dnsenabled = document.querySelector('#dnsenable') const setdnssettingsbutton = document.querySelector('#set_dns_settings') const dnssettingbody = document.querySelector('#dnssettingsbody') const dnsserverstablebody = document.querySelector('#dnsserverstablebody') - const adddnsserverbutton = document.querySelector('#add_dns_server_button') - const localdomainstablebody = document.querySelector('#domainsstablebody') const addlocaldomainbutton = document.querySelector('#add_local_domain_button') @@ -12,46 +10,27 @@ let dnsservers = [] let localdomains = [] const get_dns_enabled_status = () => { - fetch(routes.dns_status(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(res => res.json()).then(jsondata => { - if (jsondata.enabled === '1') { - dnsenabled.checked = true - dnssettingbody.classList.add('active') - adddnsserverbutton.classList.add('active') - addlocaldomainbutton.classList.add('active') - return; - } else { - adddnsserverbutton.parentElement.classList.add('onebutton') - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_dns_enabled_status = () => { - fetch(routes.dns_status_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","enabled":${dnsenabled.checked}}` - }).then(res => res.json()).then(jsondata => { - if (jsondata.enabled === '1') { - dnsenabled.checked = true - dnssettingbody.classList.add('active') - adddnsserverbutton.classList.add('active') - } - notification("Настройки dns сохранены", "success") - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + dnsenabled.checked = settings.dns.enabled + if (settings.dns.enabled) { + dnssettingbody.classList.add('active') + adddnsserverbutton.classList.add('active') + addlocaldomainbutton.classList.add('active') + } else { + adddnsserverbutton.parentElement.classList.add('onebutton') + dnssettingbody.classList.remove('active') + adddnsserverbutton.classList.remove('active') + addlocaldomainbutton.classList.remove('active') + } } const set_dns_settings = () => { - set_dns_enabled_status() - if (dnsenabled.checked) { - set_dns_server() - set_local_domains() - } + settings.dns.enabled = dnsenabled.checked + settings.dns.servers = dnsservers + settings.dns.localdomains = localdomains + set_all_settings() + dnsenabled.checked = settings.dns.enabled + get_dns_server() + get_local_domains() } setdnssettingsbutton.addEventListener('click', () => { @@ -76,41 +55,16 @@ dnsenabled.addEventListener('click', () => { }) const get_dns_server = () => { - fetch(routes.dns_servers_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(res => res.json()).then(jsondata => { - dnsservers = jsondata.servers - dnsserverstablebody.innerHTML = "" - for (const server in jsondata.servers) { - dnsserverstablebody.innerHTML += ` - - ${jsondata.servers[server]} - - - ` - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_dns_server = () => { - start_loader("Load DNS") - fetch(routes.dns_servers_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}","servers":${JSON.stringify(dnsservers)}}` - }).then(data => data.json()).then(jsondata => { - stop_loader() - if (jsondata.success) { - get_dns_server() - notification("Настройки dns сохранены", "success") - } else { - notification("Неизвестная ошибка", "error") - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + dnsservers = settings.dns.servers + dnsserverstablebody.innerHTML = "" + for (const server in settings.dns.servers) { + dnsserverstablebody.innerHTML += ` + + ${settings.dns.servers[server]} + + + ` + } } adddnsserverbutton.addEventListener('click', () => { @@ -118,7 +72,7 @@ adddnsserverbutton.addEventListener('click', () => { if (dnsserver === null) return if (confirm('Confirm DNS settings? (add DNS server)')) { dnsservers.push(dnsserver) - set_dns_server() + set_dns_settings() } }) @@ -126,7 +80,7 @@ dnsserverstablebody.addEventListener('click', (e) => { if (e.target.classList.contains('deldnsserverbutton')) { dnsservers.splice(dnsservers.indexOf(e.target.id), 1) if (confirm(`Confirm DNS settings? (delete DNS server: ${e.target.id})`)) { - set_dns_server() + set_dns_settings() } get_dns_server() } @@ -135,42 +89,17 @@ dnsserverstablebody.addEventListener('click', (e) => { // ---------------------- local domains --------------------------- const get_local_domains = () => { - fetch(routes.dns_local_domains_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}"}` - }).then(data => data.json()).then(jsondata => { - localdomains = jsondata.domains - localdomainstablebody.innerHTML = "" - for (const domain in jsondata.domains) { - localdomainstablebody.innerHTML += ` - ${jsondata.domains[domain].ip} - - ${jsondata.domains[domain].domain} - - - ` - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_local_domains = (domains = localdomains) => { - start_loader() - fetch(routes.dns_local_domains_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}","domains":${JSON.stringify(domains)}}` - }).then(data => data.json()).then(jsondata => { - stop_loader() - if (jsondata.success) { - get_local_domains() - notification("Настройки dns сохранены", "success") - } else { - notification("Неизвестная ошибка", "error") - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + localdomains = settings.dns.localdomains + localdomainstablebody.innerHTML = "" + for (const domain in settings.dns.localdomains) { + localdomainstablebody.innerHTML += ` + ${settings.dns.localdomains[domain].ip} + + ${settings.dns.localdomains[domain].domain} + + + ` + } } addlocaldomainbutton.addEventListener('click', () => { @@ -180,7 +109,7 @@ addlocaldomainbutton.addEventListener('click', () => { if (domainip === null || domain === null) return if (confirm(`Confirm DNS settings? (Add Local Domain: ${domainip} ${domain})`)) { localdomains.push({ "ip": domainip, "domain": domain }) - set_local_domains() + set_dns_settings() } }) @@ -188,7 +117,7 @@ localdomainstablebody.addEventListener('click', (e) => { if (e.target.classList.contains('deldomain')) { localdomains.splice(localdomains.indexOf(localdomains.find(dom => dom.domain === e.target.id)), 1) if (confirm(`Confirm DNS settings? (delete Local Domain: ${e.target.id})`)) { - set_local_domains() + set_dns_settings() } get_local_domains() } diff --git a/src/web/js/settings/ftp.js b/src/web/js/settings/ftp.js index 61de91e..a732aca 100644 --- a/src/web/js/settings/ftp.js +++ b/src/web/js/settings/ftp.js @@ -2,33 +2,13 @@ const ftpenabled = document.querySelector('#ftpenable') const setftpsettingsbutton = document.querySelector('#set_ftp_settings') const get_ftp_enabled_status = () => { - fetch(routes.ftp_status(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(res => res.json()).then(jsondata => { - if (jsondata.enabled === '1') { - ftpenabled.checked = true - // ftpsettingbody.classList.add('active') - return; - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + ftpenabled.checked = settings.ftp.enabled } const set_ftp_enabled_status = () => { - fetch(routes.ftp_status_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","enabled":${ftpenabled.checked}}` - }).then(res => res.json()).then(jsondata => { - if (jsondata.enabled === '1') { - ftpenabled.checked = true - // ftpsettingbody.classList.add('active') - } - notification("Настройки ftp сохранены", "success") - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + settings.ftp.enabled = ftpenabled.checked + set_all_settings() + ftpenabled.checked = settings.ftp.enabled } setftpsettingsbutton.addEventListener('click', () => { diff --git a/src/web/js/settings/settings.js b/src/web/js/settings/settings.js index 9f1a4db..55d2f7f 100644 --- a/src/web/js/settings/settings.js +++ b/src/web/js/settings/settings.js @@ -1,20 +1,42 @@ -const get_all_settings = () => { - get_dns_enabled_status() - get_dns_server() - get_local_domains() +let settings = {} - get_ftp_enabled_status() - - get_telnet_enabled_status() - get_telnet_port() +const get_all_settings = async () => { + settings = await fetch(routes.settings_get(), { + method: 'POST', + body: `{"token":"${localStorage.getItem("token")}"}` + }).then(data => data.json()).then(jsondata => { + return jsondata.settings + }).catch((error) => { + notification(`Ошибка на сервере: ${error}`, "error") + }) get_ssh_status() get_ssh_keys() get_ssh_port() get_ssh_server() + get_telnet_enabled_status() + get_telnet_port() + + get_ftp_enabled_status() + + get_dns_enabled_status() + get_dns_server() + get_local_domains() + get_wifi_status() get_wifi_name() get_wifi_channel() - // get_wifi_password() +} + +const set_all_settings = async () => { + settings = await fetch(routes.settings_set(), { + method: 'POST', + body: `{"token":"${localStorage.getItem("token")}","settings":${JSON.stringify(settings)}}` + }).then(res => res.json()).then(jsondata => { + notification("Настройки сохранены", "success") + return jsondata.settings + }).catch((error) => { + notification(`Ошибка на сервере: ${error}`, "error") + }) } diff --git a/src/web/js/settings/ssh.js b/src/web/js/settings/ssh.js index fbb93cc..6b21be9 100644 --- a/src/web/js/settings/ssh.js +++ b/src/web/js/settings/ssh.js @@ -10,42 +10,38 @@ let sshkeys = [] // -------------------- SSH status -------------------------------- const get_ssh_status = () => { - fetch(routes.ssh_status_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(data => data.json()).then(jsondata => { - if (jsondata.status === '1') { - sshenable.checked = true - sshsettingsbody.classList.add('active') - add_ssh_key_button.classList.add('active') - add_ssh_key_button.parentElement.classList.remove('onebutton') - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + sshenable.checked = settings.ssh.enabled + if (settings.ssh.enabled) { + sshsettingsbody.classList.add('active') + add_ssh_key_button.classList.add('active') + add_ssh_key_button.parentElement.classList.remove('onebutton') + } else { + sshsettingsbody.classList.remove('active') + add_ssh_key_button.classList.remove('active') + add_ssh_key_button.parentElement.classList.add('onebutton') + } } -const set_ssh_status = () => { - fetch(routes.ssh_status_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","status":${sshenable.checked}}` - }).then(res => res.json()).then(jsondata => { - if (jsondata.status === '1') { - sshenable.checked = true - // telnetsettingbody.classList.add('active') +const set_ssh_settings_func = () => { + settings.ssh.enabled = sshenable.checked + settings.ssh.keys = sshkeys + settings.ssh.port = sshportinput.value + settings.ssh.server = sshserverinput.options[sshserverinput.selectedIndex].value + set_all_settings() + sshenable.checked = settings.ssh.enabled + get_ssh_keys() + sshportinput.value = settings.ssh.port + for (let i = 0; i < sshserverinput.options.length; i++) { + if (sshserverinput.options[i].value === settings.ssh.server) { + sshserverinput.selectedIndex = i; + break; } - notification("Настройки SSH сохранены", "success") - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + } } -// -------------------------------- end SSH status --------------------------------------- set_ssh_settings.addEventListener('click', () => { if (confirm("Confirm SSH settings?")) { - set_ssh_status() - set_ssh_port() - set_ssh_server() + set_ssh_settings_func() } }) @@ -63,41 +59,16 @@ sshenable.addEventListener('click', () => { // -------------------- SSH keys --------------------------------- const get_ssh_keys = () => { - fetch(routes.ssh_keys_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(data => data.json()).then(jsondata => { - sshkeys = jsondata.keys - sshkeystablebody.innerHTML = "" - for (const key in sshkeys) { - sshkeystablebody.innerHTML += ` + sshkeys = settings.ssh.keys + sshkeystablebody.innerHTML = "" + for (const key in sshkeys) { + sshkeystablebody.innerHTML += ` ${sshkeys[key]} ` - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_ssh_keys = () => { - start_loader() - fetch(routes.ssh_keys_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","keys":${JSON.stringify(sshkeys)}}` - }).then(data => data.json()).then(jsondata => { - stop_loader() - if (jsondata.success) { - get_ssh_keys() - notification("Настройки SSH сохранены", "success") - } else { - notification("Неизвестная ошибка", "error") - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + } } // ------------------- end SSH keys ------------------------------ @@ -105,7 +76,7 @@ sshkeystablebody.addEventListener('click', (e) => { if (e.target.classList.contains('delsshkeybutton')) { if (confirm(`Confirm SSH settings?: (delete SSH key)`)) { sshkeys.splice(e.target.id) - set_ssh_keys() + set_ssh_settings_func() } } }) @@ -115,63 +86,22 @@ add_ssh_key_button.addEventListener('click', () => { if (sshkey === null) return if (confirm('Confirm SSH settings? (add SSH key)')) { sshkeys.push(sshkey) - set_ssh_keys() + set_ssh_settings_func() } }) // -------------------- SSH port ---------------------- const get_ssh_port = () => { - fetch(routes.ssh_port_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(data => data.json()).then(jsondata => { - sshportinput.value = jsondata.port - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_ssh_port = () => { - fetch(routes.ssh_port_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","port":"${sshportinput.value}"}` - }).then(data => data.json()).then(jsondata => { - sshportinput.value = jsondata.port - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + sshportinput.value = settings.ssh.port } // ------------------- end SSH port ------------------- const get_ssh_server = () => { - fetch(routes.ssh_server_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(data => data.json()).then(jsondata => { - for (let i = 0; i < sshserverinput.options.length; i++) { - if (sshserverinput.options[i].value === jsondata.server) { - sshserverinput.selectedIndex = i; - break; - } + for (let i = 0; i < sshserverinput.options.length; i++) { + if (sshserverinput.options[i].value === settings.ssh.server) { + sshserverinput.selectedIndex = i; + break; } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_ssh_server = () => { - fetch(routes.ssh_server_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","server":"${sshserverinput.options[sshserverinput.selectedIndex].value}"}` - }).then(data => data.json()).then(jsondata => { - for (let i = 0; i < sshserverinput.options.length; i++) { - if (sshserverinput.options[i].value === jsondata.server) { - sshserverinput.selectedIndex = i; - break; - } - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + } } diff --git a/src/web/js/settings/telnet.js b/src/web/js/settings/telnet.js index 1bf8ff8..7ce24d8 100644 --- a/src/web/js/settings/telnet.js +++ b/src/web/js/settings/telnet.js @@ -1,61 +1,33 @@ const telnetenabled = document.querySelector('#telnetenable') const telnetsettingbody = document.querySelector('.telnetsettingbody') const settelnetsettingsbutton = document.querySelector('#set_telnet_settings') -// const addtelnetuserbutton = document.querySelector('#add_telnet_user_button') - const telnetportinput = document.querySelector('#telnetportinput') const get_telnet_enabled_status = () => { - fetch(routes.telnet_status(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(res => res.json()).then(jsondata => { - if (jsondata.enabled === '1') { - telnetenabled.checked = true - // addtelnetuserbutton.classList.add('active') - // addtelnetuserbutton.parentElement.classList.remove('onebutton') - telnetsettingbody.classList.add('active') - return; - } else { - // addtelnetuserbutton.parentElement.classList.add('onebutton') - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_telnet_enabled_status = () => { - fetch(routes.telnet_status_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","enabled":${telnetenabled.checked}}` - }).then(res => res.json()).then(jsondata => { - if (jsondata.enabled === '1') { - telnetenabled.checked = true - // telnetsettingbody.classList.add('active') - } - notification("Настройки telnet сохранены", "success") - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + telnetenabled.checked = settings.telnet.enabled + if (settings.telnet.enabled) { + telnetsettingbody.classList.add('active') + } else { + telnetsettingbody.classList.remove('active') + } } telnetenabled.addEventListener('click', () => { if (telnetenabled.checked) { - // addtelnetuserbutton.classList.add('active') - // addtelnetuserbutton.parentElement.classList.remove('onebutton') telnetsettingbody.classList.add('active') } else { telnetsettingbody.classList.remove('active') - // addtelnetuserbutton.classList.remove('active') - // addtelnetuserbutton.parentElement.classList.add('onebutton') } }) const set_telnet_settings = () => { - set_telnet_enabled_status() - if (telnetenabled.checked) { - set_telnet_port() - } + start_loader() + settings.telnet.enabled = telnetenabled.checked + settings.telnet.port = telnetportinput.value + set_all_settings() + telnetenabled.checked = settings.telnet.enabled + telnetportinput.value = settings.telnet.port + stop_loader() } settelnetsettingsbutton.addEventListener('click', () => { @@ -65,26 +37,5 @@ settelnetsettingsbutton.addEventListener('click', () => { }) const get_telnet_port = () => { - fetch(routes.telnet_port_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}"}` - }).then(data => data.json()).then(jsondata => { - telnetportinput.value = jsondata.port - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_telnet_port = () => { - start_loader() - fetch(routes.telnet_port_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem("token")}","port":"${telnetportinput.value}"}` - }).then(data => data.json()).then(jsondata => { - stop_loader() - telnetportinput.value = jsondata.port - }).catch((error) => { - stop_loader() - notification(`Ошибка на сервере: ${error}`, "error") - }) + telnetportinput.value = settings.telnet.port } diff --git a/src/web/js/settings/wifi.js b/src/web/js/settings/wifi.js index d916bc3..cf017e2 100644 --- a/src/web/js/settings/wifi.js +++ b/src/web/js/settings/wifi.js @@ -13,74 +13,17 @@ const channelselect = document.querySelector('#wifichannel') let old_wifi_name = "" const get_wifi_status = () => { - fetch(routes.wifi_status_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}"}` - }).then(data => data.json()).then(jsondata => { - if (jsondata.status == "1") { - wifistatus.checked = true - wifisettingsbody.classList.add('active') - } else { - wifistatus.checked = false - } - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_wifi_status = () => { - fetch(routes.wifi_status_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}","status":${wifistatus.checked}}` - }).then(data => data.json()).then(jsondata => { - if (jsondata.status === "1") { - wifistatus.checked = true - wifisettingsbody.classList.add('active') - notification("Настройки WIFI оспешно сорранены", "success") - return - } - wifistatus.checked = false + wifistatus.checked = settings.wifi.enabled + if (settings.wifi.enabled) { + wifisettingsbody.classList.add('active') + } else { wifisettingsbody.classList.remove('active') - notification("Настройки WIFI оспешно сорранены", "success") - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + } } const get_wifi_name = () => { - fetch(routes.wifi_name_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}"}` - }).then(data => data.json()).then(jsondata => { - wifissid.value = jsondata.name - old_wifi_name = jsondata.name - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const set_wifi_name = () => { - fetch(routes.wifi_name_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}","name":"${wifissid.value}"}` - }).then(data => data.json()).then(jsondata => { - wifissid.value = jsondata.name - old_wifi_name = jsondata.name - notification("Имя WIFI оспешно сорранено", "success") - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -} - -const get_wifi_password = () => { - fetch(routes.wifi_password_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}"}` - }).then(data => data.json()).then(jsondata => { - wifipassword.value = jsondata.password - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + wifissid.value = settings.wifi.name + old_wifi_name = settings.wifi.name } const set_wifi_password = () => { @@ -103,15 +46,19 @@ const set_wifi_password = () => { const set_wifi_settings = () => { if (confirm("Confirm wifi settings?")) { start_loader() - set_wifi_status() - if (wifistatus.checked) { - set_wifi_channel() - if (old_wifi_name !== wifissid.value) { - set_wifi_name() - } - if (wifipassword.value !== "" && newwifipassword.value !== "") { - set_wifi_password() - } + settings.wifi.enabled = wifistatus.checked + settings.wifi.name = wifissid.value + settings.wifi.channel = channelselect.value + set_all_settings() + wifistatus.checked = settings.wifi.enabled + if (settings.wifi.enabled) { + wifisettingsbody.classList.add('active') + } + wifissid.value = settings.wifi.name + old_wifi_name = settings.wifi.name + channelselect.value = settings.wifi.channel + if (wifipassword.value !== "" && newwifipassword.value !== "") { + set_wifi_password() } stop_loader() } @@ -166,23 +113,5 @@ wifiqr.addEventListener('click', () => { }) const get_wifi_channel = () => { - fetch(routes.wifi_channel_get(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}"}` - }).then(data => data.json()).then(jsondata => { - channelselect.value = jsondata.channel - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) + channelselect.value = settings.wifi.channel } - -const set_wifi_channel = () => { - fetch(routes.wifi_channel_set(), { - method: 'POST', - body: `{"token":"${localStorage.getItem('token')}","channel":"${channelselect.value}"}` - }).then(data => data.json()).then(jsondata => { - channelselect.value = jsondata.channel - }).catch((error) => { - notification(`Ошибка на сервере: ${error}`, "error") - }) -}