mirror of
https://github.com/CrowCpp/Crow.git
synced 2024-06-07 21:10:44 +00:00
* 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.
This commit is contained in:
parent
c0bfaa7709
commit
0b90bb486c
@ -65,6 +65,57 @@ namespace crow
|
||||
return crow::get_header_value(headers, key);
|
||||
}
|
||||
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// clang-format off
|
||||
response() {}
|
||||
explicit response(int code) : code(code) {}
|
||||
@ -101,13 +152,13 @@ namespace crow
|
||||
response(std::string contentType, std::string body):
|
||||
body(std::move(body))
|
||||
{
|
||||
set_header("Content-Type", mime_types.at(contentType));
|
||||
set_header("Content-Type", get_mime_type(contentType));
|
||||
}
|
||||
|
||||
response(int code, std::string contentType, std::string body):
|
||||
code(code), body(std::move(body))
|
||||
{
|
||||
set_header("Content-Type", mime_types.at(contentType));
|
||||
set_header("Content-Type", get_mime_type(contentType));
|
||||
}
|
||||
|
||||
response& operator=(const response& r) = delete;
|
||||
@ -255,15 +306,7 @@ namespace crow
|
||||
|
||||
if (!extension.empty())
|
||||
{
|
||||
const auto mimeType = mime_types.find(extension);
|
||||
if (mimeType != mime_types.end())
|
||||
{
|
||||
this->add_header("Content-Type", mimeType->second);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->add_header("Content-Type", "text/plain");
|
||||
}
|
||||
this->add_header("Content-Type", get_mime_type(extension));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -290,6 +290,34 @@ TEST_CASE("simple_response_routing_params")
|
||||
CHECK("hello" == rp.get<string>(0));
|
||||
} // simple_response_routing_params
|
||||
|
||||
TEST_CASE("custom_content_types")
|
||||
{
|
||||
// standard behaviour: content type is a key of mime_types
|
||||
CHECK("text/html" == response("html", "").get_header_value("Content-Type"));
|
||||
CHECK("image/jpeg" == response("jpg", "").get_header_value("Content-Type"));
|
||||
CHECK("video/mpeg" == response("mpg", "").get_header_value("Content-Type"));
|
||||
|
||||
// content type is already a valid mime type
|
||||
CHECK("text/csv" == response("text/csv", "").get_header_value("Content-Type"));
|
||||
CHECK("application/xhtml+xml" == response("application/xhtml+xml", "").get_header_value("Content-Type"));
|
||||
CHECK("font/custom;parameters=ok" == response("font/custom;parameters=ok", "").get_header_value("Content-Type"));
|
||||
|
||||
// content type looks like a mime type, but is invalid
|
||||
// note: RFC6838 only allows a limited set of parent types:
|
||||
// https://datatracker.ietf.org/doc/html/rfc6838#section-4.2.7
|
||||
//
|
||||
// These types are: application, audio, font, example, image, message,
|
||||
// model, multipart, text, video
|
||||
|
||||
CHECK("text/plain" == response("custom/type", "").get_header_value("Content-Type"));
|
||||
|
||||
// content type does not look like a mime type.
|
||||
CHECK("text/plain" == response("notarealextension", "").get_header_value("Content-Type"));
|
||||
CHECK("text/plain" == response("image/", "").get_header_value("Content-Type"));
|
||||
CHECK("text/plain" == response("/json", "").get_header_value("Content-Type"));
|
||||
|
||||
} // custom_content_types
|
||||
|
||||
TEST_CASE("handler_with_response")
|
||||
{
|
||||
SimpleApp app;
|
||||
|
Loading…
Reference in New Issue
Block a user