Line data Source code
1 : // Snap Websites Server -- manage HTTP cookies to be sent to the browser
2 : // Copyright (c) 2013-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // This program is free software; you can redistribute it and/or modify
5 : // it under the terms of the GNU General Public License as published by
6 : // the Free Software Foundation; either version 2 of the License, or
7 : // (at your option) any later version.
8 : //
9 : // This program is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : // GNU General Public License for more details.
13 : //
14 : // You should have received a copy of the GNU General Public License
15 : // along with this program; if not, write to the Free Software
16 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 :
18 :
19 : // self
20 : //
21 : #include "snapwebsites/http_cookie.h"
22 :
23 :
24 : // snapwebsites lib
25 : //
26 : #include "snapwebsites/log.h"
27 : #include "snapwebsites/snapwebsites.h"
28 :
29 :
30 : // libdbproxy lib
31 : //
32 : #include <libdbproxy/libdbproxy.h>
33 :
34 :
35 : // C lib
36 : //
37 : #include <sys/time.h>
38 :
39 :
40 : // snapdev lib
41 : //
42 : #include <snapdev/poison.h>
43 :
44 :
45 :
46 : namespace snap
47 : {
48 :
49 :
50 : namespace
51 : {
52 :
53 : // CHAR = <any US-ASCII character (octets 0 - 127)>
54 : // token = 1*<any CHAR except CTLs or separators>
55 : // separators = "(" | ")" | "<" | ">" | "@"
56 : // | "," | ";" | ":" | "\" | <">
57 : // | "/" | "[" | "]" | "?" | "="
58 : // | "{" | "}" | SP | HT
59 : #define SNAP_HTTP_TOKEN_CHAR(base, c) (static_cast<uint32_t>(1)<<(((c)-(base))&0x1F))
60 : uint32_t http_token[4] = {
61 : /* 00-1F */ 0x00000000,
62 : /* 20-3F */
63 : SNAP_HTTP_TOKEN_CHAR(0x20, '!')
64 : | SNAP_HTTP_TOKEN_CHAR(0x20, '#')
65 : | SNAP_HTTP_TOKEN_CHAR(0x20, '$')
66 : | SNAP_HTTP_TOKEN_CHAR(0x20, '%')
67 : | SNAP_HTTP_TOKEN_CHAR(0x20, '&')
68 : | SNAP_HTTP_TOKEN_CHAR(0x20, '\'')
69 : | SNAP_HTTP_TOKEN_CHAR(0x20, '*')
70 : | SNAP_HTTP_TOKEN_CHAR(0x20, '+')
71 : | SNAP_HTTP_TOKEN_CHAR(0x20, '-')
72 : | SNAP_HTTP_TOKEN_CHAR(0x20, '.')
73 : | SNAP_HTTP_TOKEN_CHAR(0x20, '0')
74 : | SNAP_HTTP_TOKEN_CHAR(0x20, '1')
75 : | SNAP_HTTP_TOKEN_CHAR(0x20, '2')
76 : | SNAP_HTTP_TOKEN_CHAR(0x20, '3')
77 : | SNAP_HTTP_TOKEN_CHAR(0x20, '4')
78 : | SNAP_HTTP_TOKEN_CHAR(0x20, '5')
79 : | SNAP_HTTP_TOKEN_CHAR(0x20, '6')
80 : | SNAP_HTTP_TOKEN_CHAR(0x20, '7')
81 : | SNAP_HTTP_TOKEN_CHAR(0x20, '8')
82 : | SNAP_HTTP_TOKEN_CHAR(0x20, '9')
83 : ,
84 : /* 40-5F */
85 : SNAP_HTTP_TOKEN_CHAR(0x40, 'A')
86 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'B')
87 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'C')
88 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'D')
89 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'E')
90 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'F')
91 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'G')
92 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'H')
93 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'I')
94 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'J')
95 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'K')
96 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'L')
97 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'M')
98 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'N')
99 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'O')
100 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'P')
101 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'Q')
102 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'R')
103 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'S')
104 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'T')
105 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'U')
106 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'V')
107 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'W')
108 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'X')
109 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'Y')
110 : | SNAP_HTTP_TOKEN_CHAR(0x40, 'Z')
111 : | SNAP_HTTP_TOKEN_CHAR(0x40, '^')
112 : | SNAP_HTTP_TOKEN_CHAR(0x40, '_')
113 : ,
114 : /* 60-7F */
115 : SNAP_HTTP_TOKEN_CHAR(0x60, '`')
116 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'a')
117 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'b')
118 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'c')
119 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'd')
120 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'e')
121 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'f')
122 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'g')
123 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'h')
124 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'i')
125 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'j')
126 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'k')
127 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'l')
128 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'm')
129 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'n')
130 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'o')
131 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'p')
132 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'q')
133 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'r')
134 : | SNAP_HTTP_TOKEN_CHAR(0x60, 's')
135 : | SNAP_HTTP_TOKEN_CHAR(0x60, 't')
136 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'u')
137 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'v')
138 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'w')
139 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'x')
140 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'y')
141 : | SNAP_HTTP_TOKEN_CHAR(0x60, 'z')
142 : | SNAP_HTTP_TOKEN_CHAR(0x60, '|')
143 : | SNAP_HTTP_TOKEN_CHAR(0x60, '~')
144 : };
145 : #undef SNAP_HTTP_TOKEN_CHAR
146 :
147 :
148 : } // no name namespace
149 :
150 :
151 : /** \brief Create an invalid cookie (no name).
152 : *
153 : * This function creates an invalid cookie. It is here because the QMap
154 : * implementation requires it. You should not use it otherwise since the
155 : * f_snap pointer will be set to NULL and is likely going to crash your
156 : * server.
157 : */
158 0 : http_cookie::http_cookie()
159 : {
160 0 : }
161 :
162 :
163 : /** \brief Initializes the cookie.
164 : *
165 : * This function initializes the cookie. The default for any cookie
166 : * is to have the following parameters:
167 : *
168 : * \li The name as supplied here.
169 : * \li The cookie contents, empty by default.
170 : * \li The domain set to this website full domain unless the user defined a
171 : * cookie domain as a site parameter (SNAP_NAME_CORE_COOKIE_DOMAIN).
172 : * \li A path set to "/".
173 : * \li No expiration date (i.e. cookie is for this session only, deleted when browser is closed)
174 : * \li Not secure
175 : * \li Not limited to HTTP
176 : *
177 : * Note that the name of the cookie is set when you create it. I cannot be
178 : * changed later.
179 : *
180 : * \note
181 : * The name of a cookie is case sensitive. In other words cookie "foo" and
182 : * cookie "Foo" can cohexist (although it certainly should not be used!)
183 : *
184 : * \warning
185 : * The cookie domain cannot be determined without a pointer to the snap
186 : * child object. If you do not have access to that pointer, make sure that
187 : * an object that has access calls the set_domain() at some point or the
188 : * cookie is likely to fail.
189 : *
190 : * \todo
191 : * If there is a redirect (i.e. we show website A but the user was
192 : * accessible website B,) then the default domain name will be wrong.
193 : * We should have a way to retrieve the primary domain name for this
194 : * purpose.
195 : *
196 : * \param[in] snap The snap child creating this cookie.
197 : * \param[in] name The name of the cookie.
198 : * \param[in] value The value of the cookie. To set a binary value, use set_value() with a QByteArray instead.
199 : *
200 : * \sa set_value()
201 : * \sa set_domain()
202 : * \sa set_path()
203 : * \sa set_delete()
204 : * \sa set_session()
205 : * \sa set_expire()
206 : * \sa set_expire_in()
207 : * \sa set_secure()
208 : * \sa set_http_only()
209 : * \sa set_comment()
210 : * \sa set_comment_url()
211 : */
212 0 : http_cookie::http_cookie(snap_child * snap, QString const & name, QString const & value)
213 : : f_snap(snap)
214 : , f_name(name)
215 0 : , f_path("/")
216 : {
217 : // XXX make this check only in debug-like versions
218 0 : int const max_len(f_name.length());
219 0 : if(max_len == 0)
220 : {
221 0 : throw http_cookie_parse_exception("the name of a cookie cannot be empty");
222 : }
223 0 : for(int i(0); i < max_len; ++i)
224 : {
225 0 : ushort c(f_name[i].unicode());
226 0 : if(c <= ' ' || c >= 127 || (http_token[c >> 5] & (1 << (c & 0x1F))) == 0)
227 : {
228 0 : throw http_cookie_parse_exception(QString("the name of a cookie must only include token compatible characters (offensive character: %1)").arg(QChar(c)));
229 : }
230 : }
231 0 : if(f_name[0] == '$')
232 : {
233 0 : throw http_cookie_parse_exception("cookie name cannot start with '$'; those are reserved by the HTTP protocol");
234 : }
235 :
236 0 : if(f_snap)
237 : {
238 0 : libdbproxy::value cookie_domain(f_snap->get_site_parameter(snap::get_name(name_t::SNAP_NAME_CORE_COOKIE_DOMAIN)));
239 0 : if(cookie_domain.nullValue())
240 : {
241 : // use the fully qualified website domain name
242 0 : f_domain = f_snap->get_website_key();
243 : }
244 : else
245 : {
246 0 : f_domain = cookie_domain.stringValue();
247 : }
248 : }
249 :
250 0 : set_value(value);
251 0 : }
252 :
253 :
254 : /** \brief Set the value of the cookie.
255 : *
256 : * This function allows you to change the value of the cookie.
257 : * By default it is set to an empty string unless you define
258 : * the value in the constructor.
259 : *
260 : * The value is encoded using the usual urlencode mechanism
261 : * as to avoid problems with controls and other data.
262 : *
263 : * We support binary data by calling the set_value() with
264 : * the QByteArray parameter.
265 : *
266 : * \param[in] value The new value of the cookie.
267 : */
268 0 : void http_cookie::set_value(QString const & value)
269 : {
270 0 : set_value(value.toUtf8());
271 0 : }
272 :
273 :
274 : /** \brief Set the value of the cookie.
275 : *
276 : * This function allows you to change the value of the cookie.
277 : * By default it is set to an empty string unless you define
278 : * the value in the constructor.
279 : *
280 : * The value is encoded using the usual urlencode mechanism
281 : * as to avoid problems with controls and other data.
282 : *
283 : * We support binary data by calling the set_value() with
284 : * the QByteArray parameter.
285 : *
286 : * \param[in] value The new value of the cookie.
287 : */
288 0 : void http_cookie::set_value(QByteArray const & value)
289 : {
290 0 : f_value = value;
291 0 : }
292 :
293 :
294 : /** \brief Set the cookie domain.
295 : *
296 : * This function is used to set the cookie domain although
297 : * it generally should not be required because the constructor
298 : * already does so automatically for you using the website
299 : * key as defined in the snap child.
300 : *
301 : * If you want to support many sub-domains, then you should
302 : * define the cookie domain as a site parameter instead.
303 : *
304 : * \note
305 : * Using the wrong domain name does nothing as the browser
306 : * ignores cookies with wrong domain names.
307 : *
308 : * \param[in] domain The new domain name for this cookie.
309 : *
310 : * \sa get_domain();
311 : */
312 0 : void http_cookie::set_domain(QString const & domain)
313 : {
314 0 : f_domain = domain;
315 :
316 : // TODO?
317 : // Enhance the check so we don't accept two periods one after
318 : // another or two dashes or a name that starts/ends with invalid
319 : // characters (i.e. cannot start/end with a dash.) Although some
320 : // of those would not be necessary if we check the domain against
321 : // the website domain name.
322 0 : int max_len(f_domain.length());
323 0 : if(max_len > 0 && f_domain[0] == '.')
324 : {
325 0 : f_domain = f_domain.mid(1);
326 0 : max_len = f_domain.length();
327 : }
328 0 : if(max_len == 0)
329 : {
330 0 : throw http_cookie_parse_exception("the domain of a cookie cannot be empty");
331 : }
332 0 : for(int i(0); i < max_len; ++i)
333 : {
334 : // TODO:
335 : // TBD -- How is that supporting Unicode characters in domain names?
336 0 : ushort c(f_domain[i].unicode());
337 0 : if((c < 'A' || c > 'Z')
338 0 : && (c < 'a' || c > 'z')
339 0 : && (c < '0' || c > '9')
340 0 : && c != '.' && c != '-' && c != '_')
341 : {
342 0 : throw http_cookie_parse_exception("the domain of a cookie must only include domain name compatible characters");
343 : }
344 : }
345 :
346 : // TODO: add a check to make sure this is a valid domain name
347 : // (i.e. to the minimum "domain + TLD")
348 0 : }
349 :
350 :
351 : /** \brief Set the path where the cookie is to be saved.
352 : *
353 : * By default the cookie is setup to be viewed everywhere (i.e. the path
354 : * is set to "/".) To constrain the cookie to a section of a website
355 : * set the path to that section here.
356 : *
357 : * This can be useful to setup a secure cookie so administrators can
358 : * get a special cookie that really only works in the administrative
359 : * part of the website.
360 : *
361 : * \param[in] path The new cookie path.
362 : *
363 : * \sa get_path();
364 : */
365 0 : void http_cookie::set_path(QString const & path)
366 : {
367 : // TODO:
368 : // TBD -- How is that supporting Unicode characters in paths?
369 : // (we may have to change them to some %XX syntax
370 0 : int max_len(path.length());
371 0 : for(int i(0); i < max_len; ++i)
372 : {
373 0 : ushort c(f_domain[i].unicode());
374 0 : if((c < ' ' || c > '~')
375 0 : && c != ',' && c != ';')
376 : {
377 0 : throw http_cookie_parse_exception("the path of a cookie must only include ASCII characters except controls, ',' and ';'.");
378 : }
379 : }
380 :
381 0 : f_path = path;
382 0 : }
383 :
384 :
385 : /** \brief Mark the cookie for deletion.
386 : *
387 : * This function sets the expiration date in the past so the cookie
388 : * gets deleted. It is usual to set the date to January 1, 1970
389 : * (i.e. Unix time of 0) and we do so here.
390 : *
391 : * \sa set_expire();
392 : * \sa set_session();
393 : * \sa get_type();
394 : */
395 0 : void http_cookie::set_delete()
396 : {
397 : // January 1, 1970 00:00:00 is represented as 0
398 0 : f_expire = QDateTime::fromMSecsSinceEpoch(0);
399 0 : }
400 :
401 :
402 : /** \brief Mark the cookie as a session cookie.
403 : *
404 : * This function invalidates the expiration date of the cookie, which is the
405 : * default. When the expiration date is invalid, it is not sent to the browser
406 : * and it transform the cookie in a session cookie.
407 : *
408 : * This type of cookie only lasts until you close the browser window. For
409 : * sensitive accounts (dealing with e-Commerce and similar) it is a good idea
410 : * to use this form of cookie.
411 : *
412 : * \sa set_delete();
413 : * \sa set_expire();
414 : * \sa get_type();
415 : */
416 0 : void http_cookie::set_session()
417 : {
418 : // use an invalid date
419 0 : f_expire = QDateTime();
420 0 : }
421 :
422 :
423 : /** \brief Set the expiration date of the cookie.
424 : *
425 : * This function changes the expiration date to the specified date and time.
426 : *
427 : * In most cases, it is easier to use the set_expire_in() function which uses
428 : * now + the specified number of seconds. The result will be the same either
429 : * way because we only send an explicit expiration date and not a Max-Age
430 : * parameter.
431 : *
432 : * In order to create a session cookie (the default), you may set the date
433 : * to an invalid date or call the set_session() function:
434 : *
435 : * \code
436 : * QDateTime invalid;
437 : * cookie.set_expiration(invalid);
438 : * \endcode
439 : *
440 : * To delete a cookie, you can set the expiration date to a date in the past.
441 : * This is also achieved by calling the set_delete() function.
442 : *
443 : * \note
444 : * If the date represents a date more than 1 year in the future, then it
445 : * gets clamped.
446 : *
447 : * \param[in] date_time The new expiration date and time.
448 : *
449 : * \sa set_session();
450 : * \sa set_delete();
451 : * \sa get_expire();
452 : * \sa get_type();
453 : */
454 0 : void http_cookie::set_expire(QDateTime const & date_time)
455 : {
456 0 : time_t const seconds(date_time.toTime_t() - (f_snap != nullptr
457 0 : ? f_snap->get_start_time()
458 0 : : time(nullptr)));
459 0 : if(seconds > 86400LL * 365LL)
460 : {
461 : // save 'now + 1 year' instead of date_time which is further in
462 : // the future and thus not HTTP 1.1 compatible
463 : //
464 0 : int64_t const start_date(f_snap != nullptr
465 0 : ? f_snap->get_start_date()
466 0 : : snap_child::get_current_date());
467 0 : f_expire = QDateTime::fromMSecsSinceEpoch(start_date / 1000LL + 86400LL * 1000LL);
468 : }
469 : else
470 : {
471 0 : f_expire = date_time;
472 : }
473 0 : }
474 :
475 :
476 : /** \brief This function sets the expiration date seconds in the future.
477 : *
478 : * This function is most often the one used and allows you to set the
479 : * expiration date of the cookie the specified number of seconds in
480 : * the future.
481 : *
482 : * The function makes use of the snap child start date plus that number
483 : * of seconds, but it sends the cookie with an Expire field (i.e. we do
484 : * not make use of the Max-Age field.)
485 : *
486 : * \note
487 : * If the HTTP cookie object was passed a pointer to the snap child
488 : * object, then the request start date is used, otherwise the current
489 : * date is used as the fallback.
490 : *
491 : * \param[in] seconds The number of seconds from now when the cookie expires.
492 : *
493 : * \sa set_expire();
494 : * \sa get_expire();
495 : * \sa set_delete();
496 : * \sa set_session();
497 : */
498 0 : void http_cookie::set_expire_in(int64_t seconds)
499 : {
500 : // clamp to 1 year (max. allowed by HTTP 1.1)
501 0 : if(seconds > 86400LL * 365LL)
502 : {
503 0 : seconds = 86400LL * 365LL;
504 : }
505 :
506 0 : int64_t const start_date(f_snap != nullptr
507 0 : ? f_snap->get_start_date()
508 0 : : snap_child::get_current_date());
509 0 : f_expire = QDateTime::fromMSecsSinceEpoch(start_date / 1000LL + seconds * 1000LL);
510 0 : }
511 :
512 :
513 : /** \brief Mark the cookie as secure.
514 : *
515 : * By default cookies are not marked as secure. Call this function with
516 : * the \p secure parameter set to true so the cookie only travels between
517 : * the browser and the server if SSL is used.
518 : *
519 : * Note that a secure cookie is not seen if the user decides to access
520 : * your website using the HTTP protocol (opposed to the HTTPS protocol.)
521 : * Websites make use of a secure cookie when they have a certificate.
522 : *
523 : * \note
524 : * Snap! implements ways to support logged in users on non-secure
525 : * connections, but with much lower rights.
526 : *
527 : * \param[in] secure Whether the cookie should be made secure or not.
528 : *
529 : * \sa get_secure();
530 : */
531 0 : void http_cookie::set_secure(bool secure)
532 : {
533 0 : f_secure = secure;
534 0 : }
535 :
536 :
537 : /** \brief Set the HttpOnly flag.
538 : *
539 : * This function changes the value of the HttpOnly flag. This flag is
540 : * used by browsers to prevent cookies from being visible form JavaScript.
541 : * This is important to avoid all sorts of session highjack attacks.
542 : *
543 : * By default the cookies are visible by JavaScript and other client
544 : * supported languages.
545 : *
546 : * \param[in] http_only Whether this cookie is only for HTTP.
547 : *
548 : * \sa get_http_only();
549 : */
550 0 : void http_cookie::set_http_only(bool http_only)
551 : {
552 0 : f_http_only = http_only;
553 0 : }
554 :
555 :
556 : /** \brief Set a comment.
557 : *
558 : * This function sets the comment of the cookie.
559 : *
560 : * In general this is verbatim information about the cookie in regard to
561 : * the user privacy.
562 : *
563 : * The set_comment_url() can also be used to set a page where the cookie
564 : * privacy information can be found.
565 : *
566 : * \param[in] comment The comment about this cookie.
567 : *
568 : * \sa get_comment();
569 : */
570 0 : void http_cookie::set_comment(QString const & comment)
571 : {
572 0 : f_comment = comment;
573 0 : }
574 :
575 :
576 : /** \brief Set a comment URL.
577 : *
578 : * This function sets the comment URL of the cookie. This is actually
579 : * made mandatory in the Snap! webserver.
580 : *
581 : * \param[in] comment_url The URL to a page tha explains the cookie usage.
582 : *
583 : * \sa get_comment_url();
584 : */
585 0 : void http_cookie::set_comment_url(QString const & comment_url)
586 : {
587 0 : f_comment_url = comment_url;
588 0 : }
589 :
590 :
591 : /** \brief Retrive the name of the cookie.
592 : *
593 : * The name of the cookie is set when you create it. It cannot be
594 : * changed.
595 : *
596 : * \return The name of the cookie.
597 : */
598 0 : const QString& http_cookie::get_name() const
599 : {
600 0 : return f_name;
601 : }
602 :
603 :
604 : /** \brief Retrieve the cookie value.
605 : *
606 : * This function returns the cookie value as a QByteValue. If you set the cookie
607 : * as a string, then you can convert it back to a string with the following:
608 : *
609 : * \code
610 : * const QByteArray& v(cookie.get_value());
611 : * QString str(QString::fromUtf8(v.data(), v.size()));
612 : * \endcode
613 : *
614 : * \return A constant reference to the cookie contents.
615 : *
616 : * \sa set_value()
617 : */
618 0 : const QByteArray& http_cookie::get_value() const
619 : {
620 0 : return f_value;
621 : }
622 :
623 :
624 : /** \brief Get the current cookie type.
625 : *
626 : * Depending on how the expiration date is setup, the cookie may have
627 : * one of the following types:
628 : *
629 : * \li HTTP_COOKIE_TYPE_PERMANENT -- the expiration date and time is valid and in the future
630 : * \li HTTP_COOKIE_TYPE_SESSION -- the expiration date and time is not valid
631 : * \li HTTP_COOKIE_TYPE_DELETE -- the expiration date and time is in the past
632 : *
633 : * \return One of the HTTP_COOKIE_TYPE_... values.
634 : *
635 : * \sa set_expire()
636 : * \sa set_delete()
637 : * \sa set_session()
638 : * \sa get_expire()
639 : * \sa set_expire_in()
640 : */
641 0 : http_cookie::http_cookie_type_t http_cookie::get_type() const
642 : {
643 0 : if(!f_expire.isValid())
644 : {
645 0 : return http_cookie_type_t::HTTP_COOKIE_TYPE_SESSION;
646 : }
647 : // TBD -- Utc? or not Utc? As far as I know the cookie date is
648 : // in UTC so we need to compare with the UTC current date
649 0 : if(f_expire < QDateTime::currentDateTimeUtc())
650 : {
651 0 : return http_cookie_type_t::HTTP_COOKIE_TYPE_DELETE;
652 : }
653 0 : return http_cookie_type_t::HTTP_COOKIE_TYPE_PERMANENT;
654 : }
655 :
656 :
657 : /** \brief Get the cookie domain information.
658 : *
659 : * This function retreives the current domain information of the cookie.
660 : * In general the domain is equal to the website key.
661 : *
662 : * \return The domain defined in this cookie.
663 : *
664 : * \sa set_domain();
665 : */
666 0 : const QString& http_cookie::get_domain() const
667 : {
668 0 : return f_domain;
669 : }
670 :
671 :
672 : /** \brief Retrieve the path under which the cookie is valid.
673 : *
674 : * This function retrieves the current path for this cookie.
675 : * By default it is set to "/" which means the cookie is
676 : * available over the entire website. It is rarely necessary
677 : * to change this value.
678 : *
679 : * \return The current path for this cookie.
680 : *
681 : * \sa set_path();
682 : */
683 0 : const QString& http_cookie::get_path() const
684 : {
685 0 : return f_path;
686 : }
687 :
688 :
689 : /** \brief Get the expiration date.
690 : *
691 : * This function returns the current expiration date. The date represents
692 : * different status of the cookie which can be determined by calling the
693 : * get_type() function.
694 : *
695 : * The default is an invalid cookie which means that the cookie is a
696 : * session cookie (lasts until the browser is closed.)
697 : *
698 : * \return The expiration date of this cookie.
699 : *
700 : * \sa set_expire();
701 : * \sa set_expire_in();
702 : * \sa set_delete();
703 : * \sa set_session();
704 : * \sa get_type();
705 : */
706 0 : const QDateTime& http_cookie::get_expire() const
707 : {
708 0 : return f_expire;
709 : }
710 :
711 :
712 : /** \brief Retrieve whether the cookie is secure.
713 : *
714 : * This function returns whether the cookie should only travel on a
715 : * secure (SSL) connection.
716 : *
717 : * The default is false (cookie is visible on non-secure connections.)
718 : *
719 : * \return true if the cookie is only for secure connections, false otherwise.
720 : *
721 : * \sa set_secure();
722 : */
723 0 : bool http_cookie::get_secure() const
724 : {
725 0 : return f_secure;
726 : }
727 :
728 :
729 : /** \brief Retrieve whether the cookie is only for HTTP.
730 : *
731 : * By default cookies are visible to JavaScript and other client languages.
732 : * This parameter can be used to hide the content of the cookie from praying
733 : * eyes and only allow the data to travel between the browser and server.
734 : *
735 : * The default is false.
736 : *
737 : * \return The current HttpOnly flag status.
738 : *
739 : * \sa set_http_only();
740 : */
741 0 : bool http_cookie::get_http_only() const
742 : {
743 0 : return f_http_only;
744 : }
745 :
746 :
747 : /** \brief Retrieve the cookie comment.
748 : *
749 : * This function returns the verbatim cookie comment.
750 : *
751 : * \return The verbatim comment of the cookie.
752 : *
753 : * \sa set_comment();
754 : */
755 0 : QString const& http_cookie::get_comment() const
756 : {
757 0 : return f_comment;
758 : }
759 :
760 :
761 : /** \brief Retrieve the cookie comment URL.
762 : *
763 : * This function returns the cookie comment URL.
764 : *
765 : * \return The comment URL of the cookie.
766 : *
767 : * \sa set_comment_url();
768 : */
769 0 : QString const& http_cookie::get_comment_url() const
770 : {
771 0 : return f_comment_url;
772 : }
773 :
774 :
775 : /** \brief Transform the cookie for the HTTP header.
776 : *
777 : * This function transforms the cookie so it works as an HTTP header.
778 : * It follows the HTTP 1.1 specifications, although it never makes
779 : * use of the Max-Age field.
780 : *
781 : * \return A valid HTTP cookie header.
782 : */
783 0 : QString http_cookie::to_http_header() const
784 : {
785 : // Note: the name was already checked for invalid characters
786 0 : QString result("Set-Cookie: " + f_name + "=");
787 :
788 0 : char const * v(f_value.constData());
789 0 : int const max_len(f_value.size());
790 0 : for(int i(0); i < max_len; ++i)
791 : {
792 0 : char c(v[i]);
793 0 : if(c == 0x21
794 0 : || (c >= 0x23 && c <= 0x2B)
795 0 : || (c >= 0x2D && c <= 0x3A)
796 0 : || (c >= 0x3C && c <= 0x5B)
797 0 : || (c >= 0x5D && c <= 0x7E))
798 : {
799 0 : result += c;
800 : }
801 : else
802 : {
803 : // add the byte as %XX
804 0 : result += QString("%%%1").arg(c, 2, 16, QChar('0'));
805 : }
806 : }
807 :
808 0 : switch(get_type())
809 : {
810 0 : case http_cookie_type_t::HTTP_COOKIE_TYPE_PERMANENT:
811 : // compute date/time
812 : // HTTP format generates: Sun, 06 Nov 1994 08:49:37 GMT
813 : // (see http://tools.ietf.org/html/rfc2616#section-3.3.1)
814 : //
815 0 : result += "; Expires=" + f_expire.toString("ddd, dd MMM yyyy hh:mm:ss' GMT'");
816 :
817 : // Modern browsers are expected to use the Max-Age=... field
818 : // instead of the Expires to avoid potential date synchronization
819 : // problems between our server and the client
820 : // (see http://tools.ietf.org/html/rfc6265#section-4.1.2.2)
821 : //
822 : // TBD: although this works, we may want to know the exact
823 : // intend of the person setting the expiration time and
824 : // maybe use that amount (or even change our current
825 : // expire to a max-age and calculate the date in Expires=...
826 : // and not the one in Max-Age.)
827 : {
828 0 : time_t const max_age(f_expire.toTime_t() - time(nullptr));
829 0 : if(max_age > 0)
830 : {
831 0 : result += QString("; Max-Age=%1").arg(max_age);
832 : }
833 : }
834 0 : break;
835 :
836 0 : case http_cookie_type_t::HTTP_COOKIE_TYPE_SESSION:
837 : // no Expires
838 0 : break;
839 :
840 0 : case http_cookie_type_t::HTTP_COOKIE_TYPE_DELETE:
841 : // no need to waste time computing that date
842 0 : result += "; Expires=Thu, 01-Jan-1970 00:00:01 GMT";
843 0 : break;
844 :
845 : }
846 :
847 0 : if(!f_domain.isEmpty())
848 : {
849 : // the domain sanity was already checked so we can save it as it here
850 0 : result += "; Domain=" + f_domain;
851 : }
852 :
853 0 : if(!f_path.isEmpty())
854 : {
855 : // the path sanity was already checked so we can save it as it here
856 0 : result += "; Path=" + f_path;
857 : }
858 :
859 0 : if(f_secure)
860 : {
861 0 : result += "; Secure";
862 : }
863 :
864 0 : if(f_http_only)
865 : {
866 0 : result += "; HttpOnly";
867 : }
868 :
869 0 : if(!f_comment.isEmpty())
870 : {
871 : // we need to escape all "bad" characters, not just quotes
872 0 : QString safe(f_comment);
873 0 : result += "; Comment=\"" + safe.replace("\"", "") + "\"";
874 : }
875 :
876 0 : if(!f_comment_url.isEmpty())
877 : {
878 : // we need to escape all "bad" characters, not just quotes
879 0 : QString safe(f_comment_url);
880 0 : result += "; CommentURL=\"" + safe.replace("\"", "") + "\"";
881 : }
882 :
883 0 : return result;
884 : }
885 :
886 :
887 :
888 6 : } // namespace snap
889 :
890 : // vim: ts=4 sw=4 et
|