aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
authoripknHama <ipknhama@gmail.com>2014-09-11 06:32:41 +0900
committeripknHama <ipknhama@gmail.com>2014-09-11 06:32:41 +0900
commit9eb96b7f4c2e134e768555de2650823a863843ce (patch)
tree87797876b1a8bdf12f11d31e2fa28fc462075610 /include
parentab1063c046b363a37ccaf91c7dfb1fecd279be36 (diff)
downloadcrow-9eb96b7f4c2e134e768555de2650823a863843ce.tar.gz
crow-9eb96b7f4c2e134e768555de2650823a863843ce.zip
Implement example CookieParser middleware and test
Diffstat (limited to 'include')
-rw-r--r--include/ci_map.h32
-rw-r--r--include/http_connection.h6
-rw-r--r--include/http_request.h24
-rw-r--r--include/http_response.h22
-rw-r--r--include/middleware.h133
-rw-r--r--include/parser.h4
6 files changed, 201 insertions, 20 deletions
diff --git a/include/ci_map.h b/include/ci_map.h
new file mode 100644
index 0000000..9b48f0b
--- /dev/null
+++ b/include/ci_map.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <boost/functional/hash.hpp>
+
+namespace crow
+{
+ struct ci_hash
+ {
+ size_t operator()(const std::string& key) const
+ {
+ std::size_t seed = 0;
+ std::locale locale;
+
+ for(auto c : key)
+ {
+ boost::hash_combine(seed, std::toupper(c, locale));
+ }
+
+ return seed;
+ }
+ };
+
+ struct ci_key_eq
+ {
+ bool operator()(const std::string& l, const std::string& r) const
+ {
+ return boost::iequals(l, r);
+ }
+ };
+
+ using ci_map = std::unordered_multimap<std::string, std::string, ci_hash, ci_key_eq>;
+}
diff --git a/include/http_connection.h b/include/http_connection.h
index 3a9fe65..54d0860 100644
--- a/include/http_connection.h
+++ b/include/http_connection.h
@@ -155,7 +155,7 @@ namespace crow
void handle_header()
{
// HTTP 1.1 Expect: 100-continue
- if (parser_.check_version(1, 1) && parser_.headers.count("expect") && parser_.headers["expect"] == "100-continue")
+ if (parser_.check_version(1, 1) && parser_.headers.count("expect") && get_header_value(parser_.headers, "expect") == "100-continue")
{
buffers_.clear();
static std::string expect_100_continue = "HTTP/1.1 100 Continue\r\n\r\n";
@@ -174,13 +174,13 @@ namespace crow
if (parser_.check_version(1, 0))
{
// HTTP/1.0
- if (!(req.headers.count("connection") && boost::iequals(req.headers["connection"],"Keep-Alive")))
+ if (!(req.headers.count("connection") && boost::iequals(req.get_header_value("connection"),"Keep-Alive")))
close_connection_ = true;
}
else if (parser_.check_version(1, 1))
{
// HTTP/1.1
- if (req.headers.count("connection") && req.headers["connection"] == "close")
+ if (req.headers.count("connection") && req.get_header_value("connection") == "close")
close_connection_ = true;
if (!req.headers.count("host"))
{
diff --git a/include/http_request.h b/include/http_request.h
index af623c6..7d2da67 100644
--- a/include/http_request.h
+++ b/include/http_request.h
@@ -1,16 +1,38 @@
#pragma once
#include "common.h"
+#include "ci_map.h"
namespace crow
{
+ template <typename T>
+ inline const std::string& get_header_value(const T& headers, const std::string& key)
+ {
+ if (headers.count(key))
+ {
+ return headers.find(key)->second;
+ }
+ static std::string empty;
+ return empty;
+ }
+
struct request
{
HTTPMethod method;
std::string url;
- std::unordered_map<std::string, std::string> headers;
+ ci_map headers;
std::string body;
+ void add_header(std::string key, std::string value)
+ {
+ headers.emplace(std::move(key), std::move(value));
+ }
+
+ const std::string& get_header_value(const std::string& key)
+ {
+ return crow::get_header_value(headers, key);
+ }
+
void* middleware_context{};
};
}
diff --git a/include/http_response.h b/include/http_response.h
index bc468b7..69c6d72 100644
--- a/include/http_response.h
+++ b/include/http_response.h
@@ -2,6 +2,8 @@
#include <string>
#include <unordered_map>
#include "json.h"
+#include "http_request.h"
+#include "ci_map.h"
namespace crow
{
@@ -15,7 +17,25 @@ namespace crow
std::string body;
json::wvalue json_value;
int code{200};
- std::unordered_map<std::string, std::string> headers;
+
+ // `headers' stores HTTP headers.
+ ci_map headers;
+
+ void set_header(std::string key, std::string value)
+ {
+ headers.erase(key);
+ headers.emplace(std::move(key), std::move(value));
+ }
+ void add_header(std::string key, std::string value)
+ {
+ headers.emplace(std::move(key), std::move(value));
+ }
+
+ const std::string& get_header_value(const std::string& key)
+ {
+ return crow::get_header_value(headers, key);
+ }
+
response() {}
explicit response(int code) : code(code) {}
diff --git a/include/middleware.h b/include/middleware.h
index 5e358af..ec54476 100644
--- a/include/middleware.h
+++ b/include/middleware.h
@@ -1,4 +1,5 @@
#pragma once
+#include <boost/algorithm/string/trim.hpp>
#include "http_request.h"
#include "http_response.h"
@@ -9,35 +10,143 @@ namespace crow
// struct context;
// storing data for the middleware; can be read from another middleware or handlers
- // template <typename AllContext>
- // void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
+ // before_handle
// called before handling the request.
// if res.end() is called, the operation is halted.
// (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>()
- // template <typename AllContext>
- // void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
+ // after_handle
// 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)
- class CookieParser
+ struct CookieParser
{
struct context
{
std::unordered_map<std::string, std::string> jar;
+ std::unordered_map<std::string, std::string> cookies_to_add;
+
+ std::string get_cookie(const std::string& key)
+ {
+ if (jar.count(key))
+ return jar[key];
+ return {};
+ }
+
+ void set_cookie(const std::string& key, const std::string& value)
+ {
+ cookies_to_add.emplace(key, value);
+ }
};
- template <typename AllContext>
- void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
+ void before_handle(request& req, response& res, context& ctx)
{
- // ctx == all_ctx.bind<CookieParser>()
- // ctx.jar[] = ;
+ 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);
+ boost::trim(name);
+ pos = pos_equal+1;
+ while(pos < cookies.size() && cookies[pos] == ' ') pos++;
+ if (pos == cookies.size())
+ break;
+
+ std::string value;
+
+ if (cookies[pos] == '"')
+ {
+ int dquote_meet_count = 0;
+ pos ++;
+ size_t pos_dquote = pos-1;
+ do
+ {
+ pos_dquote = cookies.find('"', pos_dquote+1);
+ dquote_meet_count ++;
+ } while(pos_dquote < cookies.size() && cookies[pos_dquote-1] == '\\');
+ if (pos_dquote == cookies.npos)
+ break;
+
+ if (dquote_meet_count == 1)
+ value = cookies.substr(pos, pos_dquote - pos);
+ else
+ {
+ value.clear();
+ value.reserve(pos_dquote-pos);
+ for(size_t p = pos; p < pos_dquote; p++)
+ {
+ // FIXME minimal escaping
+ if (cookies[p] == '\\' && p + 1 < pos_dquote)
+ {
+ p++;
+ if (cookies[p] == '\\' || cookies[p] == '"')
+ value += cookies[p];
+ else
+ {
+ value += '\\';
+ value += cookies[p];
+ }
+ }
+ else
+ value += cookies[p];
+ }
+ }
+
+ ctx.jar.emplace(std::move(name), std::move(value));
+ pos = cookies.find(";", pos_dquote+1);
+ if (pos == cookies.npos)
+ break;
+ pos++;
+ while(pos < cookies.size() && cookies[pos] == ' ') pos++;
+ if (pos == cookies.size())
+ break;
+ }
+ else
+ {
+ size_t pos_semicolon = cookies.find(';', pos);
+ value = cookies.substr(pos, pos_semicolon - pos);
+ boost::trim(value);
+ 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++;
+ if (pos == cookies.size())
+ break;
+ }
+ }
}
- template <typename AllContext>
- void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
+ void after_handle(request& req, response& res, context& ctx)
{
+ for(auto& cookie:ctx.cookies_to_add)
+ {
+ res.add_header("Set-Cookie", cookie.first + "=" + cookie.second);
+ }
}
- }
+ };
/*
App<CookieParser, AnotherJarMW> app;
diff --git a/include/parser.h b/include/parser.h
index 1b8240f..869061c 100644
--- a/include/parser.h
+++ b/include/parser.h
@@ -31,7 +31,6 @@ namespace crow
case 0:
if (!self->header_value.empty())
{
- boost::algorithm::to_lower(self->header_field);
self->headers.emplace(std::move(self->header_field), std::move(self->header_value));
}
self->header_field.assign(at, at+length);
@@ -63,7 +62,6 @@ namespace crow
HTTPParser* self = static_cast<HTTPParser*>(self_);
if (!self->header_field.empty())
{
- boost::algorithm::to_lower(self->header_field);
self->headers.emplace(std::move(self->header_field), std::move(self->header_value));
}
self->process_header();
@@ -144,7 +142,7 @@ namespace crow
int header_building_state = 0;
std::string header_field;
std::string header_value;
- std::unordered_map<std::string, std::string> headers;
+ ci_map headers;
std::string body;
Handler* handler_;