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("resubmit_wait", "10");
|
||||||
parSettings.add_default("log_file", "-");
|
parSettings.add_default("log_file", "-");
|
||||||
parSettings.add_default("highlight_css", "sh_darkness.css");
|
parSettings.add_default("highlight_css", "sh_darkness.css");
|
||||||
|
parSettings.add_default("max_post_size", "1048576");
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_buildtime_info() {
|
void print_buildtime_info() {
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "cgi_env.hpp"
|
#include "cgi_env.hpp"
|
||||||
#include "cgi_environment_vars.hpp"
|
#include "cgi_environment_vars.hpp"
|
||||||
#include "duckhandy/lexical_cast.hpp"
|
#include "duckhandy/lexical_cast.hpp"
|
||||||
|
#include "tawashi_exception.hpp"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <ciso646>
|
#include <ciso646>
|
||||||
#include <boost/spirit/include/qi_core.hpp>
|
#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_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()))
|
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;
|
Env::~Env() noexcept = default;
|
||||||
|
@ -217,6 +230,10 @@ namespace cgi {
|
||||||
return retval;
|
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 {
|
std::ostream& Env::print_all (std::ostream& parStream, const char* parNewline) const {
|
||||||
for (std::size_t z = 0; z < m_cgi_env.size(); ++z) {
|
for (std::size_t z = 0; z < m_cgi_env.size(); ++z) {
|
||||||
parStream << CGIVars::_from_integral(z) <<
|
parStream << CGIVars::_from_integral(z) <<
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "escapist.hpp"
|
#include "escapist.hpp"
|
||||||
#include "kakoune/safe_ptr.hh"
|
#include "kakoune/safe_ptr.hh"
|
||||||
#include "request_method_type.hpp"
|
#include "request_method_type.hpp"
|
||||||
|
#include "mime_split.hpp"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <boost/utility/string_ref.hpp>
|
#include <boost/utility/string_ref.hpp>
|
||||||
|
@ -67,6 +68,7 @@ namespace tawashi {
|
||||||
const std::string& server_software() const;
|
const std::string& server_software() const;
|
||||||
|
|
||||||
GetMapType query_string_split() const a_pure;
|
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;
|
std::ostream& print_all (std::ostream& parStream, const char* parNewline) const;
|
||||||
|
|
||||||
|
@ -75,6 +77,7 @@ namespace tawashi {
|
||||||
Escapist m_houdini;
|
Escapist m_houdini;
|
||||||
std::size_t m_skip_path_info;
|
std::size_t m_skip_path_info;
|
||||||
RequestMethodType m_request_method_type;
|
RequestMethodType m_request_method_type;
|
||||||
|
SplitMime m_split_mime;
|
||||||
};
|
};
|
||||||
} //namespace cgi
|
} //namespace cgi
|
||||||
} //namespace tawashi
|
} //namespace tawashi
|
||||||
|
|
|
@ -28,11 +28,42 @@
|
||||||
#include <ciso646>
|
#include <ciso646>
|
||||||
|
|
||||||
namespace tawashi {
|
namespace tawashi {
|
||||||
|
UnsupportedContentTypeException::UnsupportedContentTypeException (const boost::string_ref& parMessage) :
|
||||||
|
TawashiException(ErrorReasons::UnsupportedContentType, parMessage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
namespace cgi {
|
namespace cgi {
|
||||||
namespace {
|
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
|
} //unnamed namespace
|
||||||
|
|
||||||
const PostMapType& read_post (std::istream& parSrc, const Env& parEnv) {
|
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 bool already_read = false;
|
||||||
static PostMapType map;
|
static PostMapType map;
|
||||||
static std::string original_data;
|
static std::string original_data;
|
||||||
|
@ -41,22 +72,17 @@ namespace tawashi {
|
||||||
assert(original_data.empty());
|
assert(original_data.empty());
|
||||||
assert(map.empty());
|
assert(map.empty());
|
||||||
|
|
||||||
const auto input_len = parEnv.content_length();
|
if (not valid_content_type(parEnv)) {
|
||||||
if (input_len > 0) {
|
throw UnsupportedContentTypeException(parEnv.content_type());
|
||||||
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);
|
|
||||||
|
|
||||||
Escapist houdini;
|
const auto input_len = std::min(parMaxLen, parEnv.content_length());
|
||||||
for (auto& itm : split_env_vars(original_data)) {
|
original_data = read_n(parSrc, input_len);
|
||||||
std::string key(houdini.unescape_url(itm.first));
|
Escapist houdini;
|
||||||
std::string val(houdini.unescape_url(itm.second));
|
for (auto& itm : split_env_vars(original_data)) {
|
||||||
map[std::move(key)] = std::move(val);
|
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;
|
already_read = true;
|
||||||
|
|
|
@ -17,11 +17,17 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "tawashi_exception.hpp"
|
||||||
#include <boost/container/flat_map.hpp>
|
#include <boost/container/flat_map.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <istream>
|
#include <istream>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
namespace tawashi {
|
namespace tawashi {
|
||||||
|
class UnsupportedContentTypeException : public TawashiException {
|
||||||
|
public:
|
||||||
|
explicit UnsupportedContentTypeException (const boost::string_ref& parMessage);
|
||||||
|
};
|
||||||
|
|
||||||
namespace cgi {
|
namespace cgi {
|
||||||
class Env;
|
class Env;
|
||||||
|
@ -29,5 +35,6 @@ namespace tawashi {
|
||||||
typedef boost::container::flat_map<std::string, std::string> PostMapType;
|
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);
|
||||||
|
const PostMapType& read_post (std::istream& parSrc, const Env& parEnv, std::size_t parMaxLen);
|
||||||
} //namespace cgi
|
} //namespace cgi
|
||||||
} //namespace tawashi
|
} //namespace tawashi
|
||||||
|
|
|
@ -27,6 +27,8 @@ namespace tawashi {
|
||||||
UnkownReason,
|
UnkownReason,
|
||||||
RedisDisconnected,
|
RedisDisconnected,
|
||||||
MissingPostVariable,
|
MissingPostVariable,
|
||||||
PastieNotFound
|
PastieNotFound,
|
||||||
|
InvalidContentType,
|
||||||
|
UnsupportedContentType
|
||||||
)
|
)
|
||||||
} //namespace tawashi
|
} //namespace tawashi
|
||||||
|
|
|
@ -50,7 +50,9 @@ namespace tawashi {
|
||||||
"An unknown error was raised.",
|
"An unknown error was raised.",
|
||||||
"Unable to connect to Redis.",
|
"Unable to connect to Redis.",
|
||||||
"Request is missing a POST variable.",
|
"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);
|
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");
|
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() {
|
HttpHeader SubmitPasteResponse::on_process() {
|
||||||
auto post = cgi::read_post(std::cin, cgi_env());
|
|
||||||
boost::string_ref pastie;
|
boost::string_ref pastie;
|
||||||
boost::string_ref lang;
|
boost::string_ref lang;
|
||||||
boost::string_ref duration;
|
boost::string_ref duration;
|
||||||
|
@ -92,18 +91,25 @@ namespace tawashi {
|
||||||
auto statuslog = spdlog::get("statuslog");
|
auto statuslog = spdlog::get("statuslog");
|
||||||
assert(statuslog);
|
assert(statuslog);
|
||||||
|
|
||||||
|
const SettingsBag& settings = this->settings();
|
||||||
try {
|
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));
|
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) {
|
catch (const TawashiException& e) {
|
||||||
statuslog->error(e.what());
|
statuslog->error(e.what());
|
||||||
return make_error_redirect(e.reason());
|
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");
|
const auto max_sz = settings.as<uint32_t>("max_pastie_size");
|
||||||
if (pastie.size() < settings.as<uint32_t>("min_pastie_size")) {
|
if (pastie.size() < settings.as<uint32_t>("min_pastie_size")) {
|
||||||
return make_error_redirect(ErrorReasons::PostLengthNotInRange);
|
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(),
|
content_length.c_str(),
|
||||||
"PATH_INFO=/",
|
"PATH_INFO=/",
|
||||||
"REQUEST_METHOD=GET",
|
"REQUEST_METHOD=GET",
|
||||||
|
"CONTENT_TYPE=application/x-www-form-urlencoded",
|
||||||
nullptr
|
nullptr
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue