mirror of
https://github.com/KingDuckZ/kamokan.git
synced 2024-12-27 21:35:41 +00:00
Fail if CONTENT_TYPE is not application/x-www-form-urlencoded.
As part of the partial improvement to the POST reading code I also added a max_post_size setting which defaults to 1 MiB. POST inputs longer than that size get truncated. This is separate to max_pastie_size, which is just the size of one of the values in the POST data.
This commit is contained in:
parent
24baf67a65
commit
2f00014758
9 changed files with 87 additions and 22 deletions
|
@ -84,6 +84,7 @@ namespace {
|
|||
parSettings.add_default("resubmit_wait", "10");
|
||||
parSettings.add_default("log_file", "-");
|
||||
parSettings.add_default("highlight_css", "sh_darkness.css");
|
||||
parSettings.add_default("max_post_size", "1048576");
|
||||
}
|
||||
|
||||
void print_buildtime_info() {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "cgi_env.hpp"
|
||||
#include "cgi_environment_vars.hpp"
|
||||
#include "duckhandy/lexical_cast.hpp"
|
||||
#include "tawashi_exception.hpp"
|
||||
#include <cassert>
|
||||
#include <ciso646>
|
||||
#include <boost/spirit/include/qi_core.hpp>
|
||||
|
@ -114,6 +115,18 @@ namespace cgi {
|
|||
m_skip_path_info(calculate_skip_path_length(m_cgi_env[CGIVars::PATH_INFO], parBasePath)),
|
||||
m_request_method_type(RequestMethodType::_from_string(m_cgi_env[CGIVars::REQUEST_METHOD].data()))
|
||||
{
|
||||
{
|
||||
const std::string& content_type = m_cgi_env.at(CGIVars::CONTENT_TYPE);
|
||||
int parsed_chars;
|
||||
bool parse_ok;
|
||||
m_split_mime = string_to_mime(&content_type, parse_ok, parsed_chars);
|
||||
if (not parse_ok) {
|
||||
std::string err_msg = "Parsing failed at position " +
|
||||
std::to_string(parsed_chars) + " for input \"" +
|
||||
content_type + "\"";
|
||||
throw TawashiException(ErrorReasons::InvalidContentType, boost::string_ref(err_msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Env::~Env() noexcept = default;
|
||||
|
@ -217,6 +230,10 @@ namespace cgi {
|
|||
return retval;
|
||||
}
|
||||
|
||||
const SplitMime& Env::content_type_split() const {
|
||||
return m_split_mime;
|
||||
}
|
||||
|
||||
std::ostream& Env::print_all (std::ostream& parStream, const char* parNewline) const {
|
||||
for (std::size_t z = 0; z < m_cgi_env.size(); ++z) {
|
||||
parStream << CGIVars::_from_integral(z) <<
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "escapist.hpp"
|
||||
#include "kakoune/safe_ptr.hh"
|
||||
#include "request_method_type.hpp"
|
||||
#include "mime_split.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <boost/utility/string_ref.hpp>
|
||||
|
@ -67,6 +68,7 @@ namespace tawashi {
|
|||
const std::string& server_software() const;
|
||||
|
||||
GetMapType query_string_split() const a_pure;
|
||||
const SplitMime& content_type_split() const a_pure;
|
||||
|
||||
std::ostream& print_all (std::ostream& parStream, const char* parNewline) const;
|
||||
|
||||
|
@ -75,6 +77,7 @@ namespace tawashi {
|
|||
Escapist m_houdini;
|
||||
std::size_t m_skip_path_info;
|
||||
RequestMethodType m_request_method_type;
|
||||
SplitMime m_split_mime;
|
||||
};
|
||||
} //namespace cgi
|
||||
} //namespace tawashi
|
||||
|
|
|
@ -28,11 +28,42 @@
|
|||
#include <ciso646>
|
||||
|
||||
namespace tawashi {
|
||||
UnsupportedContentTypeException::UnsupportedContentTypeException (const boost::string_ref& parMessage) :
|
||||
TawashiException(ErrorReasons::UnsupportedContentType, parMessage)
|
||||
{
|
||||
}
|
||||
|
||||
namespace cgi {
|
||||
namespace {
|
||||
bool valid_content_type (const Env& parEnv) {
|
||||
if (parEnv.content_type_split().type != "application" or
|
||||
parEnv.content_type_split().subtype !=
|
||||
"x-www-form-urlencoded") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string read_n (std::istream& parSrc, std::size_t parSize) {
|
||||
if (0 == parSize)
|
||||
return std::string();
|
||||
|
||||
std::string original_data;
|
||||
original_data.reserve(parSize);
|
||||
std::copy_n(
|
||||
std::istream_iterator<char>(parSrc),
|
||||
parSize,
|
||||
std::back_inserter(original_data)
|
||||
);
|
||||
return sanitized_utf8(original_data);
|
||||
}
|
||||
} //unnamed namespace
|
||||
|
||||
const PostMapType& read_post (std::istream& parSrc, const Env& parEnv) {
|
||||
return read_post(parSrc, parEnv, parEnv.content_length());
|
||||
}
|
||||
|
||||
const PostMapType& read_post (std::istream& parSrc, const Env& parEnv, std::size_t parMaxLen) {
|
||||
static bool already_read = false;
|
||||
static PostMapType map;
|
||||
static std::string original_data;
|
||||
|
@ -41,22 +72,17 @@ namespace tawashi {
|
|||
assert(original_data.empty());
|
||||
assert(map.empty());
|
||||
|
||||
const auto input_len = parEnv.content_length();
|
||||
if (input_len > 0) {
|
||||
original_data.reserve(input_len);
|
||||
std::copy_n(
|
||||
std::istream_iterator<char>(parSrc),
|
||||
input_len,
|
||||
std::back_inserter(original_data)
|
||||
);
|
||||
original_data = sanitized_utf8(original_data);
|
||||
if (not valid_content_type(parEnv)) {
|
||||
throw UnsupportedContentTypeException(parEnv.content_type());
|
||||
}
|
||||
|
||||
Escapist houdini;
|
||||
for (auto& itm : split_env_vars(original_data)) {
|
||||
std::string key(houdini.unescape_url(itm.first));
|
||||
std::string val(houdini.unescape_url(itm.second));
|
||||
map[std::move(key)] = std::move(val);
|
||||
}
|
||||
const auto input_len = std::min(parMaxLen, parEnv.content_length());
|
||||
original_data = read_n(parSrc, input_len);
|
||||
Escapist houdini;
|
||||
for (auto& itm : split_env_vars(original_data)) {
|
||||
std::string key(houdini.unescape_url(itm.first));
|
||||
std::string val(houdini.unescape_url(itm.second));
|
||||
map[std::move(key)] = std::move(val);
|
||||
}
|
||||
|
||||
already_read = true;
|
||||
|
|
|
@ -17,11 +17,17 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "tawashi_exception.hpp"
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
#include <cstddef>
|
||||
|
||||
namespace tawashi {
|
||||
class UnsupportedContentTypeException : public TawashiException {
|
||||
public:
|
||||
explicit UnsupportedContentTypeException (const boost::string_ref& parMessage);
|
||||
};
|
||||
|
||||
namespace cgi {
|
||||
class Env;
|
||||
|
@ -29,5 +35,6 @@ namespace tawashi {
|
|||
typedef boost::container::flat_map<std::string, std::string> PostMapType;
|
||||
|
||||
const PostMapType& read_post (std::istream& parSrc, const Env& parEnv);
|
||||
const PostMapType& read_post (std::istream& parSrc, const Env& parEnv, std::size_t parMaxLen);
|
||||
} //namespace cgi
|
||||
} //namespace tawashi
|
||||
|
|
|
@ -27,6 +27,8 @@ namespace tawashi {
|
|||
UnkownReason,
|
||||
RedisDisconnected,
|
||||
MissingPostVariable,
|
||||
PastieNotFound
|
||||
PastieNotFound,
|
||||
InvalidContentType,
|
||||
UnsupportedContentType
|
||||
)
|
||||
} //namespace tawashi
|
||||
|
|
|
@ -50,7 +50,9 @@ namespace tawashi {
|
|||
"An unknown error was raised.",
|
||||
"Unable to connect to Redis.",
|
||||
"Request is missing a POST variable.",
|
||||
"Pastie not found."
|
||||
"Pastie not found.",
|
||||
"Invalid CONTENT_TYPE.",
|
||||
"Unsupported CONTENT_TYPE."
|
||||
};
|
||||
constexpr const auto lengths = string_lengths(err_descs);
|
||||
static_assert(err_descs.static_size == lengths.static_size, "Mismatching array sizes between strings and their lengths");
|
||||
|
|
|
@ -84,7 +84,6 @@ namespace tawashi {
|
|||
}
|
||||
|
||||
HttpHeader SubmitPasteResponse::on_process() {
|
||||
auto post = cgi::read_post(std::cin, cgi_env());
|
||||
boost::string_ref pastie;
|
||||
boost::string_ref lang;
|
||||
boost::string_ref duration;
|
||||
|
@ -92,18 +91,25 @@ namespace tawashi {
|
|||
auto statuslog = spdlog::get("statuslog");
|
||||
assert(statuslog);
|
||||
|
||||
const SettingsBag& settings = this->settings();
|
||||
try {
|
||||
auto post = cgi::read_post(std::cin, cgi_env(), settings.as<uint32_t>("max_post_size"));
|
||||
pastie = get_value_from_post(post, make_string_ref(g_post_key));
|
||||
lang = get_value_from_post_log_failure(post, make_string_ref(g_language_key));
|
||||
duration = get_value_from_post_log_failure(post, make_string_ref(g_duration_key));
|
||||
}
|
||||
catch (const UnsupportedContentTypeException& err) {
|
||||
statuslog->info(
|
||||
"Unsupported content type exception: \"{}\"",
|
||||
err.what()
|
||||
);
|
||||
return make_error_redirect(ErrorReasons::UnsupportedContentType);
|
||||
}
|
||||
catch (const TawashiException& e) {
|
||||
statuslog->error(e.what());
|
||||
return make_error_redirect(e.reason());
|
||||
}
|
||||
|
||||
lang = get_value_from_post_log_failure(post, make_string_ref(g_language_key));
|
||||
duration = get_value_from_post_log_failure(post, make_string_ref(g_duration_key));
|
||||
|
||||
const SettingsBag& settings = this->settings();
|
||||
const auto max_sz = settings.as<uint32_t>("max_pastie_size");
|
||||
if (pastie.size() < settings.as<uint32_t>("min_pastie_size")) {
|
||||
return make_error_redirect(ErrorReasons::PostLengthNotInRange);
|
||||
|
|
|
@ -53,6 +53,7 @@ TEST_CASE ("Retrieve and sanitize invalid an invalid utf-8 text from POST data",
|
|||
content_length.c_str(),
|
||||
"PATH_INFO=/",
|
||||
"REQUEST_METHOD=GET",
|
||||
"CONTENT_TYPE=application/x-www-form-urlencoded",
|
||||
nullptr
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue