1
0
Fork 0
mirror of https://github.com/KingDuckZ/kamokan.git synced 2024-11-23 00:33:44 +00:00

Make on_process() return an HttpHeader.

Response type is now decided by on_process' return value.
This commit is contained in:
King_DuckZ 2017-05-18 18:50:01 +01:00
parent c5f2bc055a
commit cdd23d35d0
10 changed files with 280 additions and 75 deletions

View file

@ -26,6 +26,7 @@ add_library(${PROJECT_NAME} STATIC
tiger.c
error_response.cpp
tawashi_exception.cpp
http_header.cpp
)
target_include_directories(${PROJECT_NAME}

View file

@ -22,7 +22,7 @@
#include "enum.h"
namespace tawashi {
BETTER_ENUM(CGIVars, std::size_t,
SLOW_ENUM(CGIVars, std::size_t,
AUTH_TYPE = 0,
CONTENT_LENGTH,
CONTENT_TYPE,

View file

@ -0,0 +1,153 @@
/* Copyright 2017, Michele Santullo
* This file is part of "tawashi".
*
* "tawashi" is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* "tawashi" is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with "tawashi". If not, see <http://www.gnu.org/licenses/>.
*/
#include "http_header.hpp"
#include "duckhandy/lexical_cast.hpp"
#include "duckhandy/sequence_bt.hpp"
#include "sprout/array/array.hpp"
#include <utility>
#include <spdlog/spdlog.h>
namespace tawashi {
namespace {
constexpr const char* get_status_code_desc (HttpStatusCodes parCode) {
switch (parCode) {
case HttpStatusCodes::Code301_MovedPermanently: return "Moved Permanently";
case HttpStatusCodes::Code302_Found: return "Found";
case HttpStatusCodes::Code303_SeeOther: return "See Other";
case HttpStatusCodes::Code400_BadRequest: return "Bad Request";
case HttpStatusCodes::Code403_Forbidden: return "Forbidden";
case HttpStatusCodes::Code404_NotFound: return "Not Found";
case HttpStatusCodes::Code413_PayloadTooLarge: return "Payload Too Large";
case HttpStatusCodes::Code429_TooManyRequests: return "Too Many Requests";
case HttpStatusCodes::Code431_RequestHeaderFieldsTooLarge: return "Request Header Fields Too Large";
case HttpStatusCodes::Code500_InternalServerError: return "Internal Server Error";
case HttpStatusCodes::Code501_NotImplemented: return "Not Implemented";
case HttpStatusCodes::Code503_ServiceUnavailable: return "Service Unavailable";
}
return "INVALID STATUS CODE";
}
inline constexpr uint16_t single_status_code_to_code (const char* parCode) {
std::size_t idx = 0;
uint16_t ret_num = 0;
uint16_t digits = 0;
char cur_char = 0;
while ((cur_char = parCode[idx++]) and digits < 3) {
if (cur_char >= '0' and cur_char <= '9') {
uint16_t multip = 1;
for (uint16_t z = 1; z < (3 - digits); ++z) {
multip *= 10;
}
ret_num += multip * (cur_char - '0');
++digits;
}
}
return ret_num;
}
template <int... Values>
inline constexpr sprout::array<uint16_t, sizeof...(Values)> make_status_codes_lookup (dhandy::bt::number_seq<int, Values...>) {
return sprout::array<uint16_t, sizeof...(Values)> {
single_status_code_to_code(HttpStatusCodes::_names()[Values])...
};
}
uint16_t status_code_name_to_num (HttpStatusCodes parCode) {
constexpr const auto status_codes = make_status_codes_lookup(dhandy::bt::number_range<int, 0, HttpStatusCodes::_size() - 1>());
return status_codes[parCode._to_integral()];
}
constexpr auto g_status_code_descriptions = ::better_enums::make_map(get_status_code_desc);
} //unnamed namespace
HttpHeader::HttpHeader() :
m_param("text/html"),
m_status_code(HttpStatusCodes::CodeNone),
m_header_type(ContentType)
{
}
HttpHeader::HttpHeader (Types parType, HttpStatusCodes parCode, std::string&& parParam) :
m_param(std::move(parParam)),
m_status_code(parCode),
m_header_type(parType)
{
}
void HttpHeader::set_status (HttpStatusCodes parCode) {
m_status_code = parCode;
}
void HttpHeader::unset_status() {
m_status_code = HttpStatusCodes::CodeNone;
}
void HttpHeader::set_type (Types parType, std::string&& parParameter) {
m_header_type = parType;
m_param = std::move(parParameter);
}
std::ostream& operator<< (std::ostream& parStream, const HttpHeader& parHeader) {
const HttpStatusCodes code_none = HttpStatusCodes::CodeNone;
if (parHeader.status_code() != code_none) {
parStream <<
"Status: " <<
status_code_name_to_num(parHeader.status_code()) <<
g_status_code_descriptions[parHeader.status_code()] <<
'\n'
;
}
switch (parHeader.type()) {
case HttpHeader::ContentType:
SPDLOG_TRACE(spdlog::get("statuslog"), "Response is a Content-type (data)");
parStream << "Content-type: " << parHeader.parameter() << '\n';
break;
case HttpHeader::Location:
SPDLOG_TRACE(spdlog::get("statuslog"), "Response is a Location (redirect)");
parStream << "Location: " << parHeader.parameter() << '\n';
break;
}
parStream << '\n';
return parStream;
}
HttpHeader make_header_type_html() {
return HttpHeader(HttpHeader::ContentType, HttpStatusCodes::CodeNone, "text/html");
}
HttpHeader make_header_type_text_utf8() {
return HttpHeader(HttpHeader::ContentType, HttpStatusCodes::CodeNone, "text/plain; charset=utf-8");
}
HttpStatusCodes int_to_status_code (uint16_t parCode) {
constexpr const auto status_codes = make_status_codes_lookup(dhandy::bt::number_range<int, 0, HttpStatusCodes::_size() - 1>());
auto it_found_code = std::find(status_codes.begin(), status_codes.end(), parCode);
if (it_found_code != status_codes.end()) {
const auto index = it_found_code - status_codes.begin();
assert(index < HttpStatusCodes::_size() - 1);
return HttpStatusCodes::_from_integral(index);
}
else {
return HttpStatusCodes::CodeNone;
}
}
bool HttpHeader::body_required() const {
return type() == ContentType;
}
} //namespace tawashi

View file

@ -0,0 +1,72 @@
/* Copyright 2017, Michele Santullo
* This file is part of "tawashi".
*
* "tawashi" is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* "tawashi" is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with "tawashi". If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "enum.h"
#include <cstdint>
#include <string>
#include <ostream>
namespace tawashi {
SLOW_ENUM(HttpStatusCodes, uint16_t,
Code301_MovedPermanently,
Code302_Found,
Code303_SeeOther,
Code400_BadRequest,
Code403_Forbidden,
Code404_NotFound,
Code413_PayloadTooLarge,
Code429_TooManyRequests,
Code431_RequestHeaderFieldsTooLarge,
Code500_InternalServerError,
Code501_NotImplemented,
Code503_ServiceUnavailable,
CodeNone
)
class HttpHeader {
public:
enum Types : uint8_t {
ContentType,
Location,
Status
};
HttpHeader();
HttpHeader (Types parType, HttpStatusCodes parCode, std::string&& parParam);
~HttpHeader() noexcept = default;
Types type() const { return m_header_type; }
HttpStatusCodes status_code() const { return m_status_code; }
const std::string& parameter() const { return m_param; }
bool body_required() const;
void set_status (HttpStatusCodes parCode);
void unset_status();
void set_type (Types parType, std::string&& parParameter);
private:
std::string m_param;
HttpStatusCodes m_status_code;
Types m_header_type;
};
std::ostream& operator<< (std::ostream& parStream, const HttpHeader& parHeader);
HttpHeader make_header_type_html();
HttpHeader make_header_type_text_utf8();
HttpStatusCodes int_to_status_code (uint16_t parCode);
} //namespace tawashi

View file

@ -42,11 +42,11 @@ namespace tawashi {
{
}
void PastieResponse::on_process() {
HttpHeader PastieResponse::on_process() {
auto get = cgi_env().query_string_split();
const std::string& query_str(cgi_env().query_string());
if (get["m"] == "plain" or query_str.empty()) {
this->change_type(Response::ContentType, "text/plain; charset=utf-8");
return make_header_type_text_utf8();
m_plain_text = true;
}
else if (query_str == g_nolang_token) {
@ -61,6 +61,7 @@ namespace tawashi {
if (m_lang_file.empty())
m_lang_file = "default.lang";
}
return make_header_type_html();
}
void PastieResponse::on_mustache_prepare (mstch::map& parContext) {

View file

@ -34,7 +34,7 @@ namespace tawashi {
virtual boost::string_ref page_basename() const override { return boost::string_ref("pastie"); }
private:
virtual void on_process() override;
virtual HttpHeader on_process() override;
virtual void on_mustache_prepare (mstch::map& parContext) override;
virtual std::string on_mustache_retrieve() override;

View file

@ -124,13 +124,11 @@ namespace tawashi {
const Kakoune::SafePtr<cgi::Env>& parCgiEnv,
bool parWantRedis
) :
m_resp_value(g_def_response_type),
//m_page_basename(fetch_page_basename(m_cgi_env)),
m_cgi_env(parCgiEnv),
m_settings(parSettings),
m_website_root(make_root_path(*parSettings)),
m_base_uri(make_base_uri(m_settings->at("base_uri"), m_cgi_env->https())),
m_resp_type(ContentType),
m_stream_out(parStreamOut),
m_header_sent(false)
{
@ -155,7 +153,8 @@ namespace tawashi {
Response::~Response() noexcept = default;
void Response::on_process() {
HttpHeader Response::on_process() {
return HttpHeader();
}
void Response::on_mustache_prepare (mstch::map&) {
@ -183,26 +182,13 @@ namespace tawashi {
}
SPDLOG_TRACE(statuslog, "Raising event on_process");
this->on_process();
HttpHeader http_header = this->on_process();
*m_stream_out << http_header;
SPDLOG_TRACE(statuslog, "Raising event on_mustache_prepare");
this->on_mustache_prepare(mustache_context);
m_header_sent = true;
bool render_page = true;
switch (m_resp_type) {
case ContentType:
SPDLOG_TRACE(statuslog, "Response is a Content-type (data)");
*m_stream_out << "Content-type: " << m_resp_value << "\n\n";
break;
case Location:
SPDLOG_TRACE(statuslog, "Response is a Location (redirect)");
*m_stream_out << "Status: 303 See Other" << "\n";
*m_stream_out << "Location: " << m_resp_value << "\n\n";
render_page = false;
break;
}
if (render_page) {
if (http_header.body_required()) {
SPDLOG_TRACE(statuslog, "Rendering in mustache");
*m_stream_out << mstch::render(
on_mustache_retrieve(),
@ -229,13 +215,6 @@ namespace tawashi {
return *m_cgi_env;
}
void Response::change_type (Types parRespType, std::string&& parValue) {
assert(not m_header_sent);
assert(not parValue.empty());
m_resp_type = parRespType;
m_resp_value = std::move(parValue);
}
const std::string& Response::base_uri() const {
return m_base_uri;
}
@ -254,4 +233,20 @@ namespace tawashi {
assert(m_settings);
return *m_settings;
}
HttpHeader Response::make_redirect (HttpStatusCodes parCode, const std::string& parLocation) {
std::ostringstream oss;
oss << base_uri() << '/' << parLocation;
return HttpHeader(HttpHeader::Location, parCode, oss.str());
}
HttpHeader Response::make_error_redirect (uint16_t parCode, ErrorReasons parReason) {
auto statuslog = spdlog::get("statuslog");
assert(statuslog);
statuslog->info("Redirecting to error page, code={} reason={}", parCode, parReason);
std::ostringstream oss;
oss << "error.cgi?code=" << parCode << "&reason=" << parReason._to_integral();
return make_redirect(int_to_status_code(parCode), oss.str());
}
} //namespace tawashi

View file

@ -19,6 +19,8 @@
#include "mstch/mstch.hpp"
#include "kakoune/safe_ptr.hh"
#include "http_header.hpp"
#include "error_reasons.hpp"
#include <string>
#include <iostream>
#include <boost/utility/string_ref.hpp>
@ -42,11 +44,6 @@ namespace tawashi {
void send();
protected:
enum Types {
ContentType,
Location
};
Response (
const Kakoune::SafePtr<SettingsBag>& parSettings,
std::ostream* parStreamOut,
@ -54,26 +51,24 @@ namespace tawashi {
bool parWantRedis
);
void change_type (Types parRespType, std::string&& parValue);
const cgi::Env& cgi_env() const;
const std::string& base_uri() const;
virtual boost::string_ref page_basename() const = 0;
redis::IncRedis& redis() const;
const SettingsBag& settings() const;
virtual std::string load_mustache() const;
HttpHeader make_redirect (HttpStatusCodes parCode, const std::string& parLocation);
HttpHeader make_error_redirect (uint16_t parCode, ErrorReasons parReason);
private:
virtual void on_process();
virtual HttpHeader on_process();
virtual void on_mustache_prepare (mstch::map& parContext);
virtual std::string on_mustache_retrieve();
std::string m_resp_value;
Kakoune::SafePtr<cgi::Env> m_cgi_env;
Kakoune::SafePtr<SettingsBag> m_settings;
std::string m_website_root;
std::string m_base_uri;
Types m_resp_type;
std::unique_ptr<redis::IncRedis> m_redis;
std::ostream* m_stream_out;
bool m_header_sent;

View file

@ -97,10 +97,9 @@ namespace tawashi {
) :
Response(parSettings, parStreamOut, parCgiEnv, true)
{
this->change_type(Response::ContentType, "text/plain");
}
void SubmitPasteResponse::on_process() {
HttpHeader SubmitPasteResponse::on_process() {
auto post = cgi::read_post(std::cin, cgi_env());
boost::string_ref pastie;
boost::string_ref lang;
@ -114,8 +113,7 @@ namespace tawashi {
}
catch (const TawashiException& e) {
statuslog->error(e.what());
error_redirect(500, e.reason());
return;
return make_error_redirect(500, e.reason());
}
try {
lang = get_value_from_post(post, make_string_ref(g_language_key));
@ -128,50 +126,51 @@ namespace tawashi {
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")) {
error_redirect(431, ErrorReasons::PostLengthNotInRange);
return;
return make_error_redirect(431, ErrorReasons::PostLengthNotInRange);
}
if (max_sz and pastie.size() > max_sz) {
if (settings.as<bool>("truncate_long_pasties")) {
pastie = pastie.substr(0, max_sz);
}
else {
error_redirect(431, ErrorReasons::PostLengthNotInRange);
return;
return make_error_redirect(431, ErrorReasons::PostLengthNotInRange);
}
}
//TODO: replace boost's lexical_cast with mine when I have some checks
//over invalid inputs
const uint32_t duration_int = std::max(std::min((duration.empty() ? 86400U : boost::lexical_cast<uint32_t>(duration)), 2628000U), 1U);
boost::optional<std::string> token = submit_to_redis(pastie, duration_int, lang);
StringOrHeader submit_result = submit_to_redis(pastie, duration_int, lang);
const auto& token = submit_result.first;
if (token) {
std::ostringstream oss;
oss << base_uri() << '/' << *token;
oss << *token;
statuslog->info("Pastie token=\"{}\" redirect=\"{}\"", *token, oss.str());
if (not lang.empty())
oss << '?' << lang;
this->change_type(Response::Location, oss.str());
return this->make_redirect(HttpStatusCodes::CodeNone, oss.str());
}
else {
statuslog->info("Empty pastie token (possibly due to a previous failure)");
return;
return submit_result.second;
}
}
boost::optional<std::string> SubmitPasteResponse::submit_to_redis (const boost::string_ref& parText, uint32_t parExpiry, const boost::string_ref& parLang) {
auto SubmitPasteResponse::submit_to_redis (
const boost::string_ref& parText,
uint32_t parExpiry,
const boost::string_ref& parLang
) -> StringOrHeader {
auto& redis = this->redis();
if (not redis.is_connected()) {
error_redirect(503, ErrorReasons::RedisDisconnected);
return boost::optional<std::string>();
return std::make_pair(boost::optional<std::string>(), make_error_redirect(503, ErrorReasons::RedisDisconnected));
}
std::string ip_hash = hashed_ip(cgi_env().remote_addr());
if (redis.get(ip_hash)) {
//please wait and submit again
error_redirect(429, ErrorReasons::UserFlooding);
return boost::optional<std::string>();
return std::make_pair(boost::optional<std::string>(), make_error_redirect(429, ErrorReasons::UserFlooding));
}
const auto next_id = redis.incr("paste_counter");
@ -185,20 +184,9 @@ namespace tawashi {
redis.set(ip_hash, "");
redis.expire(ip_hash, settings().as<uint32_t>("resubmit_wait"));
if (redis.expire(token, parExpiry))
return boost::make_optional(token);
return std::make_pair(boost::make_optional(token), HttpHeader());
}
error_redirect(500, ErrorReasons::PastieNotSaved);
return boost::optional<std::string>();
}
void SubmitPasteResponse::error_redirect (int parCode, ErrorReasons parReason) {
auto statuslog = spdlog::get("statuslog");
assert(statuslog);
statuslog->info("Redirecting to error page, code={} reason={}", parCode, parReason);
std::ostringstream oss;
oss << base_uri() << "/error.cgi?code=" << parCode << "&reason=" << parReason._to_integral();
this->change_type(Response::Location, oss.str());
return std::make_pair(boost::optional<std::string>(), make_error_redirect(500, ErrorReasons::PastieNotSaved));
}
} //namespace tawashi

View file

@ -18,11 +18,11 @@
#pragma once
#include "response.hpp"
#include "error_reasons.hpp"
#include <string>
#include <boost/optional.hpp>
#include <boost/utility/string_ref.hpp>
#include <cassert>
#include <utility>
namespace tawashi {
class SubmitPasteResponse : public Response {
@ -37,8 +37,8 @@ namespace tawashi {
virtual boost::string_ref page_basename() const override { assert(false); return boost::string_ref(""); }
private:
virtual void on_process() override;
boost::optional<std::string> submit_to_redis (const boost::string_ref& parText, uint32_t parExpiry, const boost::string_ref& parLang);
void error_redirect (int parCode, ErrorReasons parReason);
typedef std::pair<boost::optional<std::string>, HttpHeader> StringOrHeader;
virtual HttpHeader on_process() override;
StringOrHeader submit_to_redis (const boost::string_ref& parText, uint32_t parExpiry, const boost::string_ref& parLang);
};
} //namespace tawashi