Crow/include/crow/middlewares/cookie_parser.h

297 lines
8.8 KiB
C
Raw Normal View History

#pragma once
2022-04-16 21:02:18 +00:00
#include <iomanip>
#include "crow/utility.h"
#include "crow/http_request.h"
#include "crow/http_response.h"
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
// before_handle
2014-09-06 19:30:53 +00:00
// called before handling the request.
// if res.end() is called, the operation is halted.
2014-09-06 19:30:53 +00:00
// (still call after_handle of this middleware)
// 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
// after_handle
2014-09-06 19:30:53 +00:00
// called after handling the request.
// 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
struct CookieParser
{
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_, HTTP_DATE_FORMAT);
2022-05-02 11:48:14 +00:00
}
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
{
delete expires_at_;
expires_at_ = new std::tm(time);
2022-04-16 10:13:18 +00:00
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
{
delete max_age_;
max_age_ = new long long(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)
{
delete same_site_;
same_site_ = new SameSitePolicy(ssp);
2022-04-16 10:13:18 +00:00
return *this;
}
~Cookie()
{
delete max_age_;
delete expires_at_;
delete same_site_;
}
Cookie(const Cookie& c):
key_(c.key_),
value_(c.value_),
domain_(c.domain_),
path_(c.path_),
secure_(c.secure_),
httponly_(c.httponly_)
{
if (c.max_age_)
max_age_ = new long long(*c.max_age_);
if (c.expires_at_)
expires_at_ = new std::tm(*c.expires_at_);
if (c.same_site_)
same_site_ = new SameSitePolicy(*c.same_site_);
}
2022-04-16 10:13:18 +00:00
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_;
long long* max_age_{nullptr};
2022-04-16 10:13:18 +00:00
std::string domain_ = "";
std::string path_ = "";
bool secure_ = false;
bool httponly_ = false;
std::tm* expires_at_{nullptr};
SameSitePolicy* same_site_{nullptr};
2022-05-02 11:48:14 +00:00
static constexpr const char* DIVIDER = "; ";
2022-04-16 10:13:18 +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;
std::string get_cookie(const std::string& key) const
{
auto cookie = jar.find(key);
if (cookie != jar.end())
return cookie->second;
return {};
}
2022-04-16 10:13:18 +00:00
template<typename U>
Cookie& set_cookie(const std::string& key, U&& value)
{
2022-04-16 10:13:18 +00:00
cookies_to_add.emplace_back(key, std::forward<U>(value));
return cookies_to_add.back();
}
};
void before_handle(request& req, response& res, context& ctx)
{
2022-05-02 11:48:14 +00:00
// TODO(dranikpg): remove copies, use string_view with c++17
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;
while (pos < cookies.size())
{
size_t pos_equal = cookies.find('=', pos);
if (pos_equal == cookies.npos)
break;
std::string name = cookies.substr(pos, pos_equal - pos);
utility::trim(name);
pos = pos_equal + 1;
while (pos < cookies.size() && cookies[pos] == ' ')
pos++;
if (pos == cookies.size())
break;
size_t pos_semicolon = cookies.find(';', pos);
std::string value = cookies.substr(pos, pos_semicolon - pos);
utility::trim(value);
if (value[0] == '"' && value[value.size() - 1] == '"')
{
value = value.substr(1, value.size() - 2);
}
ctx.jar.emplace(std::move(name), std::move(value));
pos = pos_semicolon;
if (pos == cookies.npos)
break;
pos++;
while (pos < cookies.size() && cookies[pos] == ' ')
pos++;
}
}
void after_handle(request& /*req*/, response& res, context& ctx)
{
2022-04-16 10:13:18 +00:00
for (const auto& cookie : ctx.cookies_to_add)
{
2022-05-02 11:48:14 +00:00
res.add_header("Set-Cookie", cookie.dump());
}
}
};
/*
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
*/
} // namespace crow