Crow  1.1
A C++ microframework for the web
 
Loading...
Searching...
No Matches
cookie_parser.h
1#pragma once
2#include <iomanip>
3#include <memory>
4#include "crow/utility.h"
5#include "crow/http_request.h"
6#include "crow/http_response.h"
7
8namespace crow
9{
10 // Any middleware requires following 3 members:
11
12 // struct context;
13 // storing data for the middleware; can be read from another middleware or handlers
14
15 // before_handle
16 // called before handling the request.
17 // if res.end() is called, the operation is halted.
18 // (still call after_handle of this middleware)
19 // 2 signatures:
20 // void before_handle(request& req, response& res, context& ctx)
21 // if you only need to access this middlewares context.
22 // template <typename AllContext>
23 // void before_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
24 // you can access another middlewares' context by calling `all_ctx.template get<MW>()'
25 // ctx == all_ctx.template get<CurrentMiddleware>()
26
27 // after_handle
28 // called after handling the request.
29 // void after_handle(request& req, response& res, context& ctx)
30 // template <typename AllContext>
31 // void after_handle(request& req, response& res, context& ctx, AllContext& all_ctx)
32
34 {
35 // Cookie stores key, value and attributes
36 struct Cookie
37 {
38 enum class SameSitePolicy
39 {
40 Strict,
41 Lax,
42 None
43 };
44
45 template<typename U>
46 Cookie(const std::string& key, U&& value):
47 Cookie()
48 {
49 key_ = key;
50 value_ = std::forward<U>(value);
51 }
52
53 Cookie(const std::string& key):
54 Cookie(key, "") {}
55
56 // format cookie to HTTP header format
57 std::string dump() const
58 {
59 const static char* HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S GMT";
60
61 std::stringstream ss;
62 ss << key_ << '=';
63 ss << (value_.empty() ? "\"\"" : value_);
64 dumpString(ss, !domain_.empty(), "Domain=", domain_);
65 dumpString(ss, !path_.empty(), "Path=", path_);
66 dumpString(ss, secure_, "Secure");
67 dumpString(ss, httponly_, "HttpOnly");
68 if (expires_at_)
69 {
70 ss << DIVIDER << "Expires="
71 << std::put_time(expires_at_.get(), HTTP_DATE_FORMAT);
72 }
73 if (max_age_)
74 {
75 ss << DIVIDER << "Max-Age=" << *max_age_;
76 }
77 if (same_site_)
78 {
79 ss << DIVIDER << "SameSite=";
80 switch (*same_site_)
81 {
82 case SameSitePolicy::Strict:
83 ss << "Strict";
84 break;
85 case SameSitePolicy::Lax:
86 ss << "Lax";
87 break;
88 case SameSitePolicy::None:
89 ss << "None";
90 break;
91 }
92 }
93 return ss.str();
94 }
95
96 const std::string& name()
97 {
98 return key_;
99 }
100
101 template<typename U>
102 Cookie& value(U&& value)
103 {
104 value_ = std::forward<U>(value);
105 return *this;
106 }
107
108 // Expires attribute
109 Cookie& expires(const std::tm& time)
110 {
111 expires_at_ = std::unique_ptr<std::tm>(new std::tm(time));
112 return *this;
113 }
114
115 // Max-Age attribute
116 Cookie& max_age(long long seconds)
117 {
118 max_age_ = std::unique_ptr<long long>(new long long(seconds));
119 return *this;
120 }
121
122 // Domain attribute
123 Cookie& domain(const std::string& name)
124 {
125 domain_ = name;
126 return *this;
127 }
128
129 // Path attribute
130 Cookie& path(const std::string& path)
131 {
132 path_ = path;
133 return *this;
134 }
135
136 // Secured attribute
137 Cookie& secure()
138 {
139 secure_ = true;
140 return *this;
141 }
142
143 // HttpOnly attribute
144 Cookie& httponly()
145 {
146 httponly_ = true;
147 return *this;
148 }
149
150 // SameSite attribute
151 Cookie& same_site(SameSitePolicy ssp)
152 {
153 same_site_ = std::unique_ptr<SameSitePolicy>(new SameSitePolicy(ssp));
154 return *this;
155 }
156
157 Cookie(const Cookie& c):
158 key_(c.key_),
159 value_(c.value_),
160 domain_(c.domain_),
161 path_(c.path_),
162 secure_(c.secure_),
163 httponly_(c.httponly_)
164 {
165 if (c.max_age_)
166 max_age_ = std::unique_ptr<long long>(new long long(*c.max_age_));
167
168 if (c.expires_at_)
169 expires_at_ = std::unique_ptr<std::tm>(new std::tm(*c.expires_at_));
170
171 if (c.same_site_)
172 same_site_ = std::unique_ptr<SameSitePolicy>(new SameSitePolicy(*c.same_site_));
173 }
174
175 private:
176 Cookie() = default;
177
178 static void dumpString(std::stringstream& ss, bool cond, const char* prefix,
179 const std::string& value = "")
180 {
181 if (cond)
182 {
183 ss << DIVIDER << prefix << value;
184 }
185 }
186
187 private:
188 std::string key_;
189 std::string value_;
190 std::unique_ptr<long long> max_age_{};
191 std::string domain_ = "";
192 std::string path_ = "";
193 bool secure_ = false;
194 bool httponly_ = false;
195 std::unique_ptr<std::tm> expires_at_{};
196 std::unique_ptr<SameSitePolicy> same_site_{};
197
198 static constexpr const char* DIVIDER = "; ";
199 };
200
201
202 struct context
203 {
204 std::unordered_map<std::string, std::string> jar;
205
206 std::string get_cookie(const std::string& key) const
207 {
208 auto cookie = jar.find(key);
209 if (cookie != jar.end())
210 return cookie->second;
211 return {};
212 }
213
214 template<typename U>
215 Cookie& set_cookie(const std::string& key, U&& value)
216 {
217 cookies_to_add.emplace_back(key, std::forward<U>(value));
218 return cookies_to_add.back();
219 }
220
221 Cookie& set_cookie(Cookie cookie)
222 {
223 cookies_to_add.push_back(std::move(cookie));
224 return cookies_to_add.back();
225 }
226
227 private:
228 friend struct CookieParser;
229 std::vector<Cookie> cookies_to_add;
230 };
231
232 void before_handle(request& req, response& res, context& ctx)
233 {
234 const int count = req.headers.count("Cookie");
235 if (!count)
236 return;
237 if (count > 1)
238 {
239 res.code = 400;
240 res.end();
241 return;
242 }
243
244 const std::string_view cookies_sv = req.get_header_value("Cookie");
245
246 auto trim_sv = [](const std::string_view sv) -> std::string_view {
247 const size_t first = sv.find_first_not_of(" \t\n\r\f\v"); // same as isspace
248 if (std::string_view::npos == first) {
249 return sv.substr(0, 0);
250 }
251 const size_t last = sv.find_last_not_of(" \t\n\r\f\v");
252 return sv.substr(first, (last - first + 1));
253 };
254
255 size_t pos = 0;
256 while (pos < cookies_sv.size())
257 {
258 const size_t pos_equal = cookies_sv.find('=', pos);
259 if (pos_equal == std::string_view::npos) {
260 break;
261 }
262
263 std::string_view name_sv = cookies_sv.substr(pos, pos_equal - pos);
264 name_sv = trim_sv(name_sv);
265
266 pos = pos_equal + 1;
267 if (pos == cookies_sv.size()) {
268 break;
269 }
270
271 const size_t pos_semicolon = cookies_sv.find(';', pos);
272 std::string_view value_sv;
273
274 if (pos_semicolon == std::string_view::npos) {
275 value_sv = cookies_sv.substr(pos);
276 pos = cookies_sv.size();
277 } else {
278 value_sv = cookies_sv.substr(pos, pos_semicolon - pos);
279 pos = pos_semicolon + 1;
280 }
281
282 value_sv = trim_sv(value_sv);
283
284 if (!value_sv.empty() && value_sv.front() == '"' && value_sv.back() == '"')
285 {
286 if (value_sv.size() >= 2) {
287 value_sv.remove_prefix(1);
288 value_sv.remove_suffix(1);
289 } else {
290 value_sv = value_sv.substr(0,0);
291 }
292 }
293
294 ctx.jar.emplace(std::string(name_sv), std::string(value_sv));
295 }
296 }
297
298 void after_handle(request& /*req*/, response& res, context& ctx)
299 {
300 for (const auto& cookie : ctx.cookies_to_add)
301 {
302 res.add_header("Set-Cookie", cookie.dump());
303 }
304 }
305 };
306
307 /*
308 App<CookieParser, AnotherJarMW> app;
309 A B C
310 A::context
311 int aa;
312
313 ctx1 : public A::context
314 ctx2 : public ctx1, public B::context
315 ctx3 : public ctx2, public C::context
316
317 C depends on A
318
319 C::handle
320 context.aaa
321
322 App::context : private CookieParser::context, ...
323 {
324 jar
325
326 }
327
328 SimpleApp
329 */
330} // namespace crow
The main namespace of the library. In this namespace is defined the most important classes and functi...
An HTTP request.
Definition http_request.h:36
HTTP response.
Definition http_response.h:40
int code
The Status code for the response.
Definition http_response.h:49
void end()
Set the response completion flag and call the handler (to send the response).
Definition http_response.h:246