2014-04-01 12:25:16 +00:00
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
|
|
#include <unordered_map>
|
2020-10-10 21:29:40 +00:00
|
|
|
#include <ios>
|
|
|
|
#include <fstream>
|
2020-10-21 01:02:09 +00:00
|
|
|
#include <sstream>
|
2022-06-22 13:00:00 +00:00
|
|
|
// S_ISREG is not defined for windows
|
|
|
|
// This defines it like suggested in https://stackoverflow.com/a/62371749
|
2022-06-22 08:54:25 +00:00
|
|
|
#if defined(_MSC_VER)
|
|
|
|
#define _CRT_INTERNAL_NONSTDC_NAMES 1
|
2022-06-22 12:54:52 +00:00
|
|
|
#endif
|
2021-01-05 14:49:10 +00:00
|
|
|
#include <sys/stat.h>
|
2022-06-22 08:54:25 +00:00
|
|
|
#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
|
|
|
|
#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
|
|
|
|
#endif
|
2016-09-21 14:11:06 +00:00
|
|
|
|
|
|
|
#include "crow/http_request.h"
|
|
|
|
#include "crow/ci_map.h"
|
2020-10-04 12:05:26 +00:00
|
|
|
#include "crow/socket_adaptors.h"
|
|
|
|
#include "crow/logging.h"
|
2020-10-04 16:11:18 +00:00
|
|
|
#include "crow/mime_types.h"
|
2021-01-05 14:49:10 +00:00
|
|
|
#include "crow/returnable.h"
|
2020-11-13 05:42:02 +00:00
|
|
|
|
2020-10-04 12:05:26 +00:00
|
|
|
|
2014-04-26 17:19:59 +00:00
|
|
|
namespace crow
|
2014-04-01 12:25:16 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
template<typename Adaptor, typename Handler, typename... Middlewares>
|
2014-08-05 18:54:38 +00:00
|
|
|
class Connection;
|
2020-11-14 02:28:07 +00:00
|
|
|
|
2022-04-11 13:01:27 +00:00
|
|
|
class Router;
|
2022-02-02 14:37:19 +00:00
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
/// HTTP response
|
2014-04-01 12:25:16 +00:00
|
|
|
struct response
|
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
template<typename Adaptor, typename Handler, typename... Middlewares>
|
2014-08-05 18:54:38 +00:00
|
|
|
friend class crow::Connection;
|
|
|
|
|
2022-04-11 13:01:27 +00:00
|
|
|
friend class Router;
|
2022-02-02 14:37:19 +00:00
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
int code{200}; ///< The Status code for the response.
|
2020-11-18 22:13:57 +00:00
|
|
|
std::string body; ///< The actual payload containing the response data.
|
2021-11-25 11:45:38 +00:00
|
|
|
ci_map headers; ///< HTTP headers.
|
2021-06-03 14:12:20 +00:00
|
|
|
|
|
|
|
#ifdef CROW_ENABLE_COMPRESSION
|
2021-01-02 19:12:04 +00:00
|
|
|
bool compressed = true; ///< If compression is enabled and this is false, the individual response will not be compressed.
|
2021-06-03 14:12:20 +00:00
|
|
|
#endif
|
2022-02-06 19:29:46 +00:00
|
|
|
bool skip_body = false; ///< Whether this is a response to a HEAD request.
|
2021-04-03 10:48:36 +00:00
|
|
|
bool manual_length_header = false; ///< Whether Crow should automatically add a "Content-Length" header.
|
2014-09-10 21:32:41 +00:00
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
/// Set the value of an existing header in the response.
|
2014-09-10 21:32:41 +00:00
|
|
|
void set_header(std::string key, std::string value)
|
|
|
|
{
|
|
|
|
headers.erase(key);
|
|
|
|
headers.emplace(std::move(key), std::move(value));
|
|
|
|
}
|
2020-11-14 02:28:07 +00:00
|
|
|
|
|
|
|
/// Add a new header to the response.
|
2014-09-10 21:32:41 +00:00
|
|
|
void add_header(std::string key, std::string value)
|
|
|
|
{
|
|
|
|
headers.emplace(std::move(key), std::move(value));
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::string& get_header_value(const std::string& key)
|
|
|
|
{
|
|
|
|
return crow::get_header_value(headers, key);
|
|
|
|
}
|
|
|
|
|
Address #534 - handling missing mime types gracefully (#536)
* Added tests for content-type to mime-type detection.
Added a custom_content_types test case that verifies that a user can
specify the mime-type through the contentType parameter upon creation
of a response. If their contentType does not appear in the mime_types
map, but looks like a valid mime type already, it should be used as the
mime type.
Validating against the full list of valid mime types
(https://www.iana.org/assignments/media-types/media-types.xhtml)
would be too intensive, so we merely verify that the parent type
(application, audio, font, text, image, etc) is a valid RFC6838
type, and that the subtype is at least one character. Thus we can
verify that custom/type fails, and incomplete strings such as
image/ and /json fail.
2022-09-12 11:16:16 +00:00
|
|
|
// naive validation of a mime-type string
|
|
|
|
static bool validate_mime_type(const std::string& candidate) noexcept
|
|
|
|
{
|
|
|
|
// Here we simply check that the candidate type starts with
|
|
|
|
// a valid parent type, and has at least one character afterwards.
|
|
|
|
std::array<std::string, 10> valid_parent_types = {
|
|
|
|
"application/", "audio/", "font/", "example/",
|
|
|
|
"image/", "message/", "model/", "multipart/",
|
|
|
|
"text/", "video/"};
|
|
|
|
for (const std::string& parent : valid_parent_types)
|
|
|
|
{
|
|
|
|
// ensure the candidate is *longer* than the parent,
|
|
|
|
// to avoid unnecessary string comparison and to
|
|
|
|
// reject zero-length subtypes.
|
|
|
|
if (candidate.size() <= parent.size())
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// strncmp is used rather than substr to avoid allocation,
|
|
|
|
// but a string_view approach would be better if Crow
|
|
|
|
// migrates to C++17.
|
|
|
|
if (strncmp(parent.c_str(), candidate.c_str(), parent.size()) == 0)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the mime type from the content type either by lookup,
|
|
|
|
// or by the content type itself, if it is a valid a mime type.
|
|
|
|
// Defaults to text/plain.
|
|
|
|
static std::string get_mime_type(const std::string& contentType)
|
|
|
|
{
|
|
|
|
const auto mimeTypeIterator = mime_types.find(contentType);
|
|
|
|
if (mimeTypeIterator != mime_types.end())
|
|
|
|
{
|
|
|
|
return mimeTypeIterator->second;
|
|
|
|
}
|
|
|
|
else if (validate_mime_type(contentType))
|
|
|
|
{
|
|
|
|
return contentType;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CROW_LOG_WARNING << "Unable to interpret mime type for content type '" << contentType << "'. Defaulting to text/plain.";
|
|
|
|
return "text/plain";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
// clang-format off
|
2014-04-01 12:25:16 +00:00
|
|
|
response() {}
|
2014-04-14 20:11:37 +00:00
|
|
|
explicit response(int code) : code(code) {}
|
2014-04-09 23:17:08 +00:00
|
|
|
response(std::string body) : body(std::move(body)) {}
|
2020-12-18 09:28:37 +00:00
|
|
|
response(int code, std::string body) : code(code), body(std::move(body)) {}
|
2021-11-25 11:45:38 +00:00
|
|
|
// clang-format on
|
|
|
|
response(returnable&& value)
|
2015-04-13 04:23:45 +00:00
|
|
|
{
|
2020-12-18 09:28:37 +00:00
|
|
|
body = value.dump();
|
2021-11-25 11:45:38 +00:00
|
|
|
set_header("Content-Type", value.content_type);
|
2015-04-13 04:23:45 +00:00
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
response(returnable& value)
|
2014-11-04 17:12:52 +00:00
|
|
|
{
|
2020-12-18 09:28:37 +00:00
|
|
|
body = value.dump();
|
2021-11-25 11:45:38 +00:00
|
|
|
set_header("Content-Type", value.content_type);
|
2014-11-04 17:12:52 +00:00
|
|
|
}
|
2021-11-25 11:45:38 +00:00
|
|
|
response(int code, returnable& value):
|
|
|
|
code(code)
|
2015-09-27 03:33:09 +00:00
|
|
|
{
|
2020-12-18 09:28:37 +00:00
|
|
|
body = value.dump();
|
2021-11-25 11:45:38 +00:00
|
|
|
set_header("Content-Type", value.content_type);
|
2015-09-27 03:33:09 +00:00
|
|
|
}
|
2022-07-05 22:14:50 +00:00
|
|
|
response(int code, returnable&& value):
|
2022-11-05 23:22:38 +00:00
|
|
|
code(code), body(value.dump())
|
2022-07-05 22:14:50 +00:00
|
|
|
{
|
|
|
|
set_header("Content-Type", std::move(value.content_type));
|
|
|
|
}
|
2014-08-05 13:38:51 +00:00
|
|
|
|
2014-04-17 14:06:41 +00:00
|
|
|
response(response&& r)
|
|
|
|
{
|
|
|
|
*this = std::move(r);
|
|
|
|
}
|
2021-11-21 16:00:44 +00:00
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
response(std::string contentType, std::string body):
|
|
|
|
body(std::move(body))
|
2021-08-29 06:49:18 +00:00
|
|
|
{
|
Address #534 - handling missing mime types gracefully (#536)
* Added tests for content-type to mime-type detection.
Added a custom_content_types test case that verifies that a user can
specify the mime-type through the contentType parameter upon creation
of a response. If their contentType does not appear in the mime_types
map, but looks like a valid mime type already, it should be used as the
mime type.
Validating against the full list of valid mime types
(https://www.iana.org/assignments/media-types/media-types.xhtml)
would be too intensive, so we merely verify that the parent type
(application, audio, font, text, image, etc) is a valid RFC6838
type, and that the subtype is at least one character. Thus we can
verify that custom/type fails, and incomplete strings such as
image/ and /json fail.
2022-09-12 11:16:16 +00:00
|
|
|
set_header("Content-Type", get_mime_type(contentType));
|
2021-08-29 06:49:18 +00:00
|
|
|
}
|
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
response(int code, std::string contentType, std::string body):
|
|
|
|
code(code), body(std::move(body))
|
2021-08-29 11:06:13 +00:00
|
|
|
{
|
Address #534 - handling missing mime types gracefully (#536)
* Added tests for content-type to mime-type detection.
Added a custom_content_types test case that verifies that a user can
specify the mime-type through the contentType parameter upon creation
of a response. If their contentType does not appear in the mime_types
map, but looks like a valid mime type already, it should be used as the
mime type.
Validating against the full list of valid mime types
(https://www.iana.org/assignments/media-types/media-types.xhtml)
would be too intensive, so we merely verify that the parent type
(application, audio, font, text, image, etc) is a valid RFC6838
type, and that the subtype is at least one character. Thus we can
verify that custom/type fails, and incomplete strings such as
image/ and /json fail.
2022-09-12 11:16:16 +00:00
|
|
|
set_header("Content-Type", get_mime_type(contentType));
|
2021-08-29 11:06:13 +00:00
|
|
|
}
|
2014-08-05 13:38:51 +00:00
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
response& operator=(const response& r) = delete;
|
2014-08-05 18:54:38 +00:00
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
response& operator=(response&& r) noexcept
|
2014-04-17 14:06:41 +00:00
|
|
|
{
|
|
|
|
body = std::move(r.body);
|
|
|
|
code = r.code;
|
|
|
|
headers = std::move(r.headers);
|
2015-02-20 03:00:15 +00:00
|
|
|
completed_ = r.completed_;
|
2021-06-28 16:05:34 +00:00
|
|
|
file_info = std::move(r.file_info);
|
2014-04-17 14:06:41 +00:00
|
|
|
return *this;
|
|
|
|
}
|
2014-08-05 13:38:51 +00:00
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
/// Check if the response has completed (whether response.end() has been called)
|
2014-09-06 19:30:53 +00:00
|
|
|
bool is_completed() const noexcept
|
|
|
|
{
|
|
|
|
return completed_;
|
|
|
|
}
|
|
|
|
|
2014-08-05 18:54:38 +00:00
|
|
|
void clear()
|
|
|
|
{
|
|
|
|
body.clear();
|
|
|
|
code = 200;
|
|
|
|
headers.clear();
|
|
|
|
completed_ = false;
|
2021-02-12 12:21:59 +00:00
|
|
|
file_info = static_file_info{};
|
2014-08-05 18:54:38 +00:00
|
|
|
}
|
|
|
|
|
2020-12-26 16:54:41 +00:00
|
|
|
/// Return a "Temporary Redirect" response.
|
2021-12-03 03:39:23 +00:00
|
|
|
|
2020-12-26 16:54:41 +00:00
|
|
|
///
|
|
|
|
/// Location can either be a route or a full URL.
|
2016-10-03 13:32:16 +00:00
|
|
|
void redirect(const std::string& location)
|
2020-12-26 16:54:41 +00:00
|
|
|
{
|
|
|
|
code = 307;
|
|
|
|
set_header("Location", location);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a "Permanent Redirect" response.
|
2021-12-03 03:39:23 +00:00
|
|
|
|
2020-12-26 16:54:41 +00:00
|
|
|
///
|
|
|
|
/// Location can either be a route or a full URL.
|
|
|
|
void redirect_perm(const std::string& location)
|
|
|
|
{
|
|
|
|
code = 308;
|
|
|
|
set_header("Location", location);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a "Found (Moved Temporarily)" response.
|
2021-12-03 03:39:23 +00:00
|
|
|
|
2020-12-26 16:54:41 +00:00
|
|
|
///
|
|
|
|
/// Location can either be a route or a full URL.
|
|
|
|
void moved(const std::string& location)
|
|
|
|
{
|
|
|
|
code = 302;
|
|
|
|
set_header("Location", location);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a "Moved Permanently" response.
|
2021-12-03 03:39:23 +00:00
|
|
|
|
2020-12-26 16:54:41 +00:00
|
|
|
///
|
|
|
|
/// Location can either be a route or a full URL.
|
|
|
|
void moved_perm(const std::string& location)
|
2016-10-03 13:32:16 +00:00
|
|
|
{
|
|
|
|
code = 301;
|
|
|
|
set_header("Location", location);
|
|
|
|
}
|
|
|
|
|
2014-08-05 18:54:38 +00:00
|
|
|
void write(const std::string& body_part)
|
2014-08-05 13:38:51 +00:00
|
|
|
{
|
|
|
|
body += body_part;
|
|
|
|
}
|
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
/// Set the response completion flag and call the handler (to send the response).
|
2014-08-05 13:38:51 +00:00
|
|
|
void end()
|
|
|
|
{
|
2014-08-05 18:54:38 +00:00
|
|
|
if (!completed_)
|
|
|
|
{
|
2014-09-13 18:15:37 +00:00
|
|
|
completed_ = true;
|
2022-02-05 15:15:19 +00:00
|
|
|
if (skip_body)
|
2021-04-03 03:00:23 +00:00
|
|
|
{
|
2021-04-03 03:21:18 +00:00
|
|
|
set_header("Content-Length", std::to_string(body.size()));
|
2021-04-03 03:00:23 +00:00
|
|
|
body = "";
|
2021-04-03 10:48:36 +00:00
|
|
|
manual_length_header = true;
|
2021-04-03 03:00:23 +00:00
|
|
|
}
|
2014-08-05 18:54:38 +00:00
|
|
|
if (complete_request_handler_)
|
|
|
|
{
|
|
|
|
complete_request_handler_();
|
2024-01-26 18:38:05 +00:00
|
|
|
manual_length_header = false;
|
|
|
|
skip_body = false;
|
2014-08-05 18:54:38 +00:00
|
|
|
}
|
|
|
|
}
|
2014-08-05 13:38:51 +00:00
|
|
|
}
|
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
/// Same as end() except it adds a body part right before ending.
|
2014-08-05 13:38:51 +00:00
|
|
|
void end(const std::string& body_part)
|
|
|
|
{
|
|
|
|
body += body_part;
|
|
|
|
end();
|
|
|
|
}
|
2014-08-05 18:54:38 +00:00
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
/// Check if the connection is still alive (usually by checking the socket status).
|
2014-08-06 16:18:33 +00:00
|
|
|
bool is_alive()
|
|
|
|
{
|
|
|
|
return is_alive_helper_ && is_alive_helper_();
|
|
|
|
}
|
2020-11-12 13:27:26 +00:00
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
/// Check whether the response has a static file defined.
|
|
|
|
bool is_static_type()
|
|
|
|
{
|
|
|
|
return file_info.path.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This constains metadata (coming from the `stat` command) related to any static files associated with this response.
|
2021-12-03 03:39:23 +00:00
|
|
|
|
2020-11-14 02:28:07 +00:00
|
|
|
///
|
2021-11-25 11:45:38 +00:00
|
|
|
/// Either a static file or a string body can be returned as 1 response.
|
|
|
|
struct static_file_info
|
|
|
|
{
|
2020-10-04 12:05:26 +00:00
|
|
|
std::string path = "";
|
|
|
|
struct stat statbuf;
|
|
|
|
int statResult;
|
|
|
|
};
|
|
|
|
|
2022-02-11 09:48:33 +00:00
|
|
|
/// Return a static file as the response body
|
2021-11-25 11:45:38 +00:00
|
|
|
void set_static_file_info(std::string path)
|
|
|
|
{
|
2022-01-11 18:48:51 +00:00
|
|
|
utility::sanitize_filename(path);
|
2022-02-11 09:48:33 +00:00
|
|
|
set_static_file_info_unsafe(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return a static file as the response body without sanitizing the path (use set_static_file_info instead)
|
|
|
|
void set_static_file_info_unsafe(std::string path)
|
|
|
|
{
|
2020-10-04 12:05:26 +00:00
|
|
|
file_info.path = path;
|
|
|
|
file_info.statResult = stat(file_info.path.c_str(), &file_info.statbuf);
|
2021-06-03 14:12:20 +00:00
|
|
|
#ifdef CROW_ENABLE_COMPRESSION
|
2021-01-21 03:46:54 +00:00
|
|
|
compressed = false;
|
2021-06-03 14:12:20 +00:00
|
|
|
#endif
|
2022-06-15 07:20:11 +00:00
|
|
|
if (file_info.statResult == 0 && S_ISREG(file_info.statbuf.st_mode))
|
2020-10-04 12:05:26 +00:00
|
|
|
{
|
2020-10-04 16:11:18 +00:00
|
|
|
std::size_t last_dot = path.find_last_of(".");
|
2021-11-25 11:45:38 +00:00
|
|
|
std::string extension = path.substr(last_dot + 1);
|
2020-10-04 12:05:26 +00:00
|
|
|
code = 200;
|
2022-04-21 23:51:22 +00:00
|
|
|
this->add_header("Content-Length", std::to_string(file_info.statbuf.st_size));
|
2020-10-21 01:02:09 +00:00
|
|
|
|
2022-04-16 21:35:44 +00:00
|
|
|
if (!extension.empty())
|
2021-11-25 11:45:38 +00:00
|
|
|
{
|
Address #534 - handling missing mime types gracefully (#536)
* Added tests for content-type to mime-type detection.
Added a custom_content_types test case that verifies that a user can
specify the mime-type through the contentType parameter upon creation
of a response. If their contentType does not appear in the mime_types
map, but looks like a valid mime type already, it should be used as the
mime type.
Validating against the full list of valid mime types
(https://www.iana.org/assignments/media-types/media-types.xhtml)
would be too intensive, so we merely verify that the parent type
(application, audio, font, text, image, etc) is a valid RFC6838
type, and that the subtype is at least one character. Thus we can
verify that custom/type fails, and incomplete strings such as
image/ and /json fail.
2022-09-12 11:16:16 +00:00
|
|
|
this->add_header("Content-Type", get_mime_type(extension));
|
2020-10-10 21:54:20 +00:00
|
|
|
}
|
2020-10-04 12:05:26 +00:00
|
|
|
}
|
2020-10-11 16:00:24 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
code = 404;
|
2022-02-09 17:57:12 +00:00
|
|
|
file_info.path.clear();
|
2020-10-11 16:00:24 +00:00
|
|
|
}
|
2020-10-04 12:05:26 +00:00
|
|
|
}
|
|
|
|
|
2021-11-25 11:45:38 +00:00
|
|
|
private:
|
|
|
|
bool completed_{};
|
|
|
|
std::function<void()> complete_request_handler_;
|
|
|
|
std::function<bool()> is_alive_helper_;
|
|
|
|
static_file_info file_info;
|
2014-04-01 12:25:16 +00:00
|
|
|
};
|
2021-11-25 11:45:38 +00:00
|
|
|
} // namespace crow
|