/* This file is part of libhttpserver Copyright (C) 2011-2019 Sebastiano Merlino This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "httpserver/http_utils.hpp" // is not pulled in transitively by // . Include it directly here so the // MHD_*-using bodies below still compile. #include // needed directly; not reachable transitively // (or its Windows equivalent) must also be // requested explicitly: it provides struct sockaddr's full definition // for get_ip_str / get_port / ip_representation::ip_representation. #if defined(_WIN32) && !defined(__CYGWIN__) #include // needed directly; not reachable transitively #include #include #include #include #include #else // WIN32 check #include #include #include #include // needed directly; not reachable transitively #include #endif // WIN32 check #include #include #include #include #include #include #include #include #include #include #include #include #include "httpserver/constants.hpp" #include "httpserver/detail/unescape_helpers.hpp" #include "httpserver/string_utilities.hpp" #if defined (__CYGWIN__) #if !defined (NI_MAXHOST) #define NI_MAXHOST 1025 #endif // NI_MAXHOST #ifndef __u_char_defined typedef unsigned char u_char; #define __u_char_defined #endif // __u_char_defined #endif // CYGWIN // libmicrohttpd deprecated some definitions with v0.9.74, and introduced new ones #if MHD_VERSION < 0x00097314 #define MHD_HTTP_CONTENT_TOO_LARGE MHD_HTTP_PAYLOAD_TOO_LARGE #define MHD_HTTP_UNPROCESSABLE_CONTENT MHD_HTTP_UNPROCESSABLE_ENTITY #endif namespace httpserver { namespace http { // See also: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html const int http_utils::http_continue = MHD_HTTP_CONTINUE; const int http_utils::http_switching_protocol = MHD_HTTP_SWITCHING_PROTOCOLS; const int http_utils::http_processing = MHD_HTTP_PROCESSING; const int http_utils::http_ok = MHD_HTTP_OK; const int http_utils::http_created = MHD_HTTP_CREATED; const int http_utils::http_accepted = MHD_HTTP_ACCEPTED; const int http_utils::http_non_authoritative_information = MHD_HTTP_NON_AUTHORITATIVE_INFORMATION; const int http_utils::http_no_content = MHD_HTTP_NO_CONTENT; const int http_utils::http_reset_content = MHD_HTTP_RESET_CONTENT; const int http_utils::http_partial_content = MHD_HTTP_PARTIAL_CONTENT; const int http_utils::http_multi_status = MHD_HTTP_MULTI_STATUS; const int http_utils::http_multiple_choices = MHD_HTTP_MULTIPLE_CHOICES; const int http_utils::http_moved_permanently = MHD_HTTP_MOVED_PERMANENTLY; const int http_utils::http_found = MHD_HTTP_FOUND; const int http_utils::http_see_other = MHD_HTTP_SEE_OTHER; const int http_utils::http_not_modified = MHD_HTTP_NOT_MODIFIED; const int http_utils::http_use_proxy = MHD_HTTP_USE_PROXY; const int http_utils::http_switch_proxy = MHD_HTTP_SWITCH_PROXY; const int http_utils::http_temporary_redirect = MHD_HTTP_TEMPORARY_REDIRECT; const int http_utils::http_bad_request = MHD_HTTP_BAD_REQUEST; const int http_utils::http_unauthorized = MHD_HTTP_UNAUTHORIZED; const int http_utils::http_payment_required = MHD_HTTP_PAYMENT_REQUIRED; const int http_utils::http_forbidden = MHD_HTTP_FORBIDDEN; const int http_utils::http_not_found = MHD_HTTP_NOT_FOUND; const int http_utils::http_method_not_allowed = MHD_HTTP_METHOD_NOT_ALLOWED; const int http_utils::http_method_not_acceptable = MHD_HTTP_NOT_ACCEPTABLE; const int http_utils::http_proxy_authentication_required = MHD_HTTP_PROXY_AUTHENTICATION_REQUIRED; const int http_utils::http_request_timeout = MHD_HTTP_REQUEST_TIMEOUT; const int http_utils::http_conflict = MHD_HTTP_CONFLICT; const int http_utils::http_gone = MHD_HTTP_GONE; const int http_utils::http_length_required = MHD_HTTP_LENGTH_REQUIRED; const int http_utils::http_precondition_failed = MHD_HTTP_PRECONDITION_FAILED; const int http_utils::http_request_entity_too_large = MHD_HTTP_CONTENT_TOO_LARGE; const int http_utils::http_request_uri_too_long = MHD_HTTP_URI_TOO_LONG; const int http_utils::http_unsupported_media_type = MHD_HTTP_UNSUPPORTED_MEDIA_TYPE; const int http_utils::http_requested_range_not_satisfiable = MHD_HTTP_RANGE_NOT_SATISFIABLE; const int http_utils::http_expectation_failed = MHD_HTTP_EXPECTATION_FAILED; const int http_utils::http_unprocessable_entity = MHD_HTTP_UNPROCESSABLE_CONTENT; const int http_utils::http_locked = MHD_HTTP_LOCKED; const int http_utils::http_failed_dependency = MHD_HTTP_FAILED_DEPENDENCY; const int http_utils::http_upgrade_required = MHD_HTTP_UPGRADE_REQUIRED; const int http_utils::http_retry_with = MHD_HTTP_RETRY_WITH; const int http_utils::http_internal_server_error = MHD_HTTP_INTERNAL_SERVER_ERROR; const int http_utils::http_not_implemented = MHD_HTTP_NOT_IMPLEMENTED; const int http_utils::http_bad_gateway = MHD_HTTP_BAD_GATEWAY; const int http_utils::http_service_unavailable = MHD_HTTP_SERVICE_UNAVAILABLE; const int http_utils::http_gateway_timeout = MHD_HTTP_GATEWAY_TIMEOUT; const int http_utils::http_version_not_supported = MHD_HTTP_HTTP_VERSION_NOT_SUPPORTED; const int http_utils::http_variant_also_negotiated = MHD_HTTP_VARIANT_ALSO_NEGOTIATES; const int http_utils::http_insufficient_storage = MHD_HTTP_INSUFFICIENT_STORAGE; const int http_utils::http_bandwidth_limit_exceeded = MHD_HTTP_BANDWIDTH_LIMIT_EXCEEDED; const int http_utils::http_not_extended = MHD_HTTP_NOT_EXTENDED; const int http_utils::shoutcast_response = MHD_ICY_FLAG; const char* http_utils::http_header_accept = MHD_HTTP_HEADER_ACCEPT; const char* http_utils::http_header_accept_charset = MHD_HTTP_HEADER_ACCEPT_CHARSET; const char* http_utils::http_header_accept_encoding = MHD_HTTP_HEADER_ACCEPT_ENCODING; const char* http_utils::http_header_accept_language = MHD_HTTP_HEADER_ACCEPT_LANGUAGE; const char* http_utils::http_header_accept_ranges = MHD_HTTP_HEADER_ACCEPT_RANGES; const char* http_utils::http_header_age = MHD_HTTP_HEADER_AGE; const char* http_utils::http_header_allow = MHD_HTTP_HEADER_ALLOW; const char* http_utils::http_header_authorization = MHD_HTTP_HEADER_AUTHORIZATION; const char* http_utils::http_header_cache_control = MHD_HTTP_HEADER_CACHE_CONTROL; const char* http_utils::http_header_connection = MHD_HTTP_HEADER_CONNECTION; const char* http_utils::http_header_content_encoding = MHD_HTTP_HEADER_CONTENT_ENCODING; const char* http_utils::http_header_content_language = MHD_HTTP_HEADER_CONTENT_LANGUAGE; const char* http_utils::http_header_content_length = MHD_HTTP_HEADER_CONTENT_LENGTH; const char* http_utils::http_header_content_location = MHD_HTTP_HEADER_CONTENT_LOCATION; const char* http_utils::http_header_content_md5 = MHD_HTTP_HEADER_CONTENT_MD5; const char* http_utils::http_header_content_range = MHD_HTTP_HEADER_CONTENT_RANGE; const char* http_utils::http_header_content_type = MHD_HTTP_HEADER_CONTENT_TYPE; const char* http_utils::http_header_date = MHD_HTTP_HEADER_DATE; const char* http_utils::http_header_etag = MHD_HTTP_HEADER_ETAG; const char* http_utils::http_header_expect = MHD_HTTP_HEADER_EXPECT; const char* http_utils::http_header_expires = MHD_HTTP_HEADER_EXPIRES; const char* http_utils::http_header_from = MHD_HTTP_HEADER_FROM; const char* http_utils::http_header_host = MHD_HTTP_HEADER_HOST; const char* http_utils::http_header_if_match = MHD_HTTP_HEADER_IF_MATCH; const char* http_utils::http_header_if_modified_since = MHD_HTTP_HEADER_IF_MODIFIED_SINCE; const char* http_utils::http_header_if_none_match = MHD_HTTP_HEADER_IF_NONE_MATCH; const char* http_utils::http_header_if_range = MHD_HTTP_HEADER_IF_RANGE; const char* http_utils::http_header_if_unmodified_since = MHD_HTTP_HEADER_IF_UNMODIFIED_SINCE; const char* http_utils::http_header_last_modified = MHD_HTTP_HEADER_LAST_MODIFIED; const char* http_utils::http_header_location = MHD_HTTP_HEADER_LOCATION; const char* http_utils::http_header_max_forwards = MHD_HTTP_HEADER_MAX_FORWARDS; const char* http_utils::http_header_pragma = MHD_HTTP_HEADER_PRAGMA; const char* http_utils::http_header_proxy_authenticate = MHD_HTTP_HEADER_PROXY_AUTHENTICATE; const char* http_utils::http_header_proxy_authentication = MHD_HTTP_HEADER_PROXY_AUTHORIZATION; const char* http_utils::http_header_range = MHD_HTTP_HEADER_RANGE; const char* http_utils::http_header_referer = MHD_HTTP_HEADER_REFERER; const char* http_utils::http_header_retry_after = MHD_HTTP_HEADER_RETRY_AFTER; const char* http_utils::http_header_server = MHD_HTTP_HEADER_SERVER; const char* http_utils::http_header_te = MHD_HTTP_HEADER_TE; const char* http_utils::http_header_trailer = MHD_HTTP_HEADER_TRAILER; const char* http_utils::http_header_transfer_encoding = MHD_HTTP_HEADER_TRANSFER_ENCODING; const char* http_utils::http_header_upgrade = MHD_HTTP_HEADER_UPGRADE; const char* http_utils::http_header_user_agent = MHD_HTTP_HEADER_USER_AGENT; const char* http_utils::http_header_vary = MHD_HTTP_HEADER_VARY; const char* http_utils::http_header_via = MHD_HTTP_HEADER_VIA; const char* http_utils::http_header_warning = MHD_HTTP_HEADER_WARNING; const char* http_utils::http_header_www_authenticate = MHD_HTTP_HEADER_WWW_AUTHENTICATE; const char* http_utils::http_version_1_0 = MHD_HTTP_VERSION_1_0; const char* http_utils::http_version_1_1 = MHD_HTTP_VERSION_1_1; const char* http_utils::http_method_connect = MHD_HTTP_METHOD_CONNECT; const char* http_utils::http_method_delete = MHD_HTTP_METHOD_DELETE; const char* http_utils::http_method_get = MHD_HTTP_METHOD_GET; const char* http_utils::http_method_head = MHD_HTTP_METHOD_HEAD; const char* http_utils::http_method_options = MHD_HTTP_METHOD_OPTIONS; const char* http_utils::http_method_post = MHD_HTTP_METHOD_POST; const char* http_utils::http_method_put = MHD_HTTP_METHOD_PUT; const char* http_utils::http_method_trace = MHD_HTTP_METHOD_TRACE; const char* http_utils::http_method_patch = MHD_HTTP_METHOD_PATCH; const char* http_utils::http_post_encoding_form_urlencoded = MHD_HTTP_POST_ENCODING_FORM_URLENCODED; const char* http_utils::http_post_encoding_multipart_formdata = MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA; const char* http_utils::application_octet_stream = "application/octet-stream"; const char* http_utils::text_plain = "text/plain"; const char* http_utils::upload_filename_template = "libhttpserver.XXXXXX"; #if defined(_WIN32) const char http_utils::path_separator = '\\'; #else // _WIN32 const char http_utils::path_separator = '/'; #endif // _WIN32 std::vector http_utils::tokenize_url(const std::string& str, const char separator) { return string_utilities::string_split(str, separator); } std::string http_utils::standardize_url(const std::string& url) { if (url.empty()) return url; std::string result = url; auto new_end = std::unique(result.begin(), result.end(), [](char a, char b) { return (a == b) && (a == '/'); }); result.erase(new_end, result.end()); if (result.length() > 1 && result.back() == '/') { result.pop_back(); } return result; } const std::string http_utils::generate_random_upload_filename(const std::string& directory) { std::string filename = directory + http_utils::path_separator + http_utils::upload_filename_template; char *template_filename = strdup(filename.c_str()); int fd = 0; #if defined(_WIN32) // only function for win32 which creates unique filenames and can handle a given template including a path // all other functions like tmpnam() always create filenames in the 'temp' directory if (0 != _mktemp_s(template_filename, filename.size() + 1)) { free(template_filename); throw generateFilenameException("Failed to create unique filename"); } // as no existing file should be overwritten the operation should fail if the file already exists // fstream or ofstream classes don't feature such an option // with the function _sopen_s this can be achieved by setting the flag _O_EXCL if (0 != _sopen_s(&fd, template_filename, _O_CREAT | _O_EXCL | _O_NOINHERIT, _SH_DENYNO, _S_IREAD | _S_IWRITE)) { free(template_filename); throw generateFilenameException("Failed to create file"); } if (fd == -1) { free(template_filename); throw generateFilenameException("File descriptor after successful _sopen_s is -1"); } _close(fd); #else // _WIN32 fd = mkstemp(template_filename); if (fd == -1) { free(template_filename); throw generateFilenameException("Failed to create unique file"); } close(fd); #endif // _WIN32 std::string ret_filename = template_filename; free(template_filename); return ret_filename; } std::string http_utils::sanitize_upload_filename(const std::string& filename) { if (filename.empty()) return ""; // Reject filenames containing embedded null bytes. A name like // "foo.txt\x00.php" would be silently truncated at the null when the // concatenated path is passed to std::ofstream::open() (which calls // c_str() and hands a const char* to the OS open() syscall), creating // a file at the truncated location rather than the full path. // (CWE-626 / null-byte injection) if (filename.find('\0') != std::string::npos) return ""; // Find the basename: take everything after the last '/' or '\' std::string::size_type pos = filename.find_last_of("/\\"); std::string basename = (pos != std::string::npos) ? filename.substr(pos + 1) : filename; // Reject empty basename, ".", and ".." if (basename.empty() || basename == "." || basename == "..") { return ""; } return basename; } // get_ip_str / get_port and the entire ip_representation impl live in // src/detail/ip_representation.cpp to keep this TU under the project // per-file LOC ceiling. See FILE_LOC_MAX in scripts/check-file-size.sh. // hex_digit_value and the core unescape loop live in the shared // internal header src/httpserver/detail/unescape_helpers.hpp so that // this TU and src/detail/http_request_impl.cpp share one truth-source. // http_unescape is a thin wrapper around unescape_buf_raw. size_t http_unescape(std::string* val) { if (val->empty()) return 0; const std::size_t new_size = httpserver::detail::unescape_buf_raw(val->data(), val->size()); (*val)[new_size] = '\0'; // add 0-terminator (std::string owns the byte) val->resize(new_size); return new_size; // = strlen(val) } const std::string load_file(const std::string& filename) { std::ifstream fp(filename.c_str(), std::ios::in | std::ios::binary | std::ios::ate); if (fp.is_open()) { std::string content; fp.seekg(0, fp.end); content.reserve(fp.tellg()); fp.seekg(0, fp.beg); content.assign((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); return content; } else { throw std::invalid_argument("Unable to open file"); } } void dump_header_map(std::ostream& os, const std::string& prefix, const http::header_view_map &map) { auto it = map.begin(); auto end = map.end(); if (map.size()) { os << " " << prefix << " ["; for (; it != end; ++it) { os << (*it).first << ":\"" << (*it).second << "\" "; } os << "]" << std::endl; } } void dump_header_map(std::ostream& os, const std::string& prefix, const http::header_map& map) { if (!map.empty()) { os << " " << prefix << " ["; for (const auto& item : map) { os << item.first << ":\"" << item.second << "\" "; } os << "]" << std::endl; } } void dump_arg_map(std::ostream& os, const std::string& prefix, const http::arg_view_map &map) { auto it = map.begin(); auto end = map.end(); if (map.size()) { os << " " << prefix << " ["; for (; it != end; ++it) { os << (*it).first << ":["; std::string sep = ""; for (const auto& v : it->second.values) { os << sep << "\"" << v << "\""; sep = ", "; } os << "] "; } os << "]" << std::endl; } } // Contract: decodes *s IN PLACE and returns the new length. When the // user configured no custom unescaper (create_webserver::unescaper not // set, so `unescaper` is nullptr), the default percent-decoder // http_unescape is applied. A custom unescaper REPLACES the default // entirely — no percent-decoding happens around it. // Note: on an empty string, (*s)[0] is the null terminator (valid per // C++11 operator[]), so the `== 0` test is a legitimate empty-check. size_t base_unescaper(std::string* s, unescaper_ptr unescaper) { if ((*s)[0] == 0) return 0; if (unescaper != nullptr) { unescaper(*s); return s->size(); } return http_unescape(s); } const char* http_utils::reason_phrase(unsigned int status_code) { return MHD_get_reason_phrase_for(status_code); } bool http_utils::is_feature_supported(int feature) { return MHD_is_feature_supported(static_cast(feature)) == MHD_YES; } const char* http_utils::get_mhd_version() { return MHD_get_version(); } // Pin start_method_T to libmicrohttpd's MHD_FLAG enum and // digest_algorithm / digest_auth_result to MHD_DigestAuthAlgo3 / // MHD_DigestAuthResult. The public-header values are hard-coded so the // umbrella does not transitively include ; these asserts // guard against an upstream renumber by failing the build at the right // place rather than silently mis-routing start-mode selection or // digest-auth result codes. static_assert( static_cast(http_utils::INTERNAL_SELECT) == (MHD_USE_SELECT_INTERNALLY | MHD_USE_AUTO), "start_method_T::INTERNAL_SELECT diverged from MHD_USE_SELECT_INTERNALLY|MHD_USE_AUTO"); static_assert( static_cast(http_utils::THREAD_PER_CONNECTION) == (MHD_USE_THREAD_PER_CONNECTION | MHD_USE_AUTO), "start_method_T::THREAD_PER_CONNECTION diverged from MHD_USE_THREAD_PER_CONNECTION|MHD_USE_AUTO"); static_assert( static_cast(http_utils::EXTERNAL_SELECT) == MHD_USE_AUTO, "start_method_T::EXTERNAL_SELECT diverged from MHD_USE_AUTO"); // digest_algorithm and digest_auth_result: pinned only when HAVE_DAUTH is set // because the MHD_DIGEST_AUTH_ALGO3_* and MHD_DAUTH_* macros are only defined // by in digest-auth-enabled builds. #ifdef HAVE_DAUTH static_assert( static_cast(http_utils::digest_algorithm::MD5) == static_cast(MHD_DIGEST_AUTH_ALGO3_MD5), "digest_algorithm::MD5 diverged from MHD_DIGEST_AUTH_ALGO3_MD5"); static_assert( static_cast(http_utils::digest_algorithm::SHA256) == static_cast(MHD_DIGEST_AUTH_ALGO3_SHA256), "digest_algorithm::SHA256 diverged from MHD_DIGEST_AUTH_ALGO3_SHA256"); static_assert( static_cast(http_utils::digest_algorithm::SHA512_256) == static_cast(MHD_DIGEST_AUTH_ALGO3_SHA512_256), "digest_algorithm::SHA512_256 diverged from MHD_DIGEST_AUTH_ALGO3_SHA512_256"); static_assert( static_cast(http_utils::digest_auth_result::OK) == MHD_DAUTH_OK, "digest_auth_result::OK diverged from MHD_DAUTH_OK"); static_assert( static_cast(http_utils::digest_auth_result::GENERIC_ERROR) == MHD_DAUTH_ERROR, "digest_auth_result::GENERIC_ERROR diverged from MHD_DAUTH_ERROR"); static_assert( static_cast(http_utils::digest_auth_result::WRONG_HEADER) == MHD_DAUTH_WRONG_HEADER, "digest_auth_result::WRONG_HEADER diverged from MHD_DAUTH_WRONG_HEADER"); static_assert( static_cast(http_utils::digest_auth_result::WRONG_USERNAME) == MHD_DAUTH_WRONG_USERNAME, "digest_auth_result::WRONG_USERNAME diverged from MHD_DAUTH_WRONG_USERNAME"); static_assert( static_cast(http_utils::digest_auth_result::WRONG_REALM) == MHD_DAUTH_WRONG_REALM, "digest_auth_result::WRONG_REALM diverged from MHD_DAUTH_WRONG_REALM"); static_assert( static_cast(http_utils::digest_auth_result::WRONG_URI) == MHD_DAUTH_WRONG_URI, "digest_auth_result::WRONG_URI diverged from MHD_DAUTH_WRONG_URI"); static_assert( static_cast(http_utils::digest_auth_result::WRONG_QOP) == MHD_DAUTH_WRONG_QOP, "digest_auth_result::WRONG_QOP diverged from MHD_DAUTH_WRONG_QOP"); static_assert( static_cast(http_utils::digest_auth_result::WRONG_ALGO) == MHD_DAUTH_WRONG_ALGO, "digest_auth_result::WRONG_ALGO diverged from MHD_DAUTH_WRONG_ALGO"); static_assert( static_cast(http_utils::digest_auth_result::TOO_LARGE) == MHD_DAUTH_TOO_LARGE, "digest_auth_result::TOO_LARGE diverged from MHD_DAUTH_TOO_LARGE"); static_assert( static_cast(http_utils::digest_auth_result::NONCE_STALE) == MHD_DAUTH_NONCE_STALE, "digest_auth_result::NONCE_STALE diverged from MHD_DAUTH_NONCE_STALE"); static_assert( static_cast(http_utils::digest_auth_result::NONCE_OTHER_COND) == MHD_DAUTH_NONCE_OTHER_COND, "digest_auth_result::NONCE_OTHER_COND diverged from MHD_DAUTH_NONCE_OTHER_COND"); static_assert( static_cast(http_utils::digest_auth_result::NONCE_WRONG) == MHD_DAUTH_NONCE_WRONG, "digest_auth_result::NONCE_WRONG diverged from MHD_DAUTH_NONCE_WRONG"); static_assert( static_cast(http_utils::digest_auth_result::RESPONSE_WRONG) == MHD_DAUTH_RESPONSE_WRONG, "digest_auth_result::RESPONSE_WRONG diverged from MHD_DAUTH_RESPONSE_WRONG"); #endif // HAVE_DAUTH } // namespace http } // namespace httpserver