Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions docs/guides/CORS.md

This file was deleted.

43 changes: 43 additions & 0 deletions docs/guides/included-middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Crow contains some middlewares that are ready to be used in your application.
<br>
Make sure you understand how to enable and use [middleware](../middleware/).

## Cookies
Include: `crow/middlewares/cookie_parser.h` <br>
Examples: `examples/middlewars/example_cookies.cpp`

This middleware allows to read and write cookies by using `CookieParser`. Once enabled, it parses all incoming cookies.

Cookies can be read and written with the middleware context. All cookie attributes can be changed as well.

```cpp
auto& ctx = app.get_context<crow::CookieParser>(request);
std::string value = ctx.get_cookie("key");
ctx.set_cookie("key", "value")
.path("/")
.max_age(120);
```

!!! note

Make sure `CookieParser` is listed before any other middleware that relies on it.

## CORS
Include: `crow/middlewares/cors.h` <br>
Examples: `examples/middlewars/example_cors.cpp`

This middleware allows to set CORS policies by using `CORSHandler`. Once enabled, it will apply the default CORS rules globally.

The CORS rules can be modified by first getting the middleware via `#!cpp auto& cors = app.get_middleware<crow::CORSHandler>();`. The rules can be set per URL prefix using `prefix()`, per blueprint using `blueprint()`, or globally via `global()`. These will return a `CORSRules` object which contains the actual rules for the prefix, blueprint, or application. For more details go [here](../../reference/structcrow_1_1_c_o_r_s_handler.html).

`CORSRules` can be modified using the methods `origin()`, `methods()`, `headers()`, `max_age()`, `allow_credentials()`, or `ignore()`. For more details on these methods and what default values they take go [here](../../reference/structcrow_1_1_c_o_r_s_rules.html).

```cpp
auto& cors = app.get_middleware<crow::CORSHandler>();
cors
.global()
.headers("X-Custom-Header", "Upgrade-Insecure-Requests")
.methods("POST"_method, "GET"_method)
.prefix("/cors")
.origin("example.com");
```
4 changes: 4 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ add_executable(example_cors middlewares/example_cors.cpp)
add_warnings_optimizations(example_cors)
target_link_libraries(example_cors PUBLIC Crow::Crow)

add_executable(example_cookies middlewares/example_cookies.cpp)
add_warnings_optimizations(example_cookies)
target_link_libraries(example_cookies PUBLIC Crow::Crow)

if(MSVC)
add_executable(example_vs example_vs.cpp)
add_warnings_optimizations(example_vs)
Expand Down
32 changes: 32 additions & 0 deletions examples/middlewares/example_cookies.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include "crow.h"
#include "crow/middlewares/cookie_parser.h"

int main()
{
// Include CookieParser middleware
crow::App<crow::CookieParser> app;

CROW_ROUTE(app, "/read")
([&](const crow::request& req) {
auto& ctx = app.get_context<crow::CookieParser>(req);
// Read cookies with get_cookie
auto value = ctx.get_cookie("key");
return "value: " + value;
});

CROW_ROUTE(app, "/write")
([&](const crow::request& req) {
auto& ctx = app.get_context<crow::CookieParser>(req);
// Store cookies with set_cookie
ctx.set_cookie("key", "word")
// configure additional parameters
.path("/")
.max_age(120)
.httponly();
return "ok!";
});

app.port(18080).run();

return 0;
}
152 changes: 144 additions & 8 deletions include/crow/middlewares/cookie_parser.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#pragma once
#include <iomanip>
#include <boost/optional.hpp>
#include <boost/algorithm/string/trim.hpp>
#include "crow/http_request.h"
#include "crow/http_response.h"
Expand Down Expand Up @@ -30,10 +32,144 @@ namespace crow

struct CookieParser
{
// 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
std::string dump() const
{
const static char* HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT";

std::stringstream ss;
ss << key_ << '=';
ss << (value_.empty() ? "\"\"" : value_);
dumpString(ss, !domain_.empty(), "Domain=", domain_);
dumpString(ss, !path_.empty(), "Path=", path_);
dumpString(ss, secure_, "Secure");
dumpString(ss, httponly_, "HttpOnly");
if (expires_at_)
{
ss << DIVIDER << "Expires="
<< std::put_time(expires_at_.get_ptr(), HTTP_DATE_FORMAT);
}
if (max_age_)
{
ss << DIVIDER << "Max-Age=" << *max_age_;
}
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
Cookie& expires(const std::tm& time)
{
expires_at_ = time;
return *this;
}

// Max-Age attribute
Cookie& max_age(long long seconds)
{
max_age_ = seconds;
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;

static void dumpString(std::stringstream& ss, bool cond, const char* prefix,
const std::string& value = "")
{
if (cond)
{
ss << DIVIDER << prefix << value;
}
}

private:
std::string key_;
std::string value_;
boost::optional<long long> max_age_{};
std::string domain_ = "";
std::string path_ = "";
bool secure_ = false;
bool httponly_ = false;
boost::optional<std::tm> expires_at_{};
boost::optional<SameSitePolicy> same_site_{};

static constexpr const char* DIVIDER = "; ";
};


struct context
{
std::unordered_map<std::string, std::string> jar;
std::unordered_map<std::string, std::string> cookies_to_add;
std::vector<Cookie> cookies_to_add;

std::string get_cookie(const std::string& key) const
{
Expand All @@ -43,14 +179,17 @@ namespace crow
return {};
}

void set_cookie(const std::string& key, const std::string& value)
template<typename U>
Cookie& set_cookie(const std::string& key, U&& value)
{
cookies_to_add.emplace(key, value);
cookies_to_add.emplace_back(key, std::forward<U>(value));
return cookies_to_add.back();
}
};

void before_handle(request& req, response& res, context& ctx)
{
// TODO(dranikpg): remove copies, use string_view with c++17
int count = req.headers.count("Cookie");
if (!count)
return;
Expand Down Expand Up @@ -97,12 +236,9 @@ namespace crow

void after_handle(request& /*req*/, response& res, context& ctx)
{
for (auto& cookie : ctx.cookies_to_add)
for (const auto& cookie : ctx.cookies_to_add)
{
if (cookie.second.empty())
res.add_header("Set-Cookie", cookie.first + "=\"\"");
else
res.add_header("Set-Cookie", cookie.first + "=" + cookie.second);
res.add_header("Set-Cookie", cookie.dump());
}
}
};
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ nav:
- Writing Tests: guides/testing.md
- Using Crow:
- HTTP Authorization: guides/auth.md
- CORS Setup: guides/CORS.md
- Included Middlewares: guides/included-middleware.md
- Server setup:
- Proxies: guides/proxies.md
- Systemd run on startup: guides/syste.md
Expand Down
51 changes: 49 additions & 2 deletions tests/unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,7 @@ TEST_CASE("local_middleware")
app.stop();
} // local_middleware

TEST_CASE("middleware_cookieparser")
TEST_CASE("middleware_cookieparser_parse")
{
static char buf[2048];

Expand Down Expand Up @@ -1499,9 +1499,56 @@ TEST_CASE("middleware_cookieparser")
CHECK("val\"ue4" == value4);
}
app.stop();
} // middleware_cookieparser
} // middleware_cookieparser_parse


TEST_CASE("middleware_cookieparser_format")
{
using Cookie = CookieParser::Cookie;

auto valid = [](const std::string& s, int parts) {
return std::count(s.begin(), s.end(), ';') == parts - 1;
};

// basic
{
auto c = Cookie("key", "value");
auto s = c.dump();
CHECK(valid(s, 1));
CHECK(s == "key=value");
}
// max-age + domain
{
auto c = Cookie("key", "value")
.max_age(123)
.domain("example.com");
auto s = c.dump();
CHECK(valid(s, 3));
CHECK(s.find("key=value") != std::string::npos);
CHECK(s.find("Max-Age=123") != std::string::npos);
CHECK(s.find("Domain=example.com") != std::string::npos);
}
// samesite + secure
{
auto c = Cookie("key", "value")
.secure()
.same_site(Cookie::SameSitePolicy::None);
auto s = c.dump();
CHECK(valid(s, 3));
CHECK(s.find("Secure") != std::string::npos);
CHECK(s.find("SameSite=None") != std::string::npos);
}
// expires
{
auto tp = boost::posix_time::time_from_string("2000-11-01 23:59:59.000");
auto c = Cookie("key", "value")
.expires(boost::posix_time::to_tm(tp));
auto s = c.dump();
CHECK(valid(s, 2));
CHECK(s.find("Expires=Wed, 01 Nov 2000 23:59:59 GMT") != std::string::npos);
}
} // middleware_cookieparser_format

TEST_CASE("middleware_cors")
{
static char buf[5012];
Expand Down