2014-09-06 16:24:45 +00:00
|
|
|
#pragma once
|
2022-04-16 21:02:18 +00:00
|
|
|
#include <iomanip>
|
2022-04-16 10:13:18 +00:00
|
|
|
#include <boost/optional.hpp>
|
2014-09-10 21:32:41 +00:00
|
|
|
#include <boost/algorithm/string/trim.hpp>
|
2016-09-21 14:11:06 +00:00
|
|
|
#include "crow/http_request.h"
|
|
|
|
#include "crow/http_response.h"
|
2014-09-06 16:24:45 +00:00
|
|
|
|
|
|
|
namespace crow
|
|
|
|
{
|
2014-09-06 19:30:53 +00:00
|
|
|
// Any middleware requires following 3 members:
|
|
|
|
|
|
|
|
// struct context;
|
|
|
|
// storing data for the middleware; can be read from another middleware or handlers
|
|
|
|
|
2014-09-10 21:32:41 +00:00
|
|
|
// before_handle
|
2014-09-06 19:30:53 +00:00
|
|
|
// called before handling the request.
|
2021-11-25 11:45:38 +00:00
|
|
|
// if res.end() is called, the operation is halted.
|
2014-09-06 19:30:53 +00:00
|
|
|
// (still call after_handle of this middleware)
|
2014-09-10 21:32:41 +00:00
|
|
|
// 2 signatures:
|
|
|
|
// void before_handle(request& req, response& res, context& ctx)
|
|
|
|
// if you only need to access this middlewares context.
|
|
|
|
// template <typename AllContext>
|
|
|
|
// void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
|
|
|
|
// you can access another middlewares' context by calling `all_ctx.template get<MW>()'
|
|
|
|
// ctx == all_ctx.template get<CurrentMiddleware>()
|
2014-09-06 19:30:53 +00:00
|
|
|
|
2014-09-10 21:32:41 +00:00
|
|
|
// after_handle
|
2014-09-06 19:30:53 +00:00
|
|
|
// called after handling the request.
|
2014-09-10 21:32:41 +00:00
|
|
|
// void after_handle(request& req, response& res, context& ctx)
|
|
|
|
// template <typename AllContext>
|
|
|
|
// void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
|
2014-09-06 19:30:53 +00:00
|
|
|
|
2014-09-10 21:32:41 +00:00
|
|
|
struct CookieParser
|
2014-09-06 16:24:45 +00:00
|
|
|
{
|
2022-04-16 10:13:18 +00:00
|
|
|
// Cookie stores key, value and attributes
|
|
|
|
struct Cookie
|
|
|
|
{
|
|
|
|
enum class SameSitePolicy
|
|
|
|
{
|
|
|
|
Strict,
|
|
|
|
Lax,
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
template<typename U>
|
|
|
|
Cookie(const std::string& key, U&& value):
|
|
|
|
Cookie()
|
|
|
|
{
|
|
|
|
key_ = key;
|
|
|
|
value_ = std::forward<U>(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// format cookie to HTTP header format
|
2022-05-02 11:48:14 +00:00
|
|
|
std::string dump() const
|
2022-04-16 10:13:18 +00:00
|
|
|
{
|
2022-04-16 21:02:18 +00:00
|
|
|
const static char* HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT";
|
2022-04-16 10:13:18 +00:00
|
|
|
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << key_ << '=';
|
|
|
|
ss << (value_.empty() ? "\"\"" : value_);
|
2022-05-06 12:36:36 +00:00
|
|
|
dumpString(ss, !domain_.empty(), "Domain=", domain_);
|
|
|
|
dumpString(ss, !path_.empty(), "Path=", path_);
|
2022-05-02 11:48:14 +00:00
|
|
|
dumpString(ss, secure_, "Secure");
|
|
|
|
dumpString(ss, httponly_, "HttpOnly");
|
2022-04-16 10:13:18 +00:00
|
|
|
if (expires_at_)
|
|
|
|
{
|
2022-05-02 11:48:14 +00:00
|
|
|
ss << DIVIDER << "Expires="
|
|
|
|
<< std::put_time(expires_at_.get_ptr(), HTTP_DATE_FORMAT);
|
|
|
|
}
|
|
|
|
if (max_age_)
|
|
|
|
{
|
|
|
|
ss << DIVIDER << "Max-Age=" << *max_age_;
|
2022-04-16 10:13:18 +00:00
|
|
|
}
|
|
|
|
if (same_site_)
|
|
|
|
{
|
|
|
|
ss << DIVIDER << "SameSite=";
|
|
|
|
switch (*same_site_)
|
|
|
|
{
|
|
|
|
case SameSitePolicy::Strict:
|
|
|
|
ss << "Strict";
|
|
|
|
break;
|
|
|
|
case SameSitePolicy::Lax:
|
|
|
|
ss << "Lax";
|
|
|
|
break;
|
|
|
|
case SameSitePolicy::None:
|
|
|
|
ss << "None";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expires attribute
|
2022-04-16 21:02:18 +00:00
|
|
|
Cookie& expires(const std::tm& time)
|
2022-04-16 10:13:18 +00:00
|
|
|
{
|
|
|
|
expires_at_ = time;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Max-Age attribute
|
2022-04-16 21:02:18 +00:00
|
|
|
Cookie& max_age(long long seconds)
|
2022-04-16 10:13:18 +00:00
|
|
|
{
|
2022-04-16 21:02:18 +00:00
|
|
|
max_age_ = seconds;
|
2022-04-16 10:13:18 +00:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Domain attribute
|
|
|
|
Cookie& domain(const std::string& name)
|
|
|
|
{
|
|
|
|
domain_ = name;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Path attribute
|
|
|
|
Cookie& path(const std::string& path)
|
|
|
|
{
|
|
|
|
path_ = path;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Secured attribute
|
|
|
|
Cookie& secure()
|
|
|
|
{
|
|
|
|
secure_ = true;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// HttpOnly attribute
|
|
|
|
Cookie& httponly()
|
|
|
|
{
|
|
|
|
httponly_ = true;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// SameSite attribute
|
|
|
|
Cookie& same_site(SameSitePolicy ssp)
|
|
|
|
{
|
|
|
|
same_site_ = ssp;
|
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Cookie() = default;
|
|
|
|
|
2022-05-02 11:48:14 +00:00
|
|
|
static void dumpString(std::stringstream& ss, bool cond, const char* prefix,
|
2022-05-06 12:36:36 +00:00
|
|
|
const std::string& value = "")
|
2022-05-02 11:48:14 +00:00
|
|
|
{
|
|
|
|
if (cond)
|
|
|
|
{
|
2022-05-06 12:36:36 +00:00
|
|
|
ss << DIVIDER << prefix << value;
|
|
|
|
}
|
2022-05-02 11:48:14 +00:00
|
|
|
}
|
|
|
|
|
2022-04-16 10:13:18 +00:00
|
|
|
private:
|
|
|
|
std::string key_;
|
|
|
|
std::string value_;
|
2022-04-16 12:44:05 +00:00
|
|
|
boost::optional<long long> max_age_{};
|
2022-04-16 10:13:18 +00:00
|
|
|
std::string domain_ = "";
|
|
|
|
std::string path_ = "";
|
|
|
|
bool secure_ = false;
|
|
|
|
bool httponly_ = false;
|
2022-04-16 21:02:18 +00:00
|
|
|
boost::optional<std::tm> expires_at_{};
|
2022-04-16 10:13:18 +00:00
|
|
|
boost::optional<SameSitePolicy> same_site_{};
|
2022-05-02 11:48:14 +00:00
|
|
|
|
|
|
|
static constexpr const char* DIVIDER = "; ";
|
2022-04-16 10:13:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2014-09-06 16:24:45 +00:00
|
|
|
struct context
|
|
|
|
{
|
|
|
|
std::unordered_map<std::string, std::string> jar;
|
2022-04-16 10:13:18 +00:00
|
|
|
std::vector<Cookie> cookies_to_add;
|
2014-09-10 21:32:41 +00:00
|
|
|
|
2017-09-17 17:39:46 +00:00
|
|
|
std::string get_cookie(const std::string& key) const
|
2014-09-10 21:32:41 +00:00
|
|
|
{
|
2017-09-17 17:39:46 +00:00
|
|
|
auto cookie = jar.find(key);
|
|
|
|
if (cookie != jar.end())
|
|
|
|
return cookie->second;
|
2014-09-10 21:32:41 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2022-04-16 10:13:18 +00:00
|
|
|
template<typename U>
|
|
|
|
Cookie& set_cookie(const std::string& key, U&& value)
|
2014-09-10 21:32:41 +00:00
|
|
|
{
|
2022-04-16 10:13:18 +00:00
|
|
|
cookies_to_add.emplace_back(key, std::forward<U>(value));
|
|
|
|
return cookies_to_add.back();
|
2014-09-10 21:32:41 +00:00
|
|
|
}
|
2014-09-06 16:24:45 +00:00
|
|
|
};
|
|
|
|
|
2014-09-10 21:32:41 +00:00
|
|
|
void before_handle(request& req, response& res, context& ctx)
|
2014-09-06 16:24:45 +00:00
|
|
|
{
|
2022-05-02 11:48:14 +00:00
|
|
|
// TODO(dranikpg): remove copies, use string_view with c++17
|
2014-09-10 21:32:41 +00:00
|
|
|
int count = req.headers.count("Cookie");
|
|
|
|
if (!count)
|
|
|
|
return;
|
|
|
|
if (count > 1)
|
|
|
|
{
|
|
|
|
res.code = 400;
|
|
|
|
res.end();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::string cookies = req.get_header_value("Cookie");
|
|
|
|
size_t pos = 0;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (pos < cookies.size())
|
2014-09-10 21:32:41 +00:00
|
|
|
{
|
|
|
|
size_t pos_equal = cookies.find('=', pos);
|
|
|
|
if (pos_equal == cookies.npos)
|
|
|
|
break;
|
2021-11-25 11:45:38 +00:00
|
|
|
std::string name = cookies.substr(pos, pos_equal - pos);
|
2014-09-10 21:32:41 +00:00
|
|
|
boost::trim(name);
|
2021-11-25 11:45:38 +00:00
|
|
|
pos = pos_equal + 1;
|
|
|
|
while (pos < cookies.size() && cookies[pos] == ' ')
|
|
|
|
pos++;
|
2014-09-10 21:32:41 +00:00
|
|
|
if (pos == cookies.size())
|
|
|
|
break;
|
|
|
|
|
2017-09-17 17:39:46 +00:00
|
|
|
size_t pos_semicolon = cookies.find(';', pos);
|
2021-11-25 11:45:38 +00:00
|
|
|
std::string value = cookies.substr(pos, pos_semicolon - pos);
|
2014-09-10 21:32:41 +00:00
|
|
|
|
2017-09-17 17:39:46 +00:00
|
|
|
boost::trim(value);
|
2021-11-25 11:45:38 +00:00
|
|
|
if (value[0] == '"' && value[value.size() - 1] == '"')
|
2014-09-10 21:32:41 +00:00
|
|
|
{
|
2021-11-25 11:45:38 +00:00
|
|
|
value = value.substr(1, value.size() - 2);
|
2014-09-10 21:32:41 +00:00
|
|
|
}
|
2017-09-17 17:39:46 +00:00
|
|
|
|
|
|
|
ctx.jar.emplace(std::move(name), std::move(value));
|
|
|
|
|
|
|
|
pos = pos_semicolon;
|
|
|
|
if (pos == cookies.npos)
|
|
|
|
break;
|
|
|
|
pos++;
|
2021-11-25 11:45:38 +00:00
|
|
|
while (pos < cookies.size() && cookies[pos] == ' ')
|
|
|
|
pos++;
|
2014-09-10 21:32:41 +00:00
|
|
|
}
|
2014-09-06 16:24:45 +00:00
|
|
|
}
|
|
|
|
|
2016-08-27 09:03:49 +00:00
|
|
|
void after_handle(request& /*req*/, response& res, context& ctx)
|
2014-09-06 16:24:45 +00:00
|
|
|
{
|
2022-04-16 10:13:18 +00:00
|
|
|
for (const auto& cookie : ctx.cookies_to_add)
|
2014-09-10 21:32:41 +00:00
|
|
|
{
|
2022-05-02 11:48:14 +00:00
|
|
|
res.add_header("Set-Cookie", cookie.dump());
|
2014-09-10 21:32:41 +00:00
|
|
|
}
|
2014-09-06 16:24:45 +00:00
|
|
|
}
|
2014-09-10 21:32:41 +00:00
|
|
|
};
|
2014-09-06 16:24:45 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
App<CookieParser, AnotherJarMW> app;
|
|
|
|
A B C
|
|
|
|
A::context
|
|
|
|
int aa;
|
|
|
|
|
|
|
|
ctx1 : public A::context
|
|
|
|
ctx2 : public ctx1, public B::context
|
|
|
|
ctx3 : public ctx2, public C::context
|
|
|
|
|
|
|
|
C depends on A
|
|
|
|
|
|
|
|
C::handle
|
|
|
|
context.aaa
|
|
|
|
|
|
|
|
App::context : private CookieParser::contetx, ...
|
|
|
|
{
|
|
|
|
jar
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
SimpleApp
|
|
|
|
*/
|
2021-11-25 11:45:38 +00:00
|
|
|
} // namespace crow
|