mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-27 12:31:28 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30b7732565 | ||
|
|
6650632e7f | ||
|
|
afe627e7af | ||
|
|
67f6ff7fa9 | ||
|
|
c7ed1796a7 | ||
|
|
44c62d838e | ||
|
|
6bb580cda8 | ||
|
|
00a8cb8e5d | ||
|
|
2e34a39673 | ||
|
|
01b90829bc | ||
|
|
e699bd0730 | ||
|
|
961a9379d5 | ||
|
|
ec87b04aff | ||
|
|
aabf752a51 | ||
|
|
afb0674ccb | ||
|
|
ee625232a4 | ||
|
|
52d8dd41f1 | ||
|
|
be07d2d7a9 |
No files matched your search
+2
-2
@@ -256,13 +256,13 @@ configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
|
|||||||
if(HTTPLIB_COMPILE)
|
if(HTTPLIB_COMPILE)
|
||||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||||
# Example: if you find_package(httplib 0.5.4)
|
# Example: if you find_package(httplib 0.5.4)
|
||||||
# then anything >= 0.5 and <= 1.0 is accepted
|
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||||
COMPATIBILITY SameMinorVersion
|
COMPATIBILITY SameMinorVersion
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||||
# Example: if you find_package(httplib 0.5.4)
|
# Example: if you find_package(httplib 0.5.4)
|
||||||
# then anything >= 0.5 and <= 1.0 is accepted
|
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||||
COMPATIBILITY SameMinorVersion
|
COMPATIBILITY SameMinorVersion
|
||||||
# Tells Cmake that it's a header-only lib
|
# Tells Cmake that it's a header-only lib
|
||||||
# Mildly useful for end-users :)
|
# Mildly useful for end-users :)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#ifndef CPPHTTPLIB_HTTPLIB_H
|
#ifndef CPPHTTPLIB_HTTPLIB_H
|
||||||
#define CPPHTTPLIB_HTTPLIB_H
|
#define CPPHTTPLIB_HTTPLIB_H
|
||||||
|
|
||||||
#define CPPHTTPLIB_VERSION "0.13.1"
|
#define CPPHTTPLIB_VERSION "0.14.0"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Configuration
|
* Configuration
|
||||||
@@ -193,6 +193,7 @@ using socket_t = SOCKET;
|
|||||||
#endif
|
#endif
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
#include <sys/select.h>
|
#include <sys/select.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
@@ -486,8 +487,7 @@ struct Request {
|
|||||||
|
|
||||||
bool has_header(const std::string &key) const;
|
bool has_header(const std::string &key) const;
|
||||||
std::string get_header_value(const std::string &key, size_t id = 0) const;
|
std::string get_header_value(const std::string &key, size_t id = 0) const;
|
||||||
template <typename T>
|
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
|
||||||
T get_header_value(const std::string &key, size_t id = 0) const;
|
|
||||||
size_t get_header_value_count(const std::string &key) const;
|
size_t get_header_value_count(const std::string &key) const;
|
||||||
void set_header(const std::string &key, const std::string &val);
|
void set_header(const std::string &key, const std::string &val);
|
||||||
|
|
||||||
@@ -519,8 +519,7 @@ struct Response {
|
|||||||
|
|
||||||
bool has_header(const std::string &key) const;
|
bool has_header(const std::string &key) const;
|
||||||
std::string get_header_value(const std::string &key, size_t id = 0) const;
|
std::string get_header_value(const std::string &key, size_t id = 0) const;
|
||||||
template <typename T>
|
uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const;
|
||||||
T get_header_value(const std::string &key, size_t id = 0) const;
|
|
||||||
size_t get_header_value_count(const std::string &key) const;
|
size_t get_header_value_count(const std::string &key) const;
|
||||||
void set_header(const std::string &key, const std::string &val);
|
void set_header(const std::string &key, const std::string &val);
|
||||||
|
|
||||||
@@ -668,6 +667,8 @@ using SocketOptions = std::function<void(socket_t sock)>;
|
|||||||
|
|
||||||
void default_socket_options(socket_t sock);
|
void default_socket_options(socket_t sock);
|
||||||
|
|
||||||
|
const char *status_message(int status);
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
|
||||||
class MatcherBase {
|
class MatcherBase {
|
||||||
@@ -782,6 +783,7 @@ public:
|
|||||||
bool remove_mount_point(const std::string &mount_point);
|
bool remove_mount_point(const std::string &mount_point);
|
||||||
Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
|
Server &set_file_extension_and_mimetype_mapping(const std::string &ext,
|
||||||
const std::string &mime);
|
const std::string &mime);
|
||||||
|
Server &set_default_file_mimetype(const std::string &mime);
|
||||||
Server &set_file_request_handler(Handler handler);
|
Server &set_file_request_handler(Handler handler);
|
||||||
|
|
||||||
Server &set_error_handler(HandlerWithResponse handler);
|
Server &set_error_handler(HandlerWithResponse handler);
|
||||||
@@ -905,6 +907,7 @@ private:
|
|||||||
};
|
};
|
||||||
std::vector<MountPointEntry> base_dirs_;
|
std::vector<MountPointEntry> base_dirs_;
|
||||||
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
std::map<std::string, std::string> file_extension_and_mimetype_map_;
|
||||||
|
std::string default_file_mimetype_ = "application/octet-stream";
|
||||||
Handler file_request_handler_;
|
Handler file_request_handler_;
|
||||||
|
|
||||||
Handlers get_handlers_;
|
Handlers get_handlers_;
|
||||||
@@ -948,6 +951,7 @@ enum class Error {
|
|||||||
UnsupportedMultipartBoundaryChars,
|
UnsupportedMultipartBoundaryChars,
|
||||||
Compression,
|
Compression,
|
||||||
ConnectionTimeout,
|
ConnectionTimeout,
|
||||||
|
ProxyConnection,
|
||||||
|
|
||||||
// For internal use only
|
// For internal use only
|
||||||
SSLPeerCouldBeClosed_,
|
SSLPeerCouldBeClosed_,
|
||||||
@@ -982,8 +986,8 @@ public:
|
|||||||
bool has_request_header(const std::string &key) const;
|
bool has_request_header(const std::string &key) const;
|
||||||
std::string get_request_header_value(const std::string &key,
|
std::string get_request_header_value(const std::string &key,
|
||||||
size_t id = 0) const;
|
size_t id = 0) const;
|
||||||
template <typename T>
|
uint64_t get_request_header_value_u64(const std::string &key,
|
||||||
T get_request_header_value(const std::string &key, size_t id = 0) const;
|
size_t id = 0) const;
|
||||||
size_t get_request_header_value_count(const std::string &key) const;
|
size_t get_request_header_value_count(const std::string &key) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -1704,15 +1708,9 @@ inline void duration_to_sec_and_usec(const T &duration, U callback) {
|
|||||||
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
|
callback(static_cast<time_t>(sec), static_cast<time_t>(usec));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
inline uint64_t get_header_value_u64(const Headers &headers,
|
||||||
inline T get_header_value(const Headers & /*headers*/,
|
const std::string &key, size_t id,
|
||||||
const std::string & /*key*/, size_t /*id*/ = 0,
|
uint64_t def) {
|
||||||
uint64_t /*def*/ = 0) {}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
inline uint64_t get_header_value<uint64_t>(const Headers &headers,
|
|
||||||
const std::string &key, size_t id,
|
|
||||||
uint64_t def) {
|
|
||||||
auto rng = headers.equal_range(key);
|
auto rng = headers.equal_range(key);
|
||||||
auto it = rng.first;
|
auto it = rng.first;
|
||||||
std::advance(it, static_cast<ssize_t>(id));
|
std::advance(it, static_cast<ssize_t>(id));
|
||||||
@@ -1724,14 +1722,14 @@ inline uint64_t get_header_value<uint64_t>(const Headers &headers,
|
|||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template <typename T>
|
inline uint64_t Request::get_header_value_u64(const std::string &key,
|
||||||
inline T Request::get_header_value(const std::string &key, size_t id) const {
|
size_t id) const {
|
||||||
return detail::get_header_value<T>(headers, key, id, 0);
|
return detail::get_header_value_u64(headers, key, id, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
inline uint64_t Response::get_header_value_u64(const std::string &key,
|
||||||
inline T Response::get_header_value(const std::string &key, size_t id) const {
|
size_t id) const {
|
||||||
return detail::get_header_value<T>(headers, key, id, 0);
|
return detail::get_header_value_u64(headers, key, id, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
@@ -1776,6 +1774,76 @@ inline void default_socket_options(socket_t sock) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline const char *status_message(int status) {
|
||||||
|
switch (status) {
|
||||||
|
case 100: return "Continue";
|
||||||
|
case 101: return "Switching Protocol";
|
||||||
|
case 102: return "Processing";
|
||||||
|
case 103: return "Early Hints";
|
||||||
|
case 200: return "OK";
|
||||||
|
case 201: return "Created";
|
||||||
|
case 202: return "Accepted";
|
||||||
|
case 203: return "Non-Authoritative Information";
|
||||||
|
case 204: return "No Content";
|
||||||
|
case 205: return "Reset Content";
|
||||||
|
case 206: return "Partial Content";
|
||||||
|
case 207: return "Multi-Status";
|
||||||
|
case 208: return "Already Reported";
|
||||||
|
case 226: return "IM Used";
|
||||||
|
case 300: return "Multiple Choice";
|
||||||
|
case 301: return "Moved Permanently";
|
||||||
|
case 302: return "Found";
|
||||||
|
case 303: return "See Other";
|
||||||
|
case 304: return "Not Modified";
|
||||||
|
case 305: return "Use Proxy";
|
||||||
|
case 306: return "unused";
|
||||||
|
case 307: return "Temporary Redirect";
|
||||||
|
case 308: return "Permanent Redirect";
|
||||||
|
case 400: return "Bad Request";
|
||||||
|
case 401: return "Unauthorized";
|
||||||
|
case 402: return "Payment Required";
|
||||||
|
case 403: return "Forbidden";
|
||||||
|
case 404: return "Not Found";
|
||||||
|
case 405: return "Method Not Allowed";
|
||||||
|
case 406: return "Not Acceptable";
|
||||||
|
case 407: return "Proxy Authentication Required";
|
||||||
|
case 408: return "Request Timeout";
|
||||||
|
case 409: return "Conflict";
|
||||||
|
case 410: return "Gone";
|
||||||
|
case 411: return "Length Required";
|
||||||
|
case 412: return "Precondition Failed";
|
||||||
|
case 413: return "Payload Too Large";
|
||||||
|
case 414: return "URI Too Long";
|
||||||
|
case 415: return "Unsupported Media Type";
|
||||||
|
case 416: return "Range Not Satisfiable";
|
||||||
|
case 417: return "Expectation Failed";
|
||||||
|
case 418: return "I'm a teapot";
|
||||||
|
case 421: return "Misdirected Request";
|
||||||
|
case 422: return "Unprocessable Entity";
|
||||||
|
case 423: return "Locked";
|
||||||
|
case 424: return "Failed Dependency";
|
||||||
|
case 425: return "Too Early";
|
||||||
|
case 426: return "Upgrade Required";
|
||||||
|
case 428: return "Precondition Required";
|
||||||
|
case 429: return "Too Many Requests";
|
||||||
|
case 431: return "Request Header Fields Too Large";
|
||||||
|
case 451: return "Unavailable For Legal Reasons";
|
||||||
|
case 501: return "Not Implemented";
|
||||||
|
case 502: return "Bad Gateway";
|
||||||
|
case 503: return "Service Unavailable";
|
||||||
|
case 504: return "Gateway Timeout";
|
||||||
|
case 505: return "HTTP Version Not Supported";
|
||||||
|
case 506: return "Variant Also Negotiates";
|
||||||
|
case 507: return "Insufficient Storage";
|
||||||
|
case 508: return "Loop Detected";
|
||||||
|
case 510: return "Not Extended";
|
||||||
|
case 511: return "Network Authentication Required";
|
||||||
|
|
||||||
|
default:
|
||||||
|
case 500: return "Internal Server Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <class Rep, class Period>
|
template <class Rep, class Period>
|
||||||
inline Server &
|
inline Server &
|
||||||
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
Server::set_read_timeout(const std::chrono::duration<Rep, Period> &duration) {
|
||||||
@@ -1816,6 +1884,7 @@ inline std::string to_string(const Error error) {
|
|||||||
return "Unsupported HTTP multipart boundary characters";
|
return "Unsupported HTTP multipart boundary characters";
|
||||||
case Error::Compression: return "Compression failed";
|
case Error::Compression: return "Compression failed";
|
||||||
case Error::ConnectionTimeout: return "Connection timed out";
|
case Error::ConnectionTimeout: return "Connection timed out";
|
||||||
|
case Error::ProxyConnection: return "Proxy connection failed";
|
||||||
case Error::Unknown: return "Unknown";
|
case Error::Unknown: return "Unknown";
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
@@ -1829,10 +1898,9 @@ inline std::ostream &operator<<(std::ostream &os, const Error &obj) {
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
inline uint64_t Result::get_request_header_value_u64(const std::string &key,
|
||||||
inline T Result::get_request_header_value(const std::string &key,
|
size_t id) const {
|
||||||
size_t id) const {
|
return detail::get_header_value_u64(request_headers_, key, id, 0);
|
||||||
return detail::get_header_value<T>(request_headers_, key, id, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Rep, class Period>
|
template <class Rep, class Period>
|
||||||
@@ -2068,6 +2136,29 @@ private:
|
|||||||
std::string glowable_buffer_;
|
std::string glowable_buffer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class mmap {
|
||||||
|
public:
|
||||||
|
mmap(const char *path);
|
||||||
|
~mmap();
|
||||||
|
|
||||||
|
bool open(const char *path);
|
||||||
|
void close();
|
||||||
|
|
||||||
|
bool is_open() const;
|
||||||
|
size_t size() const;
|
||||||
|
const char *data() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#if defined(_WIN32)
|
||||||
|
HANDLE hFile_;
|
||||||
|
HANDLE hMapping_;
|
||||||
|
#else
|
||||||
|
int fd_;
|
||||||
|
#endif
|
||||||
|
size_t size_;
|
||||||
|
void *addr_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@@ -2356,6 +2447,13 @@ inline std::string trim_copy(const std::string &s) {
|
|||||||
return s.substr(r.first, r.second - r.first);
|
return s.substr(r.first, r.second - r.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::string trim_double_quotes_copy(const std::string &s) {
|
||||||
|
if (s.length() >= 2 && s.front() == '"' && s.back() == '"') {
|
||||||
|
return s.substr(1, s.size() - 2);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
inline void split(const char *b, const char *e, char d,
|
inline void split(const char *b, const char *e, char d,
|
||||||
std::function<void(const char *, const char *)> fn) {
|
std::function<void(const char *, const char *)> fn) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
@@ -2441,6 +2539,95 @@ inline void stream_line_reader::append(char c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline mmap::mmap(const char *path)
|
||||||
|
#if defined(_WIN32)
|
||||||
|
: hFile_(NULL), hMapping_(NULL)
|
||||||
|
#else
|
||||||
|
: fd_(-1)
|
||||||
|
#endif
|
||||||
|
,
|
||||||
|
size_(0), addr_(nullptr) {
|
||||||
|
if (!open(path)) { std::runtime_error(""); }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline mmap::~mmap() { close(); }
|
||||||
|
|
||||||
|
inline bool mmap::open(const char *path) {
|
||||||
|
close();
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
hFile_ = ::CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||||
|
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
|
||||||
|
if (hFile_ == INVALID_HANDLE_VALUE) { return false; }
|
||||||
|
|
||||||
|
size_ = ::GetFileSize(hFile_, NULL);
|
||||||
|
|
||||||
|
hMapping_ = ::CreateFileMapping(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||||
|
|
||||||
|
if (hMapping_ == NULL) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addr_ = ::MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0);
|
||||||
|
#else
|
||||||
|
fd_ = ::open(path, O_RDONLY);
|
||||||
|
if (fd_ == -1) { return false; }
|
||||||
|
|
||||||
|
struct stat sb;
|
||||||
|
if (fstat(fd_, &sb) == -1) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_ = static_cast<size_t>(sb.st_size);
|
||||||
|
|
||||||
|
addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (addr_ == nullptr) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool mmap::is_open() const { return addr_ != nullptr; }
|
||||||
|
|
||||||
|
inline size_t mmap::size() const { return size_; }
|
||||||
|
|
||||||
|
inline const char *mmap::data() const { return (const char *)addr_; }
|
||||||
|
|
||||||
|
inline void mmap::close() {
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (addr_) {
|
||||||
|
::UnmapViewOfFile(addr_);
|
||||||
|
addr_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hMapping_) {
|
||||||
|
::CloseHandle(hMapping_);
|
||||||
|
hMapping_ = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hFile_ != INVALID_HANDLE_VALUE) {
|
||||||
|
::CloseHandle(hFile_);
|
||||||
|
hFile_ = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (addr_ != nullptr) {
|
||||||
|
munmap(addr_, size_);
|
||||||
|
addr_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fd_ != -1) {
|
||||||
|
::close(fd_);
|
||||||
|
fd_ = -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
size_ = 0;
|
||||||
|
}
|
||||||
inline int close_socket(socket_t sock) {
|
inline int close_socket(socket_t sock) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return closesocket(sock);
|
return closesocket(sock);
|
||||||
@@ -3116,9 +3303,10 @@ inline constexpr unsigned int operator"" _t(const char *s, size_t l) {
|
|||||||
|
|
||||||
} // namespace udl
|
} // namespace udl
|
||||||
|
|
||||||
inline const char *
|
inline std::string
|
||||||
find_content_type(const std::string &path,
|
find_content_type(const std::string &path,
|
||||||
const std::map<std::string, std::string> &user_data) {
|
const std::map<std::string, std::string> &user_data,
|
||||||
|
const std::string &default_content_type) {
|
||||||
auto ext = file_extension(path);
|
auto ext = file_extension(path);
|
||||||
|
|
||||||
auto it = user_data.find(ext);
|
auto it = user_data.find(ext);
|
||||||
@@ -3127,7 +3315,8 @@ find_content_type(const std::string &path,
|
|||||||
using udl::operator""_t;
|
using udl::operator""_t;
|
||||||
|
|
||||||
switch (str2tag(ext)) {
|
switch (str2tag(ext)) {
|
||||||
default: return nullptr;
|
default: return default_content_type;
|
||||||
|
|
||||||
case "css"_t: return "text/css";
|
case "css"_t: return "text/css";
|
||||||
case "csv"_t: return "text/csv";
|
case "csv"_t: return "text/csv";
|
||||||
case "htm"_t:
|
case "htm"_t:
|
||||||
@@ -3180,76 +3369,6 @@ find_content_type(const std::string &path,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const char *status_message(int status) {
|
|
||||||
switch (status) {
|
|
||||||
case 100: return "Continue";
|
|
||||||
case 101: return "Switching Protocol";
|
|
||||||
case 102: return "Processing";
|
|
||||||
case 103: return "Early Hints";
|
|
||||||
case 200: return "OK";
|
|
||||||
case 201: return "Created";
|
|
||||||
case 202: return "Accepted";
|
|
||||||
case 203: return "Non-Authoritative Information";
|
|
||||||
case 204: return "No Content";
|
|
||||||
case 205: return "Reset Content";
|
|
||||||
case 206: return "Partial Content";
|
|
||||||
case 207: return "Multi-Status";
|
|
||||||
case 208: return "Already Reported";
|
|
||||||
case 226: return "IM Used";
|
|
||||||
case 300: return "Multiple Choice";
|
|
||||||
case 301: return "Moved Permanently";
|
|
||||||
case 302: return "Found";
|
|
||||||
case 303: return "See Other";
|
|
||||||
case 304: return "Not Modified";
|
|
||||||
case 305: return "Use Proxy";
|
|
||||||
case 306: return "unused";
|
|
||||||
case 307: return "Temporary Redirect";
|
|
||||||
case 308: return "Permanent Redirect";
|
|
||||||
case 400: return "Bad Request";
|
|
||||||
case 401: return "Unauthorized";
|
|
||||||
case 402: return "Payment Required";
|
|
||||||
case 403: return "Forbidden";
|
|
||||||
case 404: return "Not Found";
|
|
||||||
case 405: return "Method Not Allowed";
|
|
||||||
case 406: return "Not Acceptable";
|
|
||||||
case 407: return "Proxy Authentication Required";
|
|
||||||
case 408: return "Request Timeout";
|
|
||||||
case 409: return "Conflict";
|
|
||||||
case 410: return "Gone";
|
|
||||||
case 411: return "Length Required";
|
|
||||||
case 412: return "Precondition Failed";
|
|
||||||
case 413: return "Payload Too Large";
|
|
||||||
case 414: return "URI Too Long";
|
|
||||||
case 415: return "Unsupported Media Type";
|
|
||||||
case 416: return "Range Not Satisfiable";
|
|
||||||
case 417: return "Expectation Failed";
|
|
||||||
case 418: return "I'm a teapot";
|
|
||||||
case 421: return "Misdirected Request";
|
|
||||||
case 422: return "Unprocessable Entity";
|
|
||||||
case 423: return "Locked";
|
|
||||||
case 424: return "Failed Dependency";
|
|
||||||
case 425: return "Too Early";
|
|
||||||
case 426: return "Upgrade Required";
|
|
||||||
case 428: return "Precondition Required";
|
|
||||||
case 429: return "Too Many Requests";
|
|
||||||
case 431: return "Request Header Fields Too Large";
|
|
||||||
case 451: return "Unavailable For Legal Reasons";
|
|
||||||
case 501: return "Not Implemented";
|
|
||||||
case 502: return "Bad Gateway";
|
|
||||||
case 503: return "Service Unavailable";
|
|
||||||
case 504: return "Gateway Timeout";
|
|
||||||
case 505: return "HTTP Version Not Supported";
|
|
||||||
case 506: return "Variant Also Negotiates";
|
|
||||||
case 507: return "Insufficient Storage";
|
|
||||||
case 508: return "Loop Detected";
|
|
||||||
case 510: return "Not Extended";
|
|
||||||
case 511: return "Network Authentication Required";
|
|
||||||
|
|
||||||
default:
|
|
||||||
case 500: return "Internal Server Error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool can_compress_content_type(const std::string &content_type) {
|
inline bool can_compress_content_type(const std::string &content_type) {
|
||||||
using udl::operator""_t;
|
using udl::operator""_t;
|
||||||
|
|
||||||
@@ -3384,16 +3503,12 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
|
|||||||
data += strm_.avail_in;
|
data += strm_.avail_in;
|
||||||
|
|
||||||
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
|
||||||
while (strm_.avail_in > 0) {
|
while (strm_.avail_in > 0 && ret == Z_OK) {
|
||||||
strm_.avail_out = static_cast<uInt>(buff.size());
|
strm_.avail_out = static_cast<uInt>(buff.size());
|
||||||
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
|
||||||
|
|
||||||
auto prev_avail_in = strm_.avail_in;
|
|
||||||
|
|
||||||
ret = inflate(&strm_, Z_NO_FLUSH);
|
ret = inflate(&strm_, Z_NO_FLUSH);
|
||||||
|
|
||||||
if (prev_avail_in - strm_.avail_in == 0) { return false; }
|
|
||||||
|
|
||||||
assert(ret != Z_STREAM_ERROR);
|
assert(ret != Z_STREAM_ERROR);
|
||||||
switch (ret) {
|
switch (ret) {
|
||||||
case Z_NEED_DICT:
|
case Z_NEED_DICT:
|
||||||
@@ -3770,7 +3885,7 @@ bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status,
|
|||||||
} else if (!has_header(x.headers, "Content-Length")) {
|
} else if (!has_header(x.headers, "Content-Length")) {
|
||||||
ret = read_content_without_length(strm, out);
|
ret = read_content_without_length(strm, out);
|
||||||
} else {
|
} else {
|
||||||
auto len = get_header_value<uint64_t>(x.headers, "Content-Length");
|
auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0);
|
||||||
if (len > payload_max_length) {
|
if (len > payload_max_length) {
|
||||||
exceed_payload_max_length = true;
|
exceed_payload_max_length = true;
|
||||||
skip_content_with_length(strm, len);
|
skip_content_with_length(strm, len);
|
||||||
@@ -4066,14 +4181,34 @@ inline bool parse_multipart_boundary(const std::string &content_type,
|
|||||||
if (pos == std::string::npos) { return false; }
|
if (pos == std::string::npos) { return false; }
|
||||||
auto end = content_type.find(';', pos);
|
auto end = content_type.find(';', pos);
|
||||||
auto beg = pos + strlen(boundary_keyword);
|
auto beg = pos + strlen(boundary_keyword);
|
||||||
boundary = content_type.substr(beg, end - beg);
|
boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg));
|
||||||
if (boundary.length() >= 2 && boundary.front() == '"' &&
|
|
||||||
boundary.back() == '"') {
|
|
||||||
boundary = boundary.substr(1, boundary.size() - 2);
|
|
||||||
}
|
|
||||||
return !boundary.empty();
|
return !boundary.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void parse_disposition_params(const std::string &s, Params ¶ms) {
|
||||||
|
std::set<std::string> cache;
|
||||||
|
split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) {
|
||||||
|
std::string kv(b, e);
|
||||||
|
if (cache.find(kv) != cache.end()) { return; }
|
||||||
|
cache.insert(kv);
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string val;
|
||||||
|
split(b, e, '=', [&](const char *b2, const char *e2) {
|
||||||
|
if (key.empty()) {
|
||||||
|
key.assign(b2, e2);
|
||||||
|
} else {
|
||||||
|
val.assign(b2, e2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!key.empty()) {
|
||||||
|
params.emplace(trim_double_quotes_copy((key)),
|
||||||
|
trim_double_quotes_copy((val)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||||
inline bool parse_range_header(const std::string &s, Ranges &ranges) {
|
inline bool parse_range_header(const std::string &s, Ranges &ranges) {
|
||||||
#else
|
#else
|
||||||
@@ -4084,7 +4219,7 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
|
|||||||
if (std::regex_match(s, m, re_first_range)) {
|
if (std::regex_match(s, m, re_first_range)) {
|
||||||
auto pos = static_cast<size_t>(m.position(1));
|
auto pos = static_cast<size_t>(m.position(1));
|
||||||
auto len = static_cast<size_t>(m.length(1));
|
auto len = static_cast<size_t>(m.length(1));
|
||||||
bool all_valid_ranges = true;
|
auto all_valid_ranges = true;
|
||||||
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
|
split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) {
|
||||||
if (!all_valid_ranges) return;
|
if (!all_valid_ranges) return;
|
||||||
static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
|
static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))");
|
||||||
@@ -4131,11 +4266,6 @@ public:
|
|||||||
bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
|
bool parse(const char *buf, size_t n, const ContentReceiver &content_callback,
|
||||||
const MultipartContentHeader &header_callback) {
|
const MultipartContentHeader &header_callback) {
|
||||||
|
|
||||||
// TODO: support 'filename*'
|
|
||||||
static const std::regex re_content_disposition(
|
|
||||||
R"~(^Content-Disposition:\s*form-data;\s*name="(.*?)"(?:;\s*filename="(.*?)")?(?:;\s*filename\*=\S+)?\s*$)~",
|
|
||||||
std::regex_constants::icase);
|
|
||||||
|
|
||||||
buf_append(buf, n);
|
buf_append(buf, n);
|
||||||
|
|
||||||
while (buf_size() > 0) {
|
while (buf_size() > 0) {
|
||||||
@@ -4173,10 +4303,40 @@ public:
|
|||||||
if (start_with_case_ignore(header, header_name)) {
|
if (start_with_case_ignore(header, header_name)) {
|
||||||
file_.content_type = trim_copy(header.substr(header_name.size()));
|
file_.content_type = trim_copy(header.substr(header_name.size()));
|
||||||
} else {
|
} else {
|
||||||
|
static const std::regex re_content_disposition(
|
||||||
|
R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~",
|
||||||
|
std::regex_constants::icase);
|
||||||
|
|
||||||
std::smatch m;
|
std::smatch m;
|
||||||
if (std::regex_match(header, m, re_content_disposition)) {
|
if (std::regex_match(header, m, re_content_disposition)) {
|
||||||
file_.name = m[1];
|
Params params;
|
||||||
file_.filename = m[2];
|
parse_disposition_params(m[1], params);
|
||||||
|
|
||||||
|
auto it = params.find("name");
|
||||||
|
if (it != params.end()) {
|
||||||
|
file_.name = it->second;
|
||||||
|
} else {
|
||||||
|
is_valid_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
it = params.find("filename");
|
||||||
|
if (it != params.end()) { file_.filename = it->second; }
|
||||||
|
|
||||||
|
it = params.find("filename*");
|
||||||
|
if (it != params.end()) {
|
||||||
|
// Only allow UTF-8 enconnding...
|
||||||
|
static const std::regex re_rfc5987_encoding(
|
||||||
|
R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase);
|
||||||
|
|
||||||
|
std::smatch m2;
|
||||||
|
if (std::regex_match(it->second, m2, re_rfc5987_encoding)) {
|
||||||
|
file_.filename = decode_url(m2[1], false); // override...
|
||||||
|
} else {
|
||||||
|
is_valid_ = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
is_valid_ = false;
|
is_valid_ = false;
|
||||||
return false;
|
return false;
|
||||||
@@ -4217,9 +4377,9 @@ public:
|
|||||||
buf_erase(crlf_.size());
|
buf_erase(crlf_.size());
|
||||||
state_ = 1;
|
state_ = 1;
|
||||||
} else {
|
} else {
|
||||||
if (dash_crlf_.size() > buf_size()) { return true; }
|
if (dash_.size() > buf_size()) { return true; }
|
||||||
if (buf_start_with(dash_crlf_)) {
|
if (buf_start_with(dash_)) {
|
||||||
buf_erase(dash_crlf_.size());
|
buf_erase(dash_.size());
|
||||||
is_valid_ = true;
|
is_valid_ = true;
|
||||||
buf_erase(buf_size()); // Remove epilogue
|
buf_erase(buf_size()); // Remove epilogue
|
||||||
} else {
|
} else {
|
||||||
@@ -4252,7 +4412,6 @@ private:
|
|||||||
|
|
||||||
const std::string dash_ = "--";
|
const std::string dash_ = "--";
|
||||||
const std::string crlf_ = "\r\n";
|
const std::string crlf_ = "\r\n";
|
||||||
const std::string dash_crlf_ = "--\r\n";
|
|
||||||
std::string boundary_;
|
std::string boundary_;
|
||||||
std::string dash_boundary_crlf_;
|
std::string dash_boundary_crlf_;
|
||||||
std::string crlf_dash_boundary_;
|
std::string crlf_dash_boundary_;
|
||||||
@@ -4438,12 +4597,13 @@ get_range_offset_and_length(const Request &req, size_t content_length,
|
|||||||
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
|
return std::make_pair(r.first, static_cast<size_t>(r.second - r.first) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::string make_content_range_header_field(size_t offset, size_t length,
|
inline std::string
|
||||||
size_t content_length) {
|
make_content_range_header_field(const std::pair<ssize_t, ssize_t> &range,
|
||||||
|
size_t content_length) {
|
||||||
std::string field = "bytes ";
|
std::string field = "bytes ";
|
||||||
field += std::to_string(offset);
|
if (range.first != -1) { field += std::to_string(range.first); }
|
||||||
field += "-";
|
field += "-";
|
||||||
field += std::to_string(offset + length - 1);
|
if (range.second != -1) { field += std::to_string(range.second); }
|
||||||
field += "/";
|
field += "/";
|
||||||
field += std::to_string(content_length);
|
field += std::to_string(content_length);
|
||||||
return field;
|
return field;
|
||||||
@@ -4465,21 +4625,22 @@ bool process_multipart_ranges_data(const Request &req, Response &res,
|
|||||||
ctoken("\r\n");
|
ctoken("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto offsets = get_range_offset_and_length(req, res.body.size(), i);
|
ctoken("Content-Range: ");
|
||||||
|
const auto &range = req.ranges[i];
|
||||||
|
stoken(make_content_range_header_field(range, res.content_length_));
|
||||||
|
ctoken("\r\n");
|
||||||
|
ctoken("\r\n");
|
||||||
|
|
||||||
|
auto offsets = get_range_offset_and_length(req, res.content_length_, i);
|
||||||
auto offset = offsets.first;
|
auto offset = offsets.first;
|
||||||
auto length = offsets.second;
|
auto length = offsets.second;
|
||||||
|
|
||||||
ctoken("Content-Range: ");
|
|
||||||
stoken(make_content_range_header_field(offset, length, res.body.size()));
|
|
||||||
ctoken("\r\n");
|
|
||||||
ctoken("\r\n");
|
|
||||||
if (!content(offset, length)) { return false; }
|
if (!content(offset, length)) { return false; }
|
||||||
ctoken("\r\n");
|
ctoken("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
ctoken("--");
|
ctoken("--");
|
||||||
stoken(boundary);
|
stoken(boundary);
|
||||||
ctoken("--\r\n");
|
ctoken("--");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -4816,7 +4977,7 @@ inline bool parse_www_authenticate(const Response &res,
|
|||||||
s = s.substr(pos + 1);
|
s = s.substr(pos + 1);
|
||||||
auto beg = std::sregex_iterator(s.begin(), s.end(), re);
|
auto beg = std::sregex_iterator(s.begin(), s.end(), re);
|
||||||
for (auto i = beg; i != std::sregex_iterator(); ++i) {
|
for (auto i = beg; i != std::sregex_iterator(); ++i) {
|
||||||
auto m = *i;
|
const auto &m = *i;
|
||||||
auto key = s.substr(static_cast<size_t>(m.position(1)),
|
auto key = s.substr(static_cast<size_t>(m.position(1)),
|
||||||
static_cast<size_t>(m.length(1)));
|
static_cast<size_t>(m.length(1)));
|
||||||
auto val = m.length(2) > 0
|
auto val = m.length(2) > 0
|
||||||
@@ -5442,6 +5603,11 @@ Server::set_file_extension_and_mimetype_mapping(const std::string &ext,
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline Server &Server::set_default_file_mimetype(const std::string &mime) {
|
||||||
|
default_file_mimetype_ = mime;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
inline Server &Server::set_file_request_handler(Handler handler) {
|
inline Server &Server::set_file_request_handler(Handler handler) {
|
||||||
file_request_handler_ = std::move(handler);
|
file_request_handler_ = std::move(handler);
|
||||||
return *this;
|
return *this;
|
||||||
@@ -5696,7 +5862,7 @@ inline bool Server::write_response_core(Stream &strm, bool close_connection,
|
|||||||
detail::BufferStream bstrm;
|
detail::BufferStream bstrm;
|
||||||
|
|
||||||
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status,
|
||||||
detail::status_message(res.status))) {
|
status_message(res.status))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5893,17 +6059,26 @@ inline bool Server::handle_file_request(const Request &req, Response &res,
|
|||||||
if (path.back() == '/') { path += "index.html"; }
|
if (path.back() == '/') { path += "index.html"; }
|
||||||
|
|
||||||
if (detail::is_file(path)) {
|
if (detail::is_file(path)) {
|
||||||
detail::read_file(path, res.body);
|
|
||||||
auto type =
|
|
||||||
detail::find_content_type(path, file_extension_and_mimetype_map_);
|
|
||||||
if (type) { res.set_header("Content-Type", type); }
|
|
||||||
for (const auto &kv : entry.headers) {
|
for (const auto &kv : entry.headers) {
|
||||||
res.set_header(kv.first.c_str(), kv.second);
|
res.set_header(kv.first.c_str(), kv.second);
|
||||||
}
|
}
|
||||||
res.status = req.has_header("Range") ? 206 : 200;
|
|
||||||
|
auto mm = std::make_shared<detail::mmap>(path.c_str());
|
||||||
|
if (!mm->is_open()) { return false; }
|
||||||
|
|
||||||
|
res.set_content_provider(
|
||||||
|
mm->size(),
|
||||||
|
detail::find_content_type(path, file_extension_and_mimetype_map_,
|
||||||
|
default_file_mimetype_),
|
||||||
|
[mm](size_t offset, size_t length, DataSink &sink) -> bool {
|
||||||
|
sink.write(mm->data() + offset, length);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
if (!head && file_request_handler_) {
|
if (!head && file_request_handler_) {
|
||||||
file_request_handler_(req, res);
|
file_request_handler_(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6151,10 +6326,10 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
|||||||
} else if (req.ranges.size() == 1) {
|
} else if (req.ranges.size() == 1) {
|
||||||
auto offsets =
|
auto offsets =
|
||||||
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
detail::get_range_offset_and_length(req, res.content_length_, 0);
|
||||||
auto offset = offsets.first;
|
|
||||||
length = offsets.second;
|
length = offsets.second;
|
||||||
|
|
||||||
auto content_range = detail::make_content_range_header_field(
|
auto content_range = detail::make_content_range_header_field(
|
||||||
offset, length, res.content_length_);
|
req.ranges[0], res.content_length_);
|
||||||
res.set_header("Content-Range", content_range);
|
res.set_header("Content-Range", content_range);
|
||||||
} else {
|
} else {
|
||||||
length = detail::get_multipart_ranges_data_length(req, res, boundary,
|
length = detail::get_multipart_ranges_data_length(req, res, boundary,
|
||||||
@@ -6177,13 +6352,15 @@ inline void Server::apply_ranges(const Request &req, Response &res,
|
|||||||
if (req.ranges.empty()) {
|
if (req.ranges.empty()) {
|
||||||
;
|
;
|
||||||
} else if (req.ranges.size() == 1) {
|
} else if (req.ranges.size() == 1) {
|
||||||
|
auto content_range = detail::make_content_range_header_field(
|
||||||
|
req.ranges[0], res.body.size());
|
||||||
|
res.set_header("Content-Range", content_range);
|
||||||
|
|
||||||
auto offsets =
|
auto offsets =
|
||||||
detail::get_range_offset_and_length(req, res.body.size(), 0);
|
detail::get_range_offset_and_length(req, res.body.size(), 0);
|
||||||
auto offset = offsets.first;
|
auto offset = offsets.first;
|
||||||
auto length = offsets.second;
|
auto length = offsets.second;
|
||||||
auto content_range = detail::make_content_range_header_field(
|
|
||||||
offset, length, res.body.size());
|
|
||||||
res.set_header("Content-Range", content_range);
|
|
||||||
if (offset < res.body.size()) {
|
if (offset < res.body.size()) {
|
||||||
res.body = res.body.substr(offset, length);
|
res.body = res.body.substr(offset, length);
|
||||||
} else {
|
} else {
|
||||||
@@ -6332,14 +6509,14 @@ Server::process_request(Stream &strm, bool close_connection,
|
|||||||
case 100:
|
case 100:
|
||||||
case 417:
|
case 417:
|
||||||
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
|
strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status,
|
||||||
detail::status_message(status));
|
status_message(status));
|
||||||
break;
|
break;
|
||||||
default: return write_response(strm, close_connection, req, res);
|
default: return write_response(strm, close_connection, req, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rounting
|
// Rounting
|
||||||
bool routed = false;
|
auto routed = false;
|
||||||
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
#ifdef CPPHTTPLIB_NO_EXCEPTIONS
|
||||||
routed = routing(req, res, strm);
|
routed = routing(req, res, strm);
|
||||||
#else
|
#else
|
||||||
@@ -6537,9 +6714,9 @@ inline bool ClientImpl::read_response_line(Stream &strm, const Request &req,
|
|||||||
if (!line_reader.getline()) { return false; }
|
if (!line_reader.getline()) { return false; }
|
||||||
|
|
||||||
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
|
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
|
||||||
const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
|
|
||||||
#else
|
|
||||||
const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
|
const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n");
|
||||||
|
#else
|
||||||
|
const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::cmatch m;
|
std::cmatch m;
|
||||||
@@ -6699,6 +6876,21 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|||||||
|
|
||||||
if (!ret) { return false; }
|
if (!ret) { return false; }
|
||||||
|
|
||||||
|
if (res.get_header_value("Connection") == "close" ||
|
||||||
|
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
||||||
|
// TODO this requires a not-entirely-obvious chain of calls to be correct
|
||||||
|
// for this to be safe.
|
||||||
|
|
||||||
|
// This is safe to call because handle_request is only called by send_
|
||||||
|
// which locks the request mutex during the process. It would be a bug
|
||||||
|
// to call it from a different thread since it's a thread-safety issue
|
||||||
|
// to do these things to the socket if another thread is using the socket.
|
||||||
|
std::lock_guard<std::mutex> guard(socket_mutex_);
|
||||||
|
shutdown_ssl(socket_, true);
|
||||||
|
shutdown_socket(socket_);
|
||||||
|
close_socket(socket_);
|
||||||
|
}
|
||||||
|
|
||||||
if (300 < res.status && res.status < 400 && follow_location_) {
|
if (300 < res.status && res.status < 400 && follow_location_) {
|
||||||
req = req_save;
|
req = req_save;
|
||||||
ret = redirect(req, res, error);
|
ret = redirect(req, res, error);
|
||||||
@@ -7113,24 +7305,6 @@ inline bool ClientImpl::process_request(Stream &strm, Request &req,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.get_header_value("Connection") == "close" ||
|
|
||||||
(res.version == "HTTP/1.0" && res.reason != "Connection established")) {
|
|
||||||
// TODO this requires a not-entirely-obvious chain of calls to be correct
|
|
||||||
// for this to be safe. Maybe a code refactor (such as moving this out to
|
|
||||||
// the send function and getting rid of the recursiveness of the mutex)
|
|
||||||
// could make this more obvious.
|
|
||||||
|
|
||||||
// This is safe to call because process_request is only called by
|
|
||||||
// handle_request which is only called by send, which locks the request
|
|
||||||
// mutex during the process. It would be a bug to call it from a different
|
|
||||||
// thread since it's a thread-safety issue to do these things to the socket
|
|
||||||
// if another thread is using the socket.
|
|
||||||
std::lock_guard<std::mutex> guard(socket_mutex_);
|
|
||||||
shutdown_ssl(socket_, true);
|
|
||||||
shutdown_socket(socket_);
|
|
||||||
close_socket(socket_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
if (logger_) { logger_(req, res); }
|
if (logger_) { logger_(req, res); }
|
||||||
|
|
||||||
@@ -7158,7 +7332,7 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
DataSink cur_sink;
|
DataSink cur_sink;
|
||||||
bool has_data = true;
|
auto has_data = true;
|
||||||
cur_sink.write = sink.write;
|
cur_sink.write = sink.write;
|
||||||
cur_sink.done = [&]() { has_data = false; };
|
cur_sink.done = [&]() { has_data = false; };
|
||||||
|
|
||||||
@@ -8238,14 +8412,14 @@ inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) {
|
|||||||
inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
||||||
bool &success, Error &error) {
|
bool &success, Error &error) {
|
||||||
success = true;
|
success = true;
|
||||||
Response res2;
|
Response proxy_res;
|
||||||
if (!detail::process_client_socket(
|
if (!detail::process_client_socket(
|
||||||
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||||
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||||
Request req2;
|
Request req2;
|
||||||
req2.method = "CONNECT";
|
req2.method = "CONNECT";
|
||||||
req2.path = host_and_port_;
|
req2.path = host_and_port_;
|
||||||
return process_request(strm, req2, res2, false, error);
|
return process_request(strm, req2, proxy_res, false, error);
|
||||||
})) {
|
})) {
|
||||||
// Thread-safe to close everything because we are assuming there are no
|
// Thread-safe to close everything because we are assuming there are no
|
||||||
// requests in flight
|
// requests in flight
|
||||||
@@ -8256,12 +8430,12 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res2.status == 407) {
|
if (proxy_res.status == 407) {
|
||||||
if (!proxy_digest_auth_username_.empty() &&
|
if (!proxy_digest_auth_username_.empty() &&
|
||||||
!proxy_digest_auth_password_.empty()) {
|
!proxy_digest_auth_password_.empty()) {
|
||||||
std::map<std::string, std::string> auth;
|
std::map<std::string, std::string> auth;
|
||||||
if (detail::parse_www_authenticate(res2, auth, true)) {
|
if (detail::parse_www_authenticate(proxy_res, auth, true)) {
|
||||||
Response res3;
|
proxy_res = Response();
|
||||||
if (!detail::process_client_socket(
|
if (!detail::process_client_socket(
|
||||||
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
socket.sock, read_timeout_sec_, read_timeout_usec_,
|
||||||
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) {
|
||||||
@@ -8272,7 +8446,7 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
|||||||
req3, auth, 1, detail::random_string(10),
|
req3, auth, 1, detail::random_string(10),
|
||||||
proxy_digest_auth_username_, proxy_digest_auth_password_,
|
proxy_digest_auth_username_, proxy_digest_auth_password_,
|
||||||
true));
|
true));
|
||||||
return process_request(strm, req3, res3, false, error);
|
return process_request(strm, req3, proxy_res, false, error);
|
||||||
})) {
|
})) {
|
||||||
// Thread-safe to close everything because we are assuming there are
|
// Thread-safe to close everything because we are assuming there are
|
||||||
// no requests in flight
|
// no requests in flight
|
||||||
@@ -8283,17 +8457,28 @@ inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
res = res2;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If status code is not 200, proxy request is failed.
|
||||||
|
// Set error to ProxyConnection and return proxy response
|
||||||
|
// as the response of the request
|
||||||
|
if (proxy_res.status != 200) {
|
||||||
|
error = Error::ProxyConnection;
|
||||||
|
res = std::move(proxy_res);
|
||||||
|
// Thread-safe to close everything because we are assuming there are
|
||||||
|
// no requests in flight
|
||||||
|
shutdown_ssl(socket, true);
|
||||||
|
shutdown_socket(socket);
|
||||||
|
close_socket(socket);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool SSLClient::load_certs() {
|
inline bool SSLClient::load_certs() {
|
||||||
bool ret = true;
|
auto ret = true;
|
||||||
|
|
||||||
std::call_once(initialize_cert_, [&]() {
|
std::call_once(initialize_cert_, [&]() {
|
||||||
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
std::lock_guard<std::mutex> guard(ctx_mutex_);
|
||||||
|
|||||||
+8
-1
@@ -86,6 +86,12 @@ subdir(join_paths('www', 'dir'))
|
|||||||
subdir(join_paths('www2', 'dir'))
|
subdir(join_paths('www2', 'dir'))
|
||||||
subdir(join_paths('www3', 'dir'))
|
subdir(join_paths('www3', 'dir'))
|
||||||
|
|
||||||
|
# GoogleTest 1.13.0 requires C++14
|
||||||
|
test_options = []
|
||||||
|
if gtest_dep.version().version_compare('>=1.13.0')
|
||||||
|
test_options += 'cpp_std=c++14'
|
||||||
|
endif
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'main',
|
'main',
|
||||||
executable(
|
executable(
|
||||||
@@ -94,7 +100,8 @@ test(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
cpp_httplib_dep,
|
cpp_httplib_dep,
|
||||||
gtest_dep
|
gtest_dep
|
||||||
]
|
],
|
||||||
|
override_options: test_options
|
||||||
),
|
),
|
||||||
depends: [
|
depends: [
|
||||||
key_pem,
|
key_pem,
|
||||||
|
|||||||
+224
-10
@@ -211,8 +211,7 @@ TEST(GetHeaderValueTest, DefaultValue) {
|
|||||||
|
|
||||||
TEST(GetHeaderValueTest, DefaultValueInt) {
|
TEST(GetHeaderValueTest, DefaultValueInt) {
|
||||||
Headers headers = {{"Dummy", "Dummy"}};
|
Headers headers = {{"Dummy", "Dummy"}};
|
||||||
auto val =
|
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 100);
|
||||||
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 100);
|
|
||||||
EXPECT_EQ(100ull, val);
|
EXPECT_EQ(100ull, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,8 +240,7 @@ TEST(GetHeaderValueTest, SetContent) {
|
|||||||
|
|
||||||
TEST(GetHeaderValueTest, RegularValueInt) {
|
TEST(GetHeaderValueTest, RegularValueInt) {
|
||||||
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
|
Headers headers = {{"Content-Length", "100"}, {"Dummy", "Dummy"}};
|
||||||
auto val =
|
auto val = detail::get_header_value_u64(headers, "Content-Length", 0, 0);
|
||||||
detail::get_header_value<uint64_t>(headers, "Content-Length", 0, 0);
|
|
||||||
EXPECT_EQ(100ull, val);
|
EXPECT_EQ(100ull, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1016,7 +1014,7 @@ TEST(UrlWithSpace, Redirect_Online) {
|
|||||||
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
|
auto res = cli.Get("/files/2595/310/Neat 1.4-17.jar");
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
EXPECT_EQ(18527U, res->get_header_value<uint64_t>("Content-Length"));
|
EXPECT_EQ(18527U, res->get_header_value_u64("Content-Length"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -2501,6 +2499,55 @@ TEST_F(ServerTest, StaticFileRange) {
|
|||||||
EXPECT_EQ(std::string("cd"), res->body);
|
EXPECT_EQ(std::string("cd"), res->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, StaticFileRanges) {
|
||||||
|
auto res =
|
||||||
|
cli_.Get("/dir/test.abcde", {{make_range_header({{1, 2}, {4, -1}})}});
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(206, res->status);
|
||||||
|
EXPECT_TRUE(
|
||||||
|
res->get_header_value("Content-Type")
|
||||||
|
.find(
|
||||||
|
"multipart/byteranges; boundary=--cpp-httplib-multipart-data-") ==
|
||||||
|
0);
|
||||||
|
EXPECT_EQ("265", res->get_header_value("Content-Length"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, StaticFileRangeHead) {
|
||||||
|
auto res = cli_.Head("/dir/test.abcde", {{make_range_header({{2, 3}})}});
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(206, res->status);
|
||||||
|
EXPECT_EQ("text/abcde", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("2", res->get_header_value("Content-Length"));
|
||||||
|
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, StaticFileRangeBigFile) {
|
||||||
|
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{-1, 5}})}});
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(206, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("5", res->get_header_value("Content-Length"));
|
||||||
|
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||||
|
EXPECT_EQ("LAST\n", res->body);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, StaticFileRangeBigFile2) {
|
||||||
|
auto res = cli_.Get("/dir/1MB.txt", {{make_range_header({{1, 4097}})}});
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(206, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("4097", res->get_header_value("Content-Length"));
|
||||||
|
EXPECT_EQ(true, res->has_header("Content-Range"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ServerTest, StaticFileBigFile) {
|
||||||
|
auto res = cli_.Get("/dir/1MB.txt");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(200, res->status);
|
||||||
|
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||||
|
EXPECT_EQ("1048576", res->get_header_value("Content-Length"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, InvalidBaseDirMount) {
|
TEST_F(ServerTest, InvalidBaseDirMount) {
|
||||||
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
|
EXPECT_EQ(false, svr_.set_mount_point("invalid_mount_point", "./www3"));
|
||||||
}
|
}
|
||||||
@@ -2886,9 +2933,9 @@ TEST_F(ServerTest, GetStreamedWithRangeMultipart) {
|
|||||||
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
|
cli_.Get("/streamed-with-range", {{make_range_header({{1, 2}, {4, 5}})}});
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
EXPECT_EQ(206, res->status);
|
EXPECT_EQ(206, res->status);
|
||||||
EXPECT_EQ("269", res->get_header_value("Content-Length"));
|
EXPECT_EQ("267", res->get_header_value("Content-Length"));
|
||||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||||
EXPECT_EQ(269U, res->body.size());
|
EXPECT_EQ(267U, res->body.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, GetStreamedEndless) {
|
TEST_F(ServerTest, GetStreamedEndless) {
|
||||||
@@ -2978,9 +3025,9 @@ TEST_F(ServerTest, GetWithRangeMultipart) {
|
|||||||
auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}});
|
auto res = cli_.Get("/with-range", {{make_range_header({{1, 2}, {4, 5}})}});
|
||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
EXPECT_EQ(206, res->status);
|
EXPECT_EQ(206, res->status);
|
||||||
EXPECT_EQ("269", res->get_header_value("Content-Length"));
|
EXPECT_EQ("267", res->get_header_value("Content-Length"));
|
||||||
EXPECT_EQ(false, res->has_header("Content-Range"));
|
EXPECT_EQ(false, res->has_header("Content-Range"));
|
||||||
EXPECT_EQ(269U, res->body.size());
|
EXPECT_EQ(267U, res->body.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
|
TEST_F(ServerTest, GetWithRangeMultipartOffsetGreaterThanContent) {
|
||||||
@@ -3247,7 +3294,7 @@ TEST_F(ServerTest, PutLargeFileWithGzip2) {
|
|||||||
ASSERT_TRUE(res);
|
ASSERT_TRUE(res);
|
||||||
EXPECT_EQ(200, res->status);
|
EXPECT_EQ(200, res->status);
|
||||||
EXPECT_EQ(LARGE_DATA, res->body);
|
EXPECT_EQ(LARGE_DATA, res->body);
|
||||||
EXPECT_EQ(101942u, res.get_request_header_value<uint64_t>("Content-Length"));
|
EXPECT_EQ(101942u, res.get_request_header_value_u64("Content-Length"));
|
||||||
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
|
EXPECT_EQ("gzip", res.get_request_header_value("Content-Encoding"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3330,6 +3377,59 @@ TEST(GzipDecompressor, ChunkedDecompression) {
|
|||||||
ASSERT_EQ(data, decompressed_data);
|
ASSERT_EQ(data, decompressed_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(GzipDecompressor, DeflateDecompression) {
|
||||||
|
std::string original_text = "Raw deflate without gzip";
|
||||||
|
unsigned char data[32] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
|
||||||
|
0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
|
||||||
|
0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
|
||||||
|
0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E};
|
||||||
|
std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
|
||||||
|
|
||||||
|
std::string decompressed_data;
|
||||||
|
{
|
||||||
|
httplib::detail::gzip_decompressor decompressor;
|
||||||
|
|
||||||
|
bool result = decompressor.decompress(
|
||||||
|
compressed_data.data(), compressed_data.size(),
|
||||||
|
[&](const char *decompressed_data_chunk,
|
||||||
|
size_t decompressed_data_chunk_size) {
|
||||||
|
decompressed_data.insert(decompressed_data.size(),
|
||||||
|
decompressed_data_chunk,
|
||||||
|
decompressed_data_chunk_size);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
}
|
||||||
|
ASSERT_EQ(original_text, decompressed_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(GzipDecompressor, DeflateDecompressionTrailingBytes) {
|
||||||
|
std::string original_text = "Raw deflate without gzip";
|
||||||
|
unsigned char data[40] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
|
||||||
|
0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
|
||||||
|
0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
|
||||||
|
0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
|
||||||
|
|
||||||
|
std::string decompressed_data;
|
||||||
|
{
|
||||||
|
httplib::detail::gzip_decompressor decompressor;
|
||||||
|
|
||||||
|
bool result = decompressor.decompress(
|
||||||
|
compressed_data.data(), compressed_data.size(),
|
||||||
|
[&](const char *decompressed_data_chunk,
|
||||||
|
size_t decompressed_data_chunk_size) {
|
||||||
|
decompressed_data.insert(decompressed_data.size(),
|
||||||
|
decompressed_data_chunk,
|
||||||
|
decompressed_data_chunk_size);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(result);
|
||||||
|
}
|
||||||
|
ASSERT_EQ(original_text, decompressed_data);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
TEST(GzipDecompressor, LargeRandomData) {
|
TEST(GzipDecompressor, LargeRandomData) {
|
||||||
|
|
||||||
@@ -6052,6 +6152,120 @@ TEST(MultipartFormDataTest, PutInvalidBoundaryChars) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MultipartFormDataTest, AlternateFilename) {
|
||||||
|
auto handled = false;
|
||||||
|
|
||||||
|
Server svr;
|
||||||
|
svr.Post("/test", [&](const Request &req, Response &res) {
|
||||||
|
ASSERT_EQ(3u, req.files.size());
|
||||||
|
|
||||||
|
auto it = req.files.begin();
|
||||||
|
ASSERT_EQ("file1", it->second.name);
|
||||||
|
ASSERT_EQ("A.txt", it->second.filename);
|
||||||
|
ASSERT_EQ("text/plain", it->second.content_type);
|
||||||
|
ASSERT_EQ("Content of a.txt.\r\n", it->second.content);
|
||||||
|
|
||||||
|
++it;
|
||||||
|
ASSERT_EQ("file2", it->second.name);
|
||||||
|
ASSERT_EQ("a.html", it->second.filename);
|
||||||
|
ASSERT_EQ("text/html", it->second.content_type);
|
||||||
|
ASSERT_EQ("<!DOCTYPE html><title>Content of a.html.</title>\r\n",
|
||||||
|
it->second.content);
|
||||||
|
|
||||||
|
++it;
|
||||||
|
ASSERT_EQ("text", it->second.name);
|
||||||
|
ASSERT_EQ("", it->second.filename);
|
||||||
|
ASSERT_EQ("", it->second.content_type);
|
||||||
|
ASSERT_EQ("text default", it->second.content);
|
||||||
|
|
||||||
|
res.set_content("ok", "text/plain");
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
ASSERT_TRUE(handled);
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
auto req = "POST /test HTTP/1.1\r\n"
|
||||||
|
"Content-Type: multipart/form-data;boundary=--------\r\n"
|
||||||
|
"Content-Length: 399\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"----------\r\n"
|
||||||
|
"Content-Disposition: form-data; name=\"text\"\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"text default\r\n"
|
||||||
|
"----------\r\n"
|
||||||
|
"Content-Disposition: form-data; filename*=\"UTF-8''\%41.txt\"; "
|
||||||
|
"filename=\"a.txt\"; name=\"file1\"\r\n"
|
||||||
|
"Content-Type: text/plain\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"Content of a.txt.\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"----------\r\n"
|
||||||
|
"Content-Disposition: form-data; name=\"file2\" ;filename = "
|
||||||
|
"\"a.html\"\r\n"
|
||||||
|
"Content-Type: text/html\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"<!DOCTYPE html><title>Content of a.html.</title>\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"------------\r\n";
|
||||||
|
|
||||||
|
ASSERT_TRUE(send_request(1, req));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(MultipartFormDataTest, CloseDelimiterWithoutCRLF) {
|
||||||
|
auto handled = false;
|
||||||
|
|
||||||
|
Server svr;
|
||||||
|
svr.Post("/test", [&](const Request &req, Response &) {
|
||||||
|
ASSERT_EQ(2u, req.files.size());
|
||||||
|
|
||||||
|
auto it = req.files.begin();
|
||||||
|
ASSERT_EQ("text1", it->second.name);
|
||||||
|
ASSERT_EQ("text1", it->second.content);
|
||||||
|
|
||||||
|
++it;
|
||||||
|
ASSERT_EQ("text2", it->second.name);
|
||||||
|
ASSERT_EQ("text2", it->second.content);
|
||||||
|
|
||||||
|
handled = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
thread t = thread([&] { svr.listen(HOST, PORT); });
|
||||||
|
auto se = detail::scope_exit([&] {
|
||||||
|
svr.stop();
|
||||||
|
t.join();
|
||||||
|
ASSERT_FALSE(svr.is_running());
|
||||||
|
ASSERT_TRUE(handled);
|
||||||
|
});
|
||||||
|
|
||||||
|
svr.wait_until_ready();
|
||||||
|
|
||||||
|
auto req = "POST /test HTTP/1.1\r\n"
|
||||||
|
"Content-Type: multipart/form-data;boundary=--------\r\n"
|
||||||
|
"Content-Length: 146\r\n"
|
||||||
|
"\r\n----------\r\n"
|
||||||
|
"Content-Disposition: form-data; name=\"text1\"\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"text1"
|
||||||
|
"\r\n----------\r\n"
|
||||||
|
"Content-Disposition: form-data; name=\"text2\"\r\n"
|
||||||
|
"\r\n"
|
||||||
|
"text2"
|
||||||
|
"\r\n------------";
|
||||||
|
|
||||||
|
std::string resonse;
|
||||||
|
ASSERT_TRUE(send_request(1, req, &resonse));
|
||||||
|
ASSERT_EQ("200", resonse.substr(9, 3));
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user