Crow  1.1
A C++ microframework for the web
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 
8 namespace 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 
33  struct CookieParser
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  // TODO(dranikpg): remove copies, use string_view with c++17
235  int count = req.headers.count("Cookie");
236  if (!count)
237  return;
238  if (count > 1)
239  {
240  res.code = 400;
241  res.end();
242  return;
243  }
244  std::string cookies = req.get_header_value("Cookie");
245  size_t pos = 0;
246  while (pos < cookies.size())
247  {
248  size_t pos_equal = cookies.find('=', pos);
249  if (pos_equal == cookies.npos)
250  break;
251  std::string name = cookies.substr(pos, pos_equal - pos);
252  name = utility::trim(name);
253  pos = pos_equal + 1;
254  if (pos == cookies.size())
255  break;
256 
257  size_t pos_semicolon = cookies.find(';', pos);
258  std::string value = cookies.substr(pos, pos_semicolon - pos);
259 
260  value = utility::trim(value);
261  if (value[0] == '"' && value[value.size() - 1] == '"')
262  {
263  value = value.substr(1, value.size() - 2);
264  }
265 
266  ctx.jar.emplace(std::move(name), std::move(value));
267 
268  pos = pos_semicolon;
269  if (pos == cookies.npos)
270  break;
271  pos++;
272  }
273  }
274 
275  void after_handle(request& /*req*/, response& res, context& ctx)
276  {
277  for (const auto& cookie : ctx.cookies_to_add)
278  {
279  res.add_header("Set-Cookie", cookie.dump());
280  }
281  }
282  };
283 
284  /*
285  App<CookieParser, AnotherJarMW> app;
286  A B C
287  A::context
288  int aa;
289 
290  ctx1 : public A::context
291  ctx2 : public ctx1, public B::context
292  ctx3 : public ctx2, public C::context
293 
294  C depends on A
295 
296  C::handle
297  context.aaa
298 
299  App::context : private CookieParser::context, ...
300  {
301  jar
302 
303  }
304 
305  SimpleApp
306  */
307 } // namespace crow
An HTTP request.
Definition: http_request.h:36
HTTP response.
Definition: http_response.h:34
int code
The Status code for the response.
Definition: http_response.h:40
void end()
Set the response completion flag and call the handler (to send the response).
Definition: http_response.h:237