diff --git a/data/assets/favicon.ico b/data/assets/favicon.ico new file mode 100644 index 0000000..90e1da4 Binary files /dev/null and b/data/assets/favicon.ico differ diff --git a/data/assets/index.html b/data/assets/index.html index 185f06c..7162632 100644 --- a/data/assets/index.html +++ b/data/assets/index.html @@ -31,6 +31,19 @@ config.set({ appName: 'ezRemote Client', listUrl: '/__local__/list', + uploadUrl: '/__local__/upload', + renameUrl: '/__local__/rename', + copyUrl: '/__local__/copy', + moveUrl: '/__local__/move', + removeUrl: '/__local__/remove', + editUrl: '/__local__/edit', + getContentUrl: '/__local__/getContent', + createFolderUrl: '/__local__/createFolder', + downloadFileUrl: '/__local__/downloadFile', + downloadMultipleUrl: '/__local__/downloadMultiple', + compressUrl: '/__local__/compress', + extractUrl: '/__local__/extract', + permissionsUrl: '/__local__/permission', pickCallback: function(item) { var msg = 'Picked %s "%s" for external use' .replace('%s', item.type) diff --git a/source/fs.cpp b/source/fs.cpp index 10ae16d..9925f43 100644 --- a/source/fs.cpp +++ b/source/fs.cpp @@ -77,6 +77,15 @@ namespace FS return (stat(path.c_str(), &dir_stat) == 0); } + int IsFolder(const std::string &path) + { + struct stat dir_stat = {0}; + if (stat(path.c_str(), &dir_stat) != 0) + return -1; + if (S_ISDIR(dir_stat.st_mode)) + return 1; + return 0; + } void Rename(const std::string &from, const std::string &to) { int res = rename(from.c_str(), to.c_str()); @@ -121,7 +130,7 @@ namespace FS int Write(FILE *f, const void *buffer, uint32_t size) { - int write = fwrite(buffer, size, 1, f); + int write = fwrite(buffer, 1, size, f); return write; } @@ -136,14 +145,17 @@ namespace FS if (fd == nullptr) return std::vector(0); const auto size = GetSize(path); + dbglogger_log("size=%lld", size); std::vector data(size); const auto read = fread(data.data(), 1, data.size(), fd); + dbglogger_log("read=%lld", size); fclose(fd); if (read < 0) return std::vector(0); - data.resize(read); + data.resize(read+1); + data[data.size()-1]=0; return data; } @@ -209,22 +221,23 @@ namespace FS return true; } - void Save(const std::string &path, const void *data, uint32_t size) + bool Save(const std::string &path, const void *data, uint32_t size) { FILE *fd = fopen(path.c_str(), "w+"); if (fd == nullptr) - return; + return false; const char *data8 = static_cast(data); while (size != 0) { - int written = fwrite(data8, size, 1, fd); + int written = fwrite(data8, 1, size, fd); fclose(fd); if (written <= 0) - return; + return false; data8 += written; size -= written; } + return true; } std::vector ListDir(const std::string &ppath, int *err) @@ -304,9 +317,7 @@ namespace FS entry.modified.seconds = lt.second; entry.file_size = file_stat.st_size; - dbglogger_log("%04d-%02d-%02d %02d:%02d:%02d", lt.year, lt.month, lt.day, lt.hour, lt.minute, lt.second); sprintf(entry.display_date, "%04d-%02d-%02d %02d:%02d:%02d", lt.year, lt.month, lt.day, lt.hour, lt.minute, lt.second); - dbglogger_log("display_date=%s", entry.display_date); if (dirent->d_type & DT_DIR) { @@ -478,6 +489,9 @@ namespace FS bool Copy(const std::string &from, const std::string &to) { MkDirs(to, true); + if (from.compare(to) == 0) + return true; + FILE *src = fopen(from.c_str(), "rb"); if (!src) { @@ -535,6 +549,9 @@ namespace FS bool Move(const std::string &from, const std::string &to) { + if (from.compare(to) == 0) + return true; + bool res = Copy(from, to); if (res) Rm(from); diff --git a/source/fs.h b/source/fs.h index 04649bb..bac39e0 100644 --- a/source/fs.h +++ b/source/fs.h @@ -25,6 +25,7 @@ namespace FS bool FileExists(const std::string &path); bool FolderExists(const std::string &path); + int IsFolder(const std::string &path); void Rename(const std::string &from, const std::string &to); @@ -54,7 +55,7 @@ namespace FS bool LoadText(std::vector *lines, const std::string &path); bool SaveText(std::vector *lines, const std::string &path); - void Save(const std::string &path, const void *data, uint32_t size); + bool Save(const std::string &path, const void *data, uint32_t size); std::vector ListFiles(const std::string &path); std::vector ListDir(const std::string &path, int *err); diff --git a/source/http/httplib.cpp b/source/http/httplib.cpp index 5e9fb0e..ed1f2ab 100644 --- a/source/http/httplib.cpp +++ b/source/http/httplib.cpp @@ -1,4 +1,5 @@ #include "httplib.h" +#include "dbglogger.h" namespace httplib { /* @@ -28,7 +29,7 @@ bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, val = 0; for (; cnt; i++, cnt--) { if (!s[i]) { return false; } - int v = 0; + auto v = 0; if (is_hex(s[i], v)) { val = val * 16 + v; } else { @@ -39,7 +40,7 @@ bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, } std::string from_i_to_hex(size_t n) { - const char *charset = "0123456789abcdef"; + static const auto charset = "0123456789abcdef"; std::string ret; do { ret = charset[n & 15] + ret; @@ -89,8 +90,8 @@ std::string base64_encode(const std::string &in) { std::string out; out.reserve(in.size()); - int val = 0; - int valb = -6; + auto val = 0; + auto valb = -6; for (auto c : in) { val = (val << 8) + static_cast(c); @@ -221,7 +222,7 @@ std::string decode_url(const std::string &s, for (size_t i = 0; i < s.size(); i++) { if (s[i] == '%' && i + 1 < s.size()) { if (s[i + 1] == 'u') { - int val = 0; + auto val = 0; if (from_hex_to_i(s, i + 2, 4, val)) { // 4 digits Unicode codes char buff[4]; @@ -232,7 +233,7 @@ std::string decode_url(const std::string &s, result += s[i]; } } else { - int val = 0; + auto val = 0; if (from_hex_to_i(s, i + 1, 2, val)) { // 2 digits hex codes result += static_cast(val); @@ -379,7 +380,7 @@ int close_socket(socket_t sock) { } template ssize_t handle_EINTR(T fn) { - ssize_t res = false; + ssize_t res = 0; while (true) { res = fn(); if (res < 0 && errno == EINTR) { continue; } @@ -483,7 +484,7 @@ Error wait_until_socket_is_ready(socket_t sock, time_t sec, if (poll_res == 0) { return Error::ConnectionTimeout; } if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { - int error = 0; + auto error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); @@ -515,7 +516,7 @@ Error wait_until_socket_is_ready(socket_t sock, time_t sec, if (ret == 0) { return Error::ConnectionTimeout; } if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { - int error = 0; + auto error = 0; socklen_t len = sizeof(error); auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, reinterpret_cast(&error), &len); @@ -695,7 +696,7 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); if (sock != INVALID_SOCKET) { - sockaddr_un addr; + sockaddr_un addr{}; addr.sun_family = AF_UNIX; std::copy(host.begin(), host.end(), addr.sun_path); @@ -753,21 +754,34 @@ socket_t create_socket(const std::string &host, const std::string &ip, int port, if (sock == INVALID_SOCKET) { continue; } #ifndef _WIN32 - if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { continue; } + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } #endif if (tcp_nodelay) { - int yes = 1; - setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&yes), - sizeof(yes)); + 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 (socket_options) { socket_options(sock); } if (rp->ai_family == AF_INET6) { - int no = 0; - setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&no), - sizeof(no)); + 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 @@ -826,7 +840,7 @@ bool bind_ip_address(socket_t sock, const std::string &host) { return ret; } -#if !defined _WIN32 && !defined ANDROID && !defined _AIX +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ #define USE_IF2IP #endif @@ -910,13 +924,14 @@ socket_t create_client_socket( #ifdef _WIN32 auto timeout = static_cast(read_timeout_sec * 1000 + read_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, - sizeof(timeout)); + 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, (char *)&tv, sizeof(tv)); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); #endif } { @@ -924,18 +939,19 @@ socket_t create_client_socket( #ifdef _WIN32 auto timeout = static_cast(write_timeout_sec * 1000 + write_timeout_usec / 1000); - setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, - sizeof(timeout)); + 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, (char *)&tv, sizeof(tv)); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); #endif - int const size = 1048576; - setsockopt(sock2, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); - setsockopt(sock2, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); + //int const size = 1048576; + //setsockopt(sock2, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); + //setsockopt(sock2, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); } error = Error::Success; @@ -1012,9 +1028,14 @@ void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { constexpr unsigned int str2tag_core(const char *s, size_t l, unsigned int h) { - return (l == 0) ? h - : str2tag_core(s + 1, l - 1, - (h * 33) ^ static_cast(*s)); + return (l == 0) + ? h + : str2tag_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(*s)); } unsigned int str2tag(const std::string &s) { @@ -1239,7 +1260,7 @@ bool gzip_compressor::compress(const char *data, size_t data_length, data += strm_.avail_in; auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; - int ret = Z_OK; + auto ret = Z_OK; std::array buff{}; do { @@ -1283,7 +1304,7 @@ bool gzip_decompressor::decompress(const char *data, size_t data_length, Callback callback) { assert(is_valid_); - int ret = Z_OK; + auto ret = Z_OK; do { constexpr size_t max_avail_in = @@ -1297,16 +1318,12 @@ bool gzip_decompressor::decompress(const char *data, size_t data_length, data += strm_.avail_in; std::array buff{}; - while (strm_.avail_in > 0) { + while (strm_.avail_in > 0 && ret == Z_OK) { strm_.avail_out = static_cast(buff.size()); strm_.next_out = reinterpret_cast(buff.data()); - auto prev_avail_in = strm_.avail_in; - ret = inflate(&strm_, Z_NO_FLUSH); - if (prev_avail_in - strm_.avail_in == 0) { return false; } - assert(ret != Z_STREAM_ERROR); switch (ret) { case Z_NEED_DICT: @@ -1388,7 +1405,7 @@ bool brotli_decompressor::decompress(const char *data, return 0; } - const uint8_t *next_in = (const uint8_t *)data; + auto next_in = reinterpret_cast(data); size_t avail_in = data_length; size_t total_out; @@ -1427,6 +1444,14 @@ const char *get_header_value(const Headers &headers, return def; } +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 bool parse_header(const char *beg, const char *end, T fn) { // Skip trailing spaces and tabs. @@ -1450,7 +1475,11 @@ bool parse_header(const char *beg, const char *end, T fn) { } if (p < end) { - fn(std::string(beg, key_end), decode_url(std::string(p, end), 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(std::move(key), std::move(val)); return true; } @@ -1548,7 +1577,8 @@ bool read_content_without_length(Stream &strm, return true; } -bool read_content_chunked(Stream &strm, +template +bool read_content_chunked(Stream &strm, T &x, ContentReceiverWithProgress out) { const auto bufsiz = 16; char buf[bufsiz]; @@ -1574,15 +1604,29 @@ bool read_content_chunked(Stream &strm, if (!line_reader.getline()) { return false; } - if (strcmp(line_reader.ptr(), "\r\n")) { break; } + if (strcmp(line_reader.ptr(), "\r\n")) { return false; } if (!line_reader.getline()) { return false; } } - if (chunk_len == 0) { - // Reader terminator after chunks - if (!line_reader.getline() || strcmp(line_reader.ptr(), "\r\n")) - return false; + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n")) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } } return true; @@ -1652,16 +1696,20 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, auto exceed_payload_max_length = false; if (is_chunked_transfer_encoding(x.headers)) { - ret = read_content_chunked(strm, out); + dbglogger_log("read_content_chunked"); + ret = read_content_chunked(strm, x, out); } else if (!has_header(x.headers, "Content-Length")) { + dbglogger_log("read_content_without_length"); ret = read_content_without_length(strm, out); } else { + dbglogger_log("skip_content_with_length"); auto len = get_header_value(x.headers, "Content-Length"); if (len > payload_max_length) { exceed_payload_max_length = true; skip_content_with_length(strm, len); ret = false; } else if (len > 0) { + dbglogger_log("read_content_with_length %llu", len); ret = read_content_with_length(strm, len, std::move(progress), out); } } @@ -1808,7 +1856,7 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, return ok; }; - data_sink.done = [&](void) { + auto done_with_trailer = [&](const Headers *trailer) { if (!ok) { return; } data_available = false; @@ -1826,16 +1874,36 @@ write_content_chunked(Stream &strm, const ContentProvider &content_provider, if (!payload.empty()) { // Emit chunked response header and footer for each chunk auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; - if (!write_data(strm, chunk.data(), chunk.size())) { + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { ok = false; return; } } - static const std::string done_marker("0\r\n\r\n"); + static const std::string done_marker("0\r\n"); if (!write_data(strm, done_marker.data(), done_marker.size())) { ok = false; } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); }; while (data_available && !is_shutting_down()) { @@ -1884,7 +1952,8 @@ bool redirect(T &cli, Request &req, Response &res, if (ret) { req = new_req; res = new_res; - res.location = location; + + if (res.location.empty()) res.location = location; } return ret; } @@ -1926,9 +1995,12 @@ void parse_query_text(const std::string &s, Params ¶ms) { bool parse_multipart_boundary(const std::string &content_type, std::string &boundary) { - auto pos = content_type.find("boundary="); + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); if (pos == std::string::npos) { return false; } - boundary = content_type.substr(pos + 9); + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = content_type.substr(beg, end - beg); if (boundary.length() >= 2 && boundary.front() == '"' && boundary.back() == '"') { boundary = boundary.substr(1, boundary.size() - 2); @@ -2442,7 +2514,7 @@ std::string message_digest(const std::string &s, const EVP_MD *algo) { std::stringstream ss; for (auto i = 0u; i < hash_length; ++i) { ss << std::hex << std::setw(2) << std::setfill('0') - << (unsigned int)hash[i]; + << static_cast(hash[i]); } return ss.str(); @@ -2461,15 +2533,15 @@ std::string SHA_512(const std::string &s) { } #endif -#ifdef _WIN32 #ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#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 bool load_system_certs_on_windows(X509_STORE *store) { auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); - if (!hStore) { return false; } + auto result = false; PCCERT_CONTEXT pContext = NULL; while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != nullptr) { @@ -2480,16 +2552,109 @@ bool load_system_certs_on_windows(X509_STORE *store) { if (x509) { X509_STORE_add_cert(store, x509); X509_free(x509); + result = true; } } CertFreeCertificateContext(pContext); CertCloseStore(hStore, 0); + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); return true; } -#endif +bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (auto i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 class WSInit { public: WSInit() { @@ -2660,7 +2825,7 @@ void hosted_at(const std::string &hostname, const auto &addr = *reinterpret_cast(rp->ai_addr); std::string ip; - int dummy = -1; + auto dummy = -1; if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, dummy)) { addrs.push_back(ip); @@ -2764,13 +2929,14 @@ MultipartFormData Request::get_file_value(const std::string &key) const { return MultipartFormData(); } -std::vector Request::get_file_values(const std::string &key) const { - std::vector values; - auto rng = files.equal_range(key); - for (auto it = rng.first; it != rng.second; it++) { - values.push_back(it->second); - } - return values; +std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; } // Response implementation @@ -2823,10 +2989,9 @@ void Response::set_content(const std::string &s, void Response::set_content_provider( size_t in_length, const std::string &content_type, ContentProvider provider, ContentProviderResourceReleaser resource_releaser) { - assert(in_length > 0); set_header("Content-Type", content_type); content_length_ = in_length; - content_provider_ = std::move(provider); + if (in_length > 0) { content_provider_ = std::move(provider); } content_provider_resource_releaser_ = resource_releaser; is_chunked_content_provider_ = false; } @@ -2998,13 +3163,105 @@ socket_t BufferStream::socket() const { return 0; } const std::string &BufferStream::get_buffer() const { return buffer; } +PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) { + const auto marker_pos = pattern.find(marker, last_param_end); + if (marker_pos == std::string::npos) { break; } + + static_fragments_.push_back( + pattern.substr(last_param_end, marker_pos - last_param_end)); + + const auto param_name_start = marker_pos + 1; + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } + + auto param_name = + pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) { + std::string msg = "Encountered path parameter '" + param_name + + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) { + static_fragments_.push_back(pattern.substr(last_param_end)); + } +} + +bool PathParamsMatcher::match(Request &request) const { + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) { + const auto &fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), + fragment.length()) != 0) { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { continue; } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { sep_pos = request.path.length(); } + + const auto ¶m_name = param_names_[i]; + + request.path_params.emplace( + param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everythin up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); +} + +bool RegexMatcher::match(Request &request) const { + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); +} + } // namespace detail // HTTP server implementation Server::Server() : new_task_queue( - [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }), - svr_sock_(INVALID_SOCKET), is_running_(false) { + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { #ifndef _WIN32 signal(SIGPIPE, SIG_IGN); #endif @@ -3012,67 +3269,76 @@ Server::Server() Server::~Server() {} +std::unique_ptr +Server::make_matcher(const std::string &pattern) { + if (pattern.find("/:") != std::string::npos) { + return detail::make_unique(pattern); + } else { + return detail::make_unique(pattern); + } +} + Server &Server::Get(const std::string &pattern, Handler handler) { get_handlers_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Post(const std::string &pattern, Handler handler) { post_handlers_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Post(const std::string &pattern, HandlerWithContentReader handler) { post_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Put(const std::string &pattern, Handler handler) { put_handlers_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Put(const std::string &pattern, HandlerWithContentReader handler) { put_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Patch(const std::string &pattern, Handler handler) { patch_handlers_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Patch(const std::string &pattern, HandlerWithContentReader handler) { patch_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Delete(const std::string &pattern, Handler handler) { delete_handlers_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Delete(const std::string &pattern, HandlerWithContentReader handler) { delete_handlers_for_content_reader_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } Server &Server::Options(const std::string &pattern, Handler handler) { options_handlers_.push_back( - std::make_pair(std::regex(pattern), std::move(handler))); + std::make_pair(make_matcher(pattern), std::move(handler))); return *this; } @@ -3151,7 +3417,6 @@ Server &Server::set_logger(Logger logger) { Server & Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { expect_100_continue_handler_ = std::move(handler); - return *this; } @@ -3217,15 +3482,25 @@ int Server::bind_to_any_port(const std::string &host, int socket_flags) { return bind_internal(host, 0, socket_flags); } -bool Server::listen_after_bind() { return listen_internal(); } +bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} 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(); } bool Server::is_running() const { return is_running_; } +void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + void Server::stop() { if (is_running_) { assert(svr_sock_ != INVALID_SOCKET); @@ -3488,7 +3763,7 @@ bool Server::read_content_with_content_receiver( bool Server::read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader mulitpart_header, + MultipartContentHeader multipart_header, ContentReceiver multipart_receiver) { detail::MultipartFormDataParser multipart_form_data_parser; ContentReceiverWithProgress out; @@ -3508,14 +3783,14 @@ bool Server::read_content_core(Stream &strm, Request &req, Response &res, while (pos < n) { auto read_size = (std::min)(1, n - pos); auto ret = multipart_form_data_parser.parse( - buf + pos, read_size, multipart_receiver, mulitpart_header); + buf + pos, read_size, multipart_receiver, multipart_header); if (!ret) { return false; } pos += read_size; } return true; */ return multipart_form_data_parser.parse(buf, n, multipart_receiver, - mulitpart_header); + multipart_header); }; } else { out = [receiver](const char *buf, size_t n, uint64_t /*off*/, @@ -3616,6 +3891,7 @@ int Server::bind_internal(const std::string &host, int port, bool Server::listen_internal() { auto ret = true; is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); { std::unique_ptr task_queue(new_task_queue()); @@ -3657,13 +3933,14 @@ bool Server::listen_internal() { #ifdef _WIN32 auto timeout = static_cast(read_timeout_sec_ * 1000 + read_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, - sizeof(timeout)); + 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, (char *)&tv, sizeof(tv)); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); #endif } { @@ -3671,13 +3948,14 @@ bool Server::listen_internal() { #ifdef _WIN32 auto timeout = static_cast(write_timeout_sec_ * 1000 + write_timeout_usec_ / 1000); - setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, - sizeof(timeout)); + 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, (char *)&tv, sizeof(tv)); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); #endif } @@ -3687,7 +3965,6 @@ bool Server::listen_internal() { task_queue->shutdown(); } - is_running_ = false; return ret; } @@ -3698,7 +3975,7 @@ bool Server::routing(Request &req, Response &res, Stream &strm) { } // File handler - bool is_head_request = req.method == "HEAD"; + auto is_head_request = req.method == "HEAD"; if ((req.method == "GET" || is_head_request) && handle_file_request(req, res, is_head_request)) { return true; @@ -3771,10 +4048,10 @@ bool Server::routing(Request &req, Response &res, Stream &strm) { bool Server::dispatch_request(Request &req, Response &res, const Handlers &handlers) { for (const auto &x : handlers) { - const auto &pattern = x.first; + const auto &matcher = x.first; const auto &handler = x.second; - if (std::regex_match(req.path, req.matches, pattern)) { + if (matcher->match(req)) { handler(req, res); return true; } @@ -3794,8 +4071,8 @@ void Server::apply_ranges(const Request &req, Response &res, res.headers.erase(it); } - res.headers.emplace("Content-Type", - "multipart/byteranges; boundary=" + boundary); + res.set_header("Content-Type", + "multipart/byteranges; boundary=" + boundary); } auto type = detail::encoding_type(req, res); @@ -3896,10 +4173,10 @@ bool Server::dispatch_request_for_content_reader( Request &req, Response &res, ContentReader content_reader, const HandlersForContentReader &handlers) { for (const auto &x : handlers) { - const auto &pattern = x.first; + const auto &matcher = x.first; const auto &handler = x.second; - if (std::regex_match(req.path, req.matches, pattern)) { + if (matcher->match(req)) { handler(req, res, content_reader); return true; } @@ -3919,15 +4196,10 @@ Server::process_request(Stream &strm, bool close_connection, if (!line_reader.getline()) { return false; } Request req; + Response res; - res.version = "HTTP/1.1"; - - for (const auto &header : default_headers_) { - if (res.headers.find(header.first) == res.headers.end()) { - res.headers.insert(header); - } - } + res.headers = default_headers_; #ifdef _WIN32 // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). @@ -4228,7 +4500,15 @@ bool ClientImpl::read_response_line(Stream &strm, const Request &req, bool ClientImpl::send(Request &req, Response &res, Error &error) { std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} +bool ClientImpl::send_(Request &req, Response &res, Error &error) { { std::lock_guard guard(socket_mutex_); @@ -4259,7 +4539,7 @@ bool ClientImpl::send(Request &req, Response &res, Error &error) { if (is_ssl()) { auto &scli = static_cast(*this); if (!proxy_host_.empty() && proxy_port_ != -1) { - bool success = false; + auto success = false; if (!scli.connect_with_proxy(socket_, res, success, error)) { return success; } @@ -4286,13 +4566,11 @@ bool ClientImpl::send(Request &req, Response &res, Error &error) { } } + auto ret = false; auto close_connection = !keep_alive_; - auto ret = process_socket(socket_, [&](Stream &strm) { - return handle_request(strm, req, res, close_connection, error); - }); - // Briefly lock mutex in order to mark that a request is no longer ongoing - { + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing std::lock_guard guard(socket_mutex_); socket_requests_in_flight_ -= 1; if (socket_requests_in_flight_ <= 0) { @@ -4306,7 +4584,11 @@ bool ClientImpl::send(Request &req, Response &res, Error &error) { shutdown_socket(socket_); close_socket(socket_); } - } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); if (!ret) { if (error == Error::Success) { error = Error::Unknown; } @@ -4394,11 +4676,11 @@ bool ClientImpl::redirect(Request &req, Response &res, Error &error) { return false; } - auto location = detail::decode_url(res.get_header_value("location"), true); + auto location = res.get_header_value("location"); if (location.empty()) { return false; } const static std::regex re( - R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*(?:\?[^#]*)?)(?:#.*)?)"); + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); std::smatch m; if (!std::regex_match(location, m, re)) { return false; } @@ -4410,6 +4692,7 @@ bool ClientImpl::redirect(Request &req, Response &res, Error &error) { if (next_host.empty()) { next_host = m[3].str(); } auto port_str = m[4].str(); auto next_path = m[5].str(); + auto next_query = m[6].str(); auto next_port = port_; if (!port_str.empty()) { @@ -4422,22 +4705,24 @@ bool ClientImpl::redirect(Request &req, Response &res, Error &error) { if (next_host.empty()) { next_host = host_; } if (next_path.empty()) { next_path = "/"; } + auto path = detail::decode_url(next_path, true) + next_query; + if (next_scheme == scheme && next_host == host_ && next_port == port_) { - return detail::redirect(*this, req, res, next_path, location, error); + return detail::redirect(*this, req, res, path, location, error); } else { if (next_scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT SSLClient cli(next_host.c_str(), next_port); cli.copy_settings(*this); if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } - return detail::redirect(cli, req, res, next_path, location, error); + return detail::redirect(cli, req, res, path, location, error); #else return false; #endif } else { ClientImpl cli(next_host.c_str(), next_port); cli.copy_settings(*this); - return detail::redirect(cli, req, res, next_path, location, error); + return detail::redirect(cli, req, res, path, location, error); } } } @@ -4448,7 +4733,7 @@ bool ClientImpl::write_content_with_provider(Stream &strm, auto is_shutting_down = []() { return false; }; if (req.is_chunked_content_provider_) { - // TODO: Brotli suport + // TODO: Brotli support std::unique_ptr compressor; #ifdef CPPHTTPLIB_ZLIB_SUPPORT if (compress_) { @@ -4472,32 +4757,32 @@ bool ClientImpl::write_request(Stream &strm, Request &req, // Prepare additional headers if (close_connection) { if (!req.has_header("Connection")) { - req.headers.emplace("Connection", "close"); + req.set_header("Connection", "close"); } } if (!req.has_header("Host")) { if (is_ssl()) { if (port_ == 443) { - req.headers.emplace("Host", host_); + req.set_header("Host", host_); } else { - req.headers.emplace("Host", host_and_port_); + req.set_header("Host", host_and_port_); } } else { if (port_ == 80) { - req.headers.emplace("Host", host_); + req.set_header("Host", host_); } else { - req.headers.emplace("Host", host_and_port_); + req.set_header("Host", host_and_port_); } } } - if (!req.has_header("Accept")) { req.headers.emplace("Accept", "*/*"); } + 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.headers.emplace("User-Agent", agent); + req.set_header("User-Agent", agent); } #endif @@ -4506,23 +4791,23 @@ bool ClientImpl::write_request(Stream &strm, Request &req, if (!req.is_chunked_content_provider_) { if (!req.has_header("Content-Length")) { auto length = std::to_string(req.content_length_); - req.headers.emplace("Content-Length", length); + req.set_header("Content-Length", length); } } } else { if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") { - req.headers.emplace("Content-Length", "0"); + req.set_header("Content-Length", "0"); } } } else { if (!req.has_header("Content-Type")) { - req.headers.emplace("Content-Type", "text/plain"); + req.set_header("Content-Type", "text/plain"); } if (!req.has_header("Content-Length")) { auto length = std::to_string(req.body.size()); - req.headers.emplace("Content-Length", length); + req.set_header("Content-Length", length); } } @@ -4590,12 +4875,10 @@ std::unique_ptr ClientImpl::send_with_content_provider( ContentProvider content_provider, ContentProviderWithoutLength content_provider_without_length, const std::string &content_type, Error &error) { - if (!content_type.empty()) { - req.headers.emplace("Content-Type", content_type); - } + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } #ifdef CPPHTTPLIB_ZLIB_SUPPORT - if (compress_) { req.headers.emplace("Content-Encoding", "gzip"); } + if (compress_) { req.set_header("Content-Encoding", "gzip"); } #endif #ifdef CPPHTTPLIB_ZLIB_SUPPORT @@ -4656,10 +4939,9 @@ std::unique_ptr ClientImpl::send_with_content_provider( req.content_provider_ = detail::ContentProviderAdapter( std::move(content_provider_without_length)); req.is_chunked_content_provider_ = true; - req.headers.emplace("Transfer-Encoding", "chunked"); + req.set_header("Transfer-Encoding", "chunked"); } else { req.body.assign(body, content_length); - ; } } @@ -4698,6 +4980,20 @@ bool ClientImpl::process_request(Stream &strm, Request &req, // Send request if (!write_request(strm, req, close_connection, error)) { return false; } +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + 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) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + // Receive response and headers if (!read_response_line(strm, req, res) || !detail::read_headers(strm, res.headers)) { @@ -5265,9 +5561,7 @@ Result ClientImpl::Delete(const std::string &path, req.headers = headers; req.path = path; - if (!content_type.empty()) { - req.headers.emplace("Content-Type", content_type); - } + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } req.body.assign(body, content_length); return send_(std::move(req)); @@ -5300,13 +5594,6 @@ Result ClientImpl::Options(const std::string &path, return send_(std::move(req)); } -size_t ClientImpl::is_socket_open() const { - std::lock_guard guard(socket_mutex_); - return socket_.is_open(); -} - -socket_t ClientImpl::socket() const { return socket_.sock; } - void ClientImpl::stop() { std::lock_guard guard(socket_mutex_); @@ -5324,12 +5611,23 @@ void ClientImpl::stop() { return; } - // Otherwise, sitll holding the mutex, we can shut everything down ourselves + // Otherwise, still holding the mutex, we can shut everything down ourselves shutdown_ssl(socket_, true); shutdown_socket(socket_); close_socket(socket_); } +std::string ClientImpl::host() const { return host_; } + +int ClientImpl::port() const { return port_; } + +size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +socket_t ClientImpl::socket() const { return socket_.sock; } + void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { connection_timeout_sec_ = sec; connection_timeout_usec_ = usec; @@ -5417,9 +5715,7 @@ void ClientImpl::set_proxy_digest_auth(const std::string &username, proxy_digest_auth_username_ = username; proxy_digest_auth_password_ = password; } -#endif -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path) { ca_cert_file_path_ = ca_cert_file_path; @@ -5431,9 +5727,34 @@ void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { ca_cert_store_ = ca_cert_store; } } -#endif -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert, + std::size_t size) { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + if (!mem) return nullptr; + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { + BIO_free_all(mem); + return nullptr; + } + + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + BIO_free_all(mem); + return cts; +} + void ClientImpl::enable_server_certificate_verification(bool enabled) { server_certificate_verification_ = enabled; } @@ -5497,7 +5818,7 @@ bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, U ssl_connect_or_accept, time_t timeout_sec, time_t timeout_usec) { - int res = 0; + auto res = 0; while ((res = ssl_connect_or_accept(ssl)) != 1) { auto err = SSL_get_error(ssl, res); switch (err) { @@ -5578,7 +5899,7 @@ ssize_t SSLSocketStream::read(char *ptr, size_t size) { auto ret = SSL_read(ssl_, ptr, static_cast(size)); if (ret < 0) { auto err = SSL_get_error(ssl_, ret); - int n = 1000; + auto n = 1000; #ifdef _WIN32 while (--n >= 0 && (err == SSL_ERROR_WANT_READ || (err == SSL_ERROR_SYSCALL && @@ -5611,7 +5932,7 @@ ssize_t SSLSocketStream::write(const char *ptr, size_t size) { auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); if (ret < 0) { auto err = SSL_get_error(ssl_, ret); - int n = 1000; + auto n = 1000; #ifdef _WIN32 while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || (err == SSL_ERROR_SYSCALL && @@ -5666,8 +5987,9 @@ SSLServer::SSLServer(const char *cert_path, const char *private_key_path, // add default password callback before opening encrypted private key if (private_key_password != nullptr && (private_key_password[0] != '\0')) { - SSL_CTX_set_default_passwd_cb_userdata(ctx_, - (char *)private_key_password); + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, + reinterpret_cast(const_cast(private_key_password))); } if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || @@ -5737,7 +6059,7 @@ bool SSLServer::process_and_close_socket(socket_t sock) { }, [](SSL * /*ssl2*/) { return true; }); - bool ret = false; + auto ret = false; if (ssl) { ret = detail::process_server_socket_ssl( svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, @@ -5831,6 +6153,11 @@ void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { } } +void SSLClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size)); +} + long SSLClient::get_openssl_verify_result() const { return verify_result_; } @@ -5915,11 +6242,16 @@ bool SSLClient::load_certs() { ret = false; } } else { + auto loaded = false; #ifdef _WIN32 - detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); -#else - SSL_CTX_set_default_verify_paths(ctx_); -#endif + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } } }); @@ -5971,6 +6303,10 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) { return true; }, [&](SSL *ssl2) { + // NOTE: With -Wold-style-cast, this can produce a warning, since + // SSL_set_tlsext_host_name is a macro (in OpenSSL), which contains + // an old style cast. Short of doing compiler specific pragma's + // here, we can't get rid of this warning. :'( SSL_set_tlsext_host_name(ssl2, host_.c_str()); return true; }); @@ -6064,15 +6400,16 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { if (alt_names) { auto dsn_matched = false; - auto ip_mached = false; + auto ip_matched = false; auto count = sk_GENERAL_NAME_num(alt_names); for (decltype(count) i = 0; i < count && !dsn_matched; i++) { auto val = sk_GENERAL_NAME_value(alt_names, i); if (val->type == type) { - auto name = (const char *)ASN1_STRING_get0_data(val->d.ia5); - auto name_len = (size_t)ASN1_STRING_length(val->d.ia5); + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); switch (type) { case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; @@ -6080,17 +6417,18 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { case GEN_IPADD: if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) { - ip_mached = true; + ip_matched = true; } break; } } } - if (dsn_matched || ip_mached) { ret = true; } + if (dsn_matched || ip_matched) { ret = true; } } - GENERAL_NAMES_free((STACK_OF(GENERAL_NAME) *)alt_names); + GENERAL_NAMES_free(const_cast( + reinterpret_cast(alt_names))); return ret; } @@ -6502,12 +6840,16 @@ bool Client::send(Request &req, Response &res, Error &error) { Result Client::send(const Request &req) { return cli_->send(req); } +void Client::stop() { cli_->stop(); } + +std::string Client::host() const { return cli_->host(); } + +int Client::port() const { return cli_->port(); } + size_t Client::is_socket_open() const { return cli_->is_socket_open(); } socket_t Client::socket() const { return cli_->socket(); } -void Client::stop() { cli_->stop(); } - void Client::set_hostname_addr_map(std::map addr_map) { cli_->set_hostname_addr_map(std::move(addr_map)); @@ -6591,7 +6933,9 @@ void Client::enable_server_certificate_verification(bool enabled) { } #endif -void Client::set_logger(Logger logger) { cli_->set_logger(logger); } +void Client::set_logger(Logger logger) { + cli_->set_logger(std::move(logger)); +} #ifdef CPPHTTPLIB_OPENSSL_SUPPORT void Client::set_ca_cert_path(const std::string &ca_cert_file_path, @@ -6607,6 +6951,10 @@ void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { } } +void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { + set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size)); +} + long Client::get_openssl_verify_result() const { if (is_ssl_) { return static_cast(*cli_).get_openssl_verify_result(); diff --git a/source/http/httplib.h b/source/http/httplib.h index f75d315..ba1dddf 100644 --- a/source/http/httplib.h +++ b/source/http/httplib.h @@ -1,14 +1,14 @@ // // httplib.h // -// Copyright (c) 2022 Yuji Hirose. All rights reserved. +// Copyright (c) 2023 Yuji Hirose. All rights reserved. // MIT License // #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.12.0" +#define CPPHTTPLIB_VERSION "0.13.1" /* * Configuration @@ -87,7 +87,7 @@ #endif #ifndef CPPHTTPLIB_RECV_BUFSIZ -#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u) #endif #ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ @@ -172,9 +172,15 @@ using socket_t = SOCKET; #else // not _WIN32 #include -#ifndef _AIX +#if !defined(_AIX) && !defined(__MVS__) #include #endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif #include #include #include @@ -223,6 +229,9 @@ using socket_t = int; #include #include #include +#include +#include +#include #ifdef CPPHTTPLIB_OPENSSL_SUPPORT #ifdef _WIN32 @@ -239,7 +248,13 @@ using socket_t = int; #pragma comment(lib, "crypt32.lib") #pragma comment(lib, "cryptui.lib") #endif -#endif //_WIN32 +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 #include #include @@ -308,6 +323,34 @@ struct ci { } }; +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + } // namespace detail using Headers = std::multimap; @@ -340,6 +383,7 @@ public: std::function write; std::function done; + std::function done_with_trailer; std::ostream os; private: @@ -430,6 +474,7 @@ struct Request { MultipartFormDataMap files; Ranges ranges; Match matches; + std::unordered_map path_params; // for client ResponseHandler response_handler; @@ -623,6 +668,76 @@ using SocketOptions = std::function; void default_socket_options(socket_t sock); +namespace detail { + +class MatcherBase { +public: + virtual ~MatcherBase() = default; + + // Match request path and populate its matches and + virtual bool match(Request &request) const = 0; +}; + +/** + * 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 + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ +class PathParamsMatcher : public MatcherBase { +public: + PathParamsMatcher(const std::string &pattern); + + 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 + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; +}; + +/** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ +class RegexMatcher : public MatcherBase { +public: + RegexMatcher(const std::string &pattern) : regex_(pattern) {} + + bool match(Request &request) const override; + +private: + std::regex regex_; +}; + +} // namespace detail + class Server { public: using Handler = std::function; @@ -708,6 +823,7 @@ public: bool listen(const std::string &host, int port, int socket_flags = 0); bool is_running() const; + void wait_until_ready() const; void stop(); std::function new_task_queue; @@ -717,7 +833,7 @@ protected: bool &connection_closed, const std::function &setup_request); - std::atomic svr_sock_; + 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; @@ -729,9 +845,14 @@ protected: size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; private: - using Handlers = std::vector>; + using Handlers = + std::vector, Handler>>; using HandlersForContentReader = - std::vector>; + std::vector, + HandlerWithContentReader>>; + + static std::unique_ptr + make_matcher(const std::string &pattern); socket_t create_server_socket(const std::string &host, int port, int socket_flags, @@ -769,21 +890,23 @@ private: ContentReceiver multipart_receiver); bool read_content_core(Stream &strm, Request &req, Response &res, ContentReceiver receiver, - MultipartContentHeader mulitpart_header, + MultipartContentHeader multipart_header, ContentReceiver multipart_receiver); virtual bool process_and_close_socket(socket_t sock); + std::atomic is_running_{false}; + std::atomic done_{false}; + struct MountPointEntry { std::string mount_point; std::string base_dir; Headers headers; }; std::vector base_dirs_; - - std::atomic is_running_; std::map file_extension_and_mimetype_map_; Handler file_request_handler_; + Handlers get_handlers_; Handlers post_handlers_; HandlersForContentReader post_handlers_for_content_reader_; @@ -794,13 +917,15 @@ private: Handlers delete_handlers_; HandlersForContentReader delete_handlers_for_content_reader_; Handlers options_handlers_; + HandlerWithResponse error_handler_; ExceptionHandler exception_handler_; HandlerWithResponse pre_routing_handler_; Handler post_routing_handler_; - Logger logger_; Expect100ContinueHandler expect_100_continue_handler_; + Logger logger_; + int address_family_ = AF_UNSPEC; bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; SocketOptions socket_options_ = default_socket_options; @@ -823,6 +948,9 @@ enum class Error { UnsupportedMultipartBoundaryChars, Compression, ConnectionTimeout, + + // For internal use only + SSLPeerCouldBeClosed_, }; std::string to_string(const Error error); @@ -831,6 +959,7 @@ std::ostream &operator<<(std::ostream &os, const Error &obj); class Result { public: + Result() = default; Result(std::unique_ptr &&res, Error err, Headers &&request_headers = Headers{}) : res_(std::move(res)), err_(err), @@ -859,7 +988,7 @@ public: private: std::unique_ptr res_; - Error err_; + Error err_ = Error::Unknown; Headers request_headers_; }; @@ -1019,12 +1148,14 @@ public: bool send(Request &req, Response &res, Error &error); Result send(const Request &req); - size_t is_socket_open() const; - - socket_t socket() const; - void stop(); + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + void set_hostname_addr_map(std::map addr_map); void set_default_headers(Headers headers); @@ -1077,6 +1208,7 @@ public: void set_ca_cert_path(const std::string &ca_cert_file_path, const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(X509_STORE *ca_cert_store); + X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size); #endif #ifdef CPPHTTPLIB_OPENSSL_SUPPORT @@ -1095,8 +1227,6 @@ protected: bool is_open() const { return sock != INVALID_SOCKET; } }; - Result send_(Request &&req); - virtual bool create_and_connect_socket(Socket &socket, Error &error); // All of: @@ -1118,7 +1248,7 @@ protected: void copy_settings(const ClientImpl &rhs); - // Socket endoint information + // Socket endpoint information const std::string host_; const int port_; const std::string host_and_port_; @@ -1197,6 +1327,9 @@ protected: Logger logger_; private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + socket_t create_client_socket(Error &error) const; bool read_response_line(Stream &strm, const Request &req, Response &res); bool write_request(Stream &strm, Request &req, bool close_connection, @@ -1390,12 +1523,14 @@ public: bool send(Request &req, Response &res, Error &error); Result send(const Request &req); - size_t is_socket_open() const; - - socket_t socket() const; - void stop(); + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + void set_hostname_addr_map(std::map addr_map); void set_default_headers(Headers headers); @@ -1456,6 +1591,7 @@ public: const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); long get_openssl_verify_result() const; @@ -1515,6 +1651,7 @@ public: bool is_valid() const override; void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); long get_openssl_verify_result() const; @@ -1624,22 +1761,22 @@ inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { 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_REUSEADDR, + reinterpret_cast(&yes), sizeof(yes)); setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, - reinterpret_cast(&yes), sizeof(yes)); + reinterpret_cast(&yes), sizeof(yes)); #else #ifdef SO_REUSEPORT - setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&yes), - sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + reinterpret_cast(&yes), sizeof(yes)); #else - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast(&yes), - sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&yes), sizeof(yes)); #endif #endif - int const size = 1048576; - setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); - setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); + //int const size = 1048576; + //setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); + //setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size)); } template @@ -1791,6 +1928,9 @@ std::string params_to_query_str(const Params ¶ms); void parse_query_text(const std::string &s, Params ¶ms); +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + bool parse_range_header(const std::string &s, Ranges &ranges); int close_socket(socket_t sock); diff --git a/source/server/http_server.cpp b/source/server/http_server.cpp index 0e68872..9ebeb94 100644 --- a/source/server/http_server.cpp +++ b/source/server/http_server.cpp @@ -8,10 +8,14 @@ #include "windows.h" #include "lang.h" #include "system.h" +#include "dbglogger.h" #define SERVER_CERT_FILE "/app0/assets/certs/domain.crt" #define SERVER_PRIVATE_KEY_FILE "/app0/assets/certs/domain.key" #define SERVER_PRIVATE_KEY_PASSWORD "12345678" +#define SUCCESS_MSG "{ \"result\": { \"success\": true, \"error\": null } }" +#define FAILURE_MSG "{ \"result\": { \"success\": false, \"error\": \"%s\" } }" +#define SUCCESS_MSG_LEN 48 using namespace httplib; SSLServer *svr; @@ -76,6 +80,99 @@ namespace HttpServer return s; } + void failed(Response & res, int status, const std::string &msg) + { + res.status = status; + char response_msg[msg.length()+strlen(FAILURE_MSG)+2]; + snprintf(response_msg, sizeof(response_msg), "{ \"result\": { \"success\": false, \"error\": \"%s\" } }", msg.c_str()); + res.set_content(response_msg, strlen(response_msg), "application/json"); + return; + } + + void bad_request(Response & res, const std::string &msg) + { + failed(res, 200, msg); + return; + } + + void success(Response & res) + { + res.status = 200; + res.set_content(SUCCESS_MSG, SUCCESS_MSG_LEN, "application/json"); + return; + } + + int CopyOrMove(const DirEntry &src, const char *dest, bool isCopy) + { + int ret; + if (src.isDir) + { + int err; + std::vector entries = FS::ListDir(src.path, &err); + FS::MkDirs(dest); + for (int i = 0; i < entries.size(); i++) + { + int path_length = strlen(dest) + strlen(entries[i].name) + 2; + char *new_path = (char *)malloc(path_length); + snprintf(new_path, path_length, "%s%s%s", dest, FS::hasEndSlash(dest) ? "" : "/", entries[i].name); + + if (entries[i].isDir) + { + if (strcmp(entries[i].name, "..") == 0) + continue; + + FS::MkDirs(new_path); + ret = CopyOrMove(entries[i], new_path, isCopy); + if (ret <= 0) + { + free(new_path); + return ret; + } + } + else + { + if (isCopy) + { + ret = FS::Copy(entries[i].path, new_path); + } + else + { + ret = FS::Move(entries[i].path, new_path); + } + if (ret <= 0) + { + free(new_path); + return ret; + } + } + free(new_path); + } + if (!isCopy) + FS::RmRecursive(src.path); + } + else + { + int path_length = strlen(dest) + strlen(src.name) + 2; + char *new_path = (char *)malloc(path_length); + snprintf(new_path, path_length, "%s%s%s", dest, FS::hasEndSlash(dest) ? "" : "/", src.name); + if (isCopy) + { + ret = FS::Copy(src.path, new_path); + } + else + { + ret = FS::Move(src.path, new_path); + } + if (ret <= 0) + { + free(new_path); + return 0; + } + free(new_path); + } + return 1; + } + void *ServerThread(void *argp) { svr->Get("/", [&](const Request & req, Response & res) @@ -96,11 +193,15 @@ namespace HttpServer onlyFolders = true; if (path == nullptr) { - res.status = 400; - res.set_content("path parameter is missing", 25, "text/plain"); + bad_request(res, "Required path parameter missing"); return; } } + else + { + bad_request(res, "Invalid payload"); + return; + } int err; std::vector files = FS::ListDir(path, &err); @@ -113,7 +214,7 @@ namespace HttpServer json_object_object_add(new_file, "name", json_object_new_string(it->name)); json_object_object_add(new_file, "rights", json_object_new_string(it->isDir ? "drwxrwxrwx" : "rw-rw-rw-")); json_object_object_add(new_file, "date", json_object_new_string(it->display_date)); - json_object_object_add(new_file, "size", json_object_new_string(it->isDir ? "" : it->display_size)); + json_object_object_add(new_file, "size", json_object_new_string(it->isDir ? "" : std::to_string(it->file_size).c_str())); json_object_object_add(new_file, "type", json_object_new_string(it->isDir ? "dir" : "file")); json_object_array_add(json_files, new_file); } @@ -126,6 +227,360 @@ namespace HttpServer res.set_content(results_str, strlen(results_str), "application/json"); }); + svr->Post("/__local__/rename", [&](const Request & req, Response & res) + { + const char *item; + const char *newItemPath; + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + item = json_object_get_string(json_object_object_get(jobj, "item")); + newItemPath = json_object_get_string(json_object_object_get(jobj, "newItemPath")); + if (item == nullptr || newItemPath == nullptr) + { + bad_request(res, "Required item or newItemPath parameter missing"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + + FS::Rename(item, newItemPath); + success(res); + return; + }); + + svr->Post("/__local__/move", [&](const Request & req, Response & res) + { + const json_object *items; + const char *newPath; + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + items = json_object_object_get(jobj, "items"); + newPath = json_object_get_string(json_object_object_get(jobj, "newPath")); + if (items == nullptr || newPath == nullptr) + { + bad_request(res, "Required items or newPath parameter missing"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + + std::string failed_items; + size_t len = json_object_array_length(items); + for (size_t i=0; i < len; i++) + { + const char *item = json_object_get_string(json_object_array_get_idx(items, i)); + DirEntry entry; + std::string temp = std::string(item); + size_t slash_pos = temp.find_last_of("/"); + sprintf(entry.name, "%s", temp.substr(slash_pos+1).c_str()); + sprintf(entry.path, "%s", item); + entry.isDir = FS::IsFolder(item); + bool ret = CopyOrMove(entry, newPath, false); + if (!ret) + { + failed_items += std::string(item) + ","; + } + } + + if (failed_items.length() > 0) + { + std::string error_msg = std::string("One or more file(s) failed to move. ") + failed_items; + failed(res, 200, error_msg); + } + else + success(res); + }); + + svr->Post("/__local__/copy", [&](const Request & req, Response & res) + { + const json_object *items; + const char *newPath; + const char *singleFilename; + + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + items = json_object_object_get(jobj, "items"); + newPath = json_object_get_string(json_object_object_get(jobj, "newPath")); + singleFilename = json_object_get_string(json_object_object_get(jobj, "singleFilename")); + + if (items == nullptr || newPath == nullptr) + { + bad_request(res, "Required items or newPath or singleFilename parameter missing"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + + std::string failed_items; + if (singleFilename != nullptr) + { + const char *src = json_object_get_string(json_object_array_get_idx(items, 0)); + std::string dest = std::string(newPath) + "/" + singleFilename; + if (dest.compare(src) != 0 && !FS::Copy(src, dest)) + { + failed_items += src; + } + } + else + { + size_t len = json_object_array_length(items); + for (size_t i=0; i < len; i++) + { + const char *item = json_object_get_string(json_object_array_get_idx(items, i)); + DirEntry entry; + std::string temp = std::string(item); + size_t slash_pos = temp.find_last_of("/"); + sprintf(entry.name, "%s", temp.substr(slash_pos+1).c_str()); + sprintf(entry.path, "%s", item); + entry.isDir = FS::IsFolder(item); + bool ret = CopyOrMove(entry, newPath, true); + if (!ret) + { + failed_items += std::string(item) + ","; + } + } + } + + if (failed_items.length() > 0) + { + std::string error_msg = std::string("One or more file(s) failed to copy. ") + failed_items; + failed(res, 200, error_msg); + } + else + success(res); + }); + + svr->Post("/__local__/remove", [&](const Request & req, Response & res) + { + json_object *items; + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + items = json_object_object_get(jobj, "items"); + if (items == nullptr) + { + bad_request(res, "Required items parameter missing"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + + std::string failed_items; + size_t len = json_object_array_length(items); + for (size_t i=0; i < len; i++) + { + const char *item = json_object_get_string(json_object_array_get_idx(items, i)); + bool ret = FS::RmRecursive(item); + if (!ret) + { + failed_items += std::string(item) + ","; + } + } + + if (failed_items.length() > 0) + { + std::string error_msg = std::string("One or more file(s) failed to delete. ") + failed_items; + failed(res, 200, error_msg); + } + else + success(res); + }); + + svr->Post("/__local__/edit", [&](const Request & req, Response & res) + { + const char *item; + const char *content; + size_t content_len; + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + item = json_object_get_string(json_object_object_get(jobj, "item")); + json_object *content_obj = json_object_object_get(jobj, "content"); + content = json_object_get_string(content_obj); + content_len = json_object_get_string_len(content_obj); + if (item == nullptr || content == nullptr) + { + bad_request(res, "Required item or content parameter missing"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + + bool ret = FS::Save(item, content, content_len); + if (!ret) + { + failed(res, 200, "Failed to content to file."); + return; + } + + success(res); + }); + + svr->Post("/__local__/getContent", [&](const Request & req, Response & res) + { + const char *item; + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + item = json_object_get_string(json_object_object_get(jobj, "item")); + if (item == nullptr) + { + bad_request(res, "Required item parameter missing"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + + std::vector content = FS::Load(item); + json_object *result = json_object_new_object(); + json_object_object_add(result, "result", json_object_new_string(content.data())); + const char *result_str = json_object_to_json_string(result); + res.status = 200; + res.set_content(result_str, strlen(result_str), "application/json"); + }); + + svr->Post("/__local__/createFolder", [&](const Request & req, Response & res) + { + const char *newPath; + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + newPath = json_object_get_string(json_object_object_get(jobj, "newPath")); + if (newPath == nullptr) + { + bad_request(res, "Required newPath parameter missing"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + + FS::MkDirs(newPath); + success(res); + }); + + svr->Post("/__local__/permission", [&](const Request & req, Response & res) + { + failed(res, 200, "Operation not supported"); + }); + + svr->Post("/__local__/compress", [&](const Request & req, Response & res) + { + failed(res, 200, "Operation not supported"); + }); + + svr->Post("/__local__/extract", [&](const Request & req, Response & res) + { + failed(res, 200, "Operation not supported"); + }); + + svr->Post("/__local__/upload", [&](const Request &req, Response &res, const ContentReader &content_reader) + { + MultipartFormDataItems items; + std::string destination; + FILE *out = nullptr; + std::string new_file; + content_reader( + [&](const MultipartFormData &item) + { + items.push_back(item); + if (item.name != "destination") + { + new_file = destination + "/" + item.filename; + if (out != nullptr) + { + FS::Close(out); + } + out = FS::Create(new_file); + } + return true; + }, + [&](const char *data, size_t data_length) + { + items.back().content.append(data, data_length); + if (items.back().name == "destination") + { + destination = items.back().content; + } + else + { + dbglogger_log("data_length=%lld", data_length); + FS::Write(out, data, data_length); + } + return true; + }); + if (out != nullptr) + { + FS::Close(out); + } + success(res); + }); + + // Download multiple files as ZIP + svr->Get("/__local__/downloadMultiple", [&](const Request & req, Response & res) + { + const char *path; + json_object *jobj = json_tokener_parse(req.body.c_str()); + if (jobj != nullptr) + { + path = json_object_get_string(json_object_object_get(jobj, "path")); + if (path == nullptr) + { + failed(res, 200, "One or more file(s) failed to download"); + return; + } + } + else + { + bad_request(res, "Invalid payload"); + return; + } + }); + + // Download single file + svr->Get("/__local__/downloadFile", [&](const Request & req, Response & res) + { + std::string path = req.get_param_value("path", 0); + if (path.empty()) + { + bad_request(res, "Failed to download"); + return; + } + + dbglogger_log("path=%s", path.c_str()); + res.status = 200; + }); + svr->Get("/google_auth", [](const Request &req, Response &res) { std::string auth_code = req.get_param_value("code");