Line data Source code
1 : // Snap Websites Server -- handle Cache-Control settings
2 : // Copyright (c) 2011-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/snap_child.h"
22 :
23 :
24 : // snapwebsites lib
25 : //
26 : #include "snapwebsites/log.h"
27 : #include "snapwebsites/snapwebsites.h"
28 :
29 :
30 : // snapdev lib
31 : //
32 : #include <snapdev/not_reached.h>
33 :
34 :
35 : // Qt lib
36 : //
37 : #include <QLocale>
38 :
39 :
40 : // boost lib
41 : //
42 : #include <boost/algorithm/string/join.hpp>
43 :
44 :
45 : // last include
46 : //
47 : #include <snapdev/poison.h>
48 :
49 :
50 :
51 : namespace snap
52 : {
53 :
54 :
55 : /** \brief Retrieve a reference to the Cache-Control data from the client.
56 : *
57 : * The client has the possibility to send the server a Cache-Control
58 : * field. This function can be used to retrieve a reference to that
59 : * data. It is somewhat complicated to convert all the fields of
60 : * the Cache-Control field so it is done by the snap_child object
61 : * in cache_control_settings objects.
62 : *
63 : * In most cases, plugins are only interested by 'max-stale' and
64 : * 'min-fresh' if they deal with the cache. The 'no-transform'
65 : * may be useful to download the original of a document, that
66 : * being said, I don't see how the user could tweak the browser
67 : * to do such a thing.
68 : *
69 : * \note
70 : * This returns a constant cache_control_settings object because there
71 : * is no reason for anyone to modify this value.
72 : *
73 : * \return A constant reference to the client cache control info.
74 : *
75 : * \sa server_cache_control()
76 : * \sa page_cache_control()
77 : */
78 0 : cache_control_settings const & snap_child::client_cache_control() const
79 : {
80 0 : return f_client_cache_control;
81 : }
82 :
83 :
84 : /** \brief Retrieve a reference to the Cache-Control data from the server.
85 : *
86 : * The server and all its plugins are expected to make changes to the
87 : * server cache control information obtained through this function.
88 : *
89 : * In most cases, functions should be called to switch the value from
90 : * the default to whatever value you can use in that field. That way
91 : * you do not override another's plugin settings. For fields that
92 : * include values, the smallest value should be kept. You can do so
93 : * using the update_...() functions.
94 : *
95 : * \todo
96 : * See: SNAP-650
97 : * Right now there is no real priority between the server and page
98 : * settings. I think the page should have priority, but that's
99 : * complicated to know what that means... unless we add a flag for
100 : * each field to know whether someone changed that field or not
101 : * (if changed in the page, use that field and totally ignore the
102 : * server field.) The server field is then checked and used no matter
103 : * what.
104 : *
105 : * See the attachment.cpp and path.cpp files (plugins) for use examples.
106 : *
107 : * \return A reference to the server cache control info.
108 : *
109 : * \sa page_cache_control()
110 : * \sa client_cache_control()
111 : */
112 0 : cache_control_settings & snap_child::server_cache_control()
113 : {
114 0 : return f_server_cache_control;
115 : }
116 :
117 :
118 : /** \brief Retrieve a reference to the Cache-Control data from the page.
119 : *
120 : * The page has the ability to have its own Cache-Control settings. This
121 : * function is used to retrieve a reference to that cache data and tweak it.
122 : *
123 : * It is rather complicated to properly handle all the Cache-Control fields
124 : * so it is done by the snap_child and cache_control_settings objects.
125 : *
126 : * This control settings are usually given priority over the server cache
127 : * control settings since they are specific to a given page.
128 : *
129 : * If you are programming a plugin that control caches server wide,
130 : * then you want to use the server_cache_control() instead.
131 : *
132 : * \todo
133 : * Right now there is no real priority between the server and page
134 : * settings. I think the page should have priority, but that's
135 : * complicated to know what that means... unless we add a flag for
136 : * each field to know whether someone changed that field or not
137 : * (if changed in the page, use that field and totally ignore the
138 : * server field.) The server field is then checked and used no matter
139 : * what.
140 : *
141 : * See the attachment.cpp and path.cpp files (plugins) for use examples.
142 : *
143 : * \return A reference to the page cache control info.
144 : *
145 : * \sa server_cache_control()
146 : * \sa client_cache_control()
147 : */
148 0 : cache_control_settings & snap_child::page_cache_control()
149 : {
150 0 : return f_page_cache_control;
151 : }
152 :
153 :
154 : /** \brief Check the current cache settings to know whether caching is turned on.
155 : *
156 : * By default caching is turned ON for the page and server, but the
157 : * client may request for caches to not be used.
158 : *
159 : * Also the page "content::cache_control" field may include parameters
160 : * that require caching to be turned on.
161 : *
162 : * Finally, the server caching parameters are set by various plugins
163 : * which may also turn on or off various caching features.
164 : *
165 : * This function checks whether the cache is to be turned on for this request.
166 : */
167 0 : bool snap_child::no_caching() const
168 : {
169 : // IMPORTANT NOTE: A 'max-age' value of 0 means 'do not cache', also
170 : // the value may be set to IGNORE_VALUE (-1).
171 : //
172 : // Note: The client may send us a "Cache-Control: no-cache" request,
173 : // which means we do not want to return data from any cache,
174 : // however, that does not mean we cannot send a cached reply!
175 : //
176 0 : return f_page_cache_control.get_no_cache()
177 0 : || f_page_cache_control.get_max_age() <= 0
178 0 : || f_server_cache_control.get_no_store()
179 0 : || f_server_cache_control.get_max_age() <= 0;
180 : }
181 :
182 :
183 : /** \brief Check the request ETag and eventually generate an HTTP 304 reply.
184 : *
185 : * First this function checks whether the ETag of the client request
186 : * is the same as what the server is about to send back to the client.
187 : * If the ETag values are not equal, then the function returns
188 : * immediately.
189 : *
190 : * When the ETag values are equal, this function kills the child process
191 : * after sending an HTTP 304 reply to the user and to the logger.
192 : *
193 : * The reply does not include any HTML because it is not allowed by the
194 : * specification (and there is no point since the client will reuse its
195 : * cache anyway.)
196 : *
197 : * \note
198 : * The header fields must include the following if they were
199 : * there with the 200 reply:
200 : *
201 : * \li Cache-Control
202 : * \li Content-Location
203 : * \li Date
204 : * \li ETag
205 : * \li Expires
206 : * \li Vary
207 : *
208 : * \warning
209 : * This function does not return when the 304 reply is sent.
210 : *
211 : * \see
212 : * https://tools.ietf.org/html/rfc7232#section-4.1
213 : */
214 0 : void snap_child::not_modified()
215 : {
216 : try
217 : {
218 : // if the "Cache-Control" header was specified with "no-cache",
219 : // then we have to re-send the data no matter what
220 : //
221 0 : if(no_caching())
222 : {
223 : // never caching this data, never send the 304.
224 : //
225 0 : return;
226 : }
227 0 : if(f_page_cache_control.get_public()
228 0 : || f_server_cache_control.get_public())
229 : {
230 : // See SNAP-650
231 : //
232 : // let snap.cgi cache this one for us, right now we don't yet
233 : // have snap.cgi converted to a real proxy so we have to reply
234 : // with a full 200 OK response to make that cache work... once
235 : // we have a correct proxy, we will re-enable a 304 Not Modified
236 : // response even for snap.cgi which can then update its caches
237 : // accordingly (i.e. if snap.cgi does not have a cache, it can
238 : // send a request without conditionals and save the new response
239 : // and if it already has a file, it can include conditionals
240 : // that are defined from that file.)
241 : //
242 0 : return;
243 : }
244 :
245 : // if the data was cached, including an ETag parameter, we may
246 : // receive this request even though the browser has a version
247 : // cached but it asks the server whether it changed so we have
248 : // to return a 304;
249 : //
250 : // this has to be checked before the If-Modified-Since
251 : //
252 0 : QString const if_none_match(snapenv("HTTP_IF_NONE_MATCH"));
253 0 : if(!if_none_match.isEmpty()
254 0 : && if_none_match == get_header("ETag"))
255 : {
256 : // this or die() was already called, forget it
257 : //
258 0 : if(f_died)
259 : {
260 : // avoid loops
261 0 : return;
262 : }
263 0 : f_died = true;
264 :
265 : // define a default error name if undefined
266 : //
267 0 : QString err_name;
268 0 : define_http_name(http_code_t::HTTP_CODE_NOT_MODIFIED, err_name);
269 :
270 : // log the fact we are sending a 304
271 : //
272 0 : SNAP_LOG_INFO("snap_child_cache_control.cpp:not_modified(): replying with HTTP 304 for ")(f_uri.path())(" (1)");
273 :
274 0 : if(f_is_being_initialized)
275 : {
276 : // send initialization process the info about the fact
277 : // (this should never occur, we may instead want to call die()?)
278 : //
279 0 : trace(QString("Error: not_modified() called: %1\n").arg(f_uri.path()));
280 0 : trace("#END\n");
281 : }
282 : else
283 : {
284 : // On error we do not return the HTTP protocol, only the Status field
285 : // it just needs to be first to make sure it works right
286 0 : set_header("Status",
287 0 : QString("%1 %2\n")
288 0 : .arg(static_cast<int>(http_code_t::HTTP_CODE_NOT_MODIFIED))
289 0 : .arg(err_name),
290 : HEADER_MODE_EVERYWHERE);
291 :
292 : // Remove the Content-Type header, this is simpler than requiring
293 : // the correct content type information
294 : //
295 0 : set_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER),
296 : "",
297 : HEADER_MODE_EVERYWHERE);
298 :
299 : // since we are going to exit without calling the attach_to_session()
300 : //
301 0 : server::pointer_t server( f_server.lock() );
302 0 : if(!server)
303 : {
304 0 : throw snap_logic_exception("server pointer is nullptr");
305 : }
306 0 : server->attach_to_session();
307 :
308 : // in case there are any cookies, send them along too
309 : //
310 0 : output_headers(HEADER_MODE_NO_ERROR);
311 :
312 : // no data to output with 304 (it's forbidden)
313 : }
314 :
315 : // the cache worked as expected
316 : //
317 0 : exit(0);
318 : }
319 :
320 : // No "If-None-Match" header found, so check fo the next
321 : // possible modification check which is the "If-Modified-Since"
322 : //
323 0 : QString const if_modified_since(snapenv("HTTP_IF_MODIFIED_SINCE"));
324 0 : QString const last_modified_str(get_header("Last-Modified"));
325 0 : if(!if_modified_since.isEmpty()
326 0 : && !last_modified_str.isEmpty())
327 : {
328 0 : time_t const modified_since(string_to_date(if_modified_since));
329 0 : time_t const last_modified(string_to_date(last_modified_str));
330 :
331 : // TBD: should we use >= instead of == here?
332 : // (see in snapcgi/src/snap.cpp too)
333 : //
334 0 : if(modified_since == last_modified
335 0 : && modified_since != -1)
336 : {
337 : // this or die() was already called, forget it
338 : //
339 0 : if(f_died)
340 : {
341 : // avoid loops
342 : //
343 0 : return;
344 : }
345 0 : f_died = true;
346 :
347 : // define a default error name if undefined
348 : //
349 0 : QString err_name;
350 0 : define_http_name(http_code_t::HTTP_CODE_NOT_MODIFIED, err_name);
351 :
352 : // log the fact we are sending a 304
353 : //
354 0 : SNAP_LOG_INFO("snap_child_cache_control.cpp:not_modified(): replying with HTTP 304 for ")(f_uri.path())(" (2)");
355 :
356 0 : if(f_is_being_initialized)
357 : {
358 : // send initialization process the info about the fact
359 : // (this should never occur, we may instead want to call die()?)
360 : //
361 0 : trace(QString("Error: not_modified() called: %1\n").arg(f_uri.path()));
362 0 : trace("#END\n");
363 : }
364 : else
365 : {
366 : // On error we do not return the HTTP protocol, only the Status field
367 : // it just needs to be first to make sure it works right
368 : //
369 0 : set_header("Status",
370 0 : QString("%1 %2\n")
371 0 : .arg(static_cast<int>(http_code_t::HTTP_CODE_NOT_MODIFIED))
372 0 : .arg(err_name),
373 : HEADER_MODE_EVERYWHERE);
374 :
375 : // Remove the Content-Type header, this is simpler than requiring
376 : // the correct content type information
377 : //
378 0 : set_header(get_name(name_t::SNAP_NAME_CORE_CONTENT_TYPE_HEADER),
379 : "",
380 : HEADER_MODE_EVERYWHERE);
381 :
382 : // since we are going to exit without calling the attach_to_session()
383 : //
384 0 : server::pointer_t server( f_server.lock() );
385 0 : if(!server)
386 : {
387 0 : throw snap_logic_exception("server pointer is nullptr");
388 : }
389 0 : server->attach_to_session();
390 :
391 : // in case there are any cookies, send them along too
392 : //
393 0 : output_headers(HEADER_MODE_NO_ERROR);
394 :
395 : // no data to output with 304 (it's forbidden)
396 : }
397 :
398 : // the cache worked as expected
399 : //
400 0 : exit(0);
401 : }
402 : }
403 :
404 : // No match from client, must return normally
405 : //
406 0 : return;
407 : }
408 0 : catch(...)
409 : {
410 : // ignore all errors because at this point we must die quickly.
411 : //
412 0 : SNAP_LOG_FATAL("snap_child_cache_control.cpp:not_modified(): try/catch caught an exception");
413 : }
414 :
415 : // exit with an error
416 0 : exit(1);
417 : }
418 :
419 :
420 :
421 : /** \brief Setup the headers in link with caching.
422 : *
423 : * This function takes the f_server_cache_control and f_page_cache_control
424 : * information and generates the corresponding HTTP headers. This funciton
425 : * is called just before we output the HTTP headers in the output buffer.
426 : *
427 : * The HTTP headers generated by this function are:
428 : *
429 : * \li Cache-Control
430 : * \li Pragma
431 : * \li Expires
432 : * \li Cache-Tag (see snapserver.conf and add_tags() for details)
433 : *
434 : * See the cache_control_settings class for details about all the possible
435 : * cache options.
436 : *
437 : * By default the cache controls are not modified meaning that the page is
438 : * marked as 'no-cache'. In other words, it won't be cached at all. The
439 : * Cache-Control field may also receive 'no-store' in that case.
440 : *
441 : * HTTP Cache-Control Reference:
442 : *
443 : * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
444 : *
445 : * \note
446 : * This function gives us one single point where the Cache-Control field
447 : * (and equivalent HTTP/1.0) are set so it makes it a lot easier to make
448 : * sure that the fields are set appropriately in all cases.
449 : *
450 : * \note
451 : * The Cache-Tag field may be renamed in the snapserver.conf file. If no
452 : * cache tags were specified with the add_tag() function, then the field
453 : * doesn't get generated.
454 : */
455 0 : void snap_child::set_cache_control()
456 : {
457 : // the Cache-Control is composed of multiple sub-fields
458 0 : snap_string_list cache_control_fields;
459 :
460 : // if the client requested "no-cache" or "no-store" we return a
461 : // cache control header which bypasses all caches, very important!
462 : //
463 : //SNAP_LOG_WARNING("no caching? ")(no_caching() ? "no-caching" : "CACHING!")
464 : // (" - ")(f_client_cache_control.get_no_cache() ? "client no-caching" : "client CACHING!")
465 : // (" - ")(f_client_cache_control.get_no_store() ? "client no-store" : "client STORE!")
466 : // (" - ")(f_page_cache_control.get_no_cache() ? "page no-cache" : "page CACHING!")
467 : // (" - ")(f_server_cache_control.get_no_store() ? "server no-store" : "server STORE!")
468 : // (" - ")(f_page_cache_control.get_max_age() <= 0 ? QString("page max-age=%1").arg(f_page_cache_control.get_max_age()) : "page NO-MAX-AGE!")
469 : // (" - ")(f_server_cache_control.get_max_age() <= 0 ? "server max-age" : "server NO-MAX-AGE!")
470 : //;
471 :
472 : // make sure this data never gets transformed
473 : //
474 : // in our case, it can be very important for OAuth2 answers and
475 : // other similar data... although OAuth2 replies should not be
476 : // cached!
477 : //
478 0 : if(f_client_cache_control.get_no_transform()
479 0 : || f_page_cache_control.get_no_transform()
480 0 : || f_server_cache_control.get_no_transform())
481 : {
482 0 : cache_control_fields << "no-transform";
483 : }
484 :
485 0 : if(no_caching())
486 : {
487 : // using Pragma for older browsers, although from what I have read
488 : // it is probably never used by any live browser
489 : //
490 0 : set_header("Pragma", "no-cache", HEADER_MODE_EVERYWHERE);
491 :
492 : // use a date in the past so nothing gets cached
493 : //
494 0 : set_header("Expires", "Sat, 01 Jan 2000 00:00:00 GMT", HEADER_MODE_EVERYWHERE);
495 :
496 : // I put all the possible "do not cache anything" in this case
497 : //
498 0 : cache_control_fields << "no-cache";
499 :
500 : // put no-store only if specified somewhere (client, page, plugins)
501 : //
502 0 : if(f_client_cache_control.get_no_store()
503 0 : || f_page_cache_control.get_no_store()
504 0 : || f_server_cache_control.get_no_store())
505 : {
506 0 : cache_control_fields << "no-store";
507 : }
508 :
509 : // put must-revalidate if specified by page or plugins
510 : //
511 0 : if(f_page_cache_control.get_must_revalidate()
512 0 : || f_server_cache_control.get_must_revalidate())
513 : {
514 0 : cache_control_fields << "must-revalidate";
515 : }
516 :
517 : // this is to make sure IE understands that it is not to cache anything
518 : //
519 0 : cache_control_fields << "post-check=0"; // IE special background processing
520 0 : cache_control_fields << "pre-check=0"; // IE special "really too late" flag
521 :
522 : // non-cached data is also marked private since intermediate
523 : // shared proxy caches should not cache this data at all
524 : // (the specs says you should not have public or private when
525 : // specifying no-cache, but it looks like it works better in
526 : // some cases for some browsers; if they just ignore that entry
527 : // as expected, it will not hurt)
528 : //
529 0 : cache_control_fields << "private";
530 : }
531 : else
532 : {
533 : // HTTP/1.0 can only be sent public data; we use this
534 : // flag to know whether any Cache-Control settings are
535 : // contradictory and prevents us from asking anyone to
536 : // cache anything if using HTTP/1.0
537 : //
538 0 : bool public_data(true);
539 :
540 : // get the smallest max_age specified
541 : //
542 : // IMPORTANT: unless no_caching() fails, one of the get_max_age()
543 : // function will not return 0 or -1
544 : //
545 0 : int64_t const max_age(f_page_cache_control.minimum(f_page_cache_control.get_max_age(), f_server_cache_control.get_max_age()));
546 0 : cache_control_fields << QString("max-age=%1").arg(max_age);
547 :
548 : // This is the default when only max-age is specified
549 : //cache_control_fields << QString("post-check=%1").arg(max_age);
550 : //cache_control_fields << QString("pre-check=%1").arg(max_age);
551 :
552 : // any 's-maxage' info?
553 : //
554 : // IMPORTANT NOTE: here max_age cannot be 0 or -1
555 : //
556 0 : int64_t const s_maxage(f_page_cache_control.minimum(f_page_cache_control.get_s_maxage(), f_server_cache_control.get_s_maxage()));
557 0 : if(s_maxage != cache_control_settings::IGNORE_VALUE
558 0 : && s_maxage < max_age)
559 : {
560 : // request for intermediate proxies to not cache data for more
561 : // than the specified value; we do not send this header if
562 : // larger than max_age since caches should respect 'max-age'
563 : // too so there would be no need to have a larger 's-maxage'
564 : //
565 0 : cache_control_fields << QString("s-maxage=%1").arg(s_maxage);
566 : }
567 :
568 : // although we specify max-age in case a browser doesn't understand
569 : // immutable, we want the immutable flag as well if true in the
570 : // page or server; this means the data never dies out (we set the
571 : // CSS and JS files to immutable because their version has to be
572 : // changed whenever you make changes to those files.)
573 : //
574 0 : if(f_page_cache_control.get_immutable()
575 0 : || f_server_cache_control.get_immutable())
576 : {
577 0 : cache_control_fields << "immutable";
578 : }
579 :
580 : // choose between public and private (or "neither"--the default is
581 : // private, really, but we leave that out if none was set to true)
582 : //
583 : // private has priority over public
584 : //
585 0 : if(f_page_cache_control.get_private()
586 0 : || f_server_cache_control.get_private())
587 : {
588 0 : cache_control_fields << "private";
589 0 : public_data = false;
590 : }
591 0 : else if(f_page_cache_control.get_public()
592 0 : || f_server_cache_control.get_public())
593 : {
594 0 : cache_control_fields << "public";
595 :
596 : // when the cache is made public, we may need to output
597 : // a no-cache and private fields with lists of field names
598 : //
599 0 : cache_control_settings::fields_t all_revalidate_fields(f_page_cache_control.get_revalidate_field_names());
600 0 : cache_control_settings::fields_t const server_revalidate_fields(f_server_cache_control.get_revalidate_field_names());
601 0 : all_revalidate_fields.insert(server_revalidate_fields.begin(), server_revalidate_fields.end());
602 0 : if(!all_revalidate_fields.empty())
603 : {
604 : // note that this value must be quoted in part because it
605 : // can include commas
606 : //
607 0 : bool add_comma(false);
608 0 : QString fields_to_revalidate("no-cache=\"");
609 0 : for(auto t : all_revalidate_fields)
610 : {
611 0 : if(add_comma)
612 : {
613 0 : fields_to_revalidate += ',';
614 : }
615 : // field name must be quoted
616 : //
617 0 : fields_to_revalidate += QString::fromUtf8(t.c_str());
618 0 : add_comma = true;
619 : }
620 :
621 : // although thelist may not be empty, each string within
622 : // the list may be empty and in that case we do not want
623 : // to have a "no-cache" field by itself
624 : //
625 0 : if(add_comma)
626 : {
627 : // close the quotation mark
628 : //
629 0 : fields_to_revalidate += "\"";
630 0 : cache_control_fields << fields_to_revalidate;
631 : }
632 : }
633 :
634 0 : cache_control_settings::fields_t all_private_fields(f_page_cache_control.get_private_field_names());
635 0 : cache_control_settings::fields_t const server_private_fields(f_server_cache_control.get_private_field_names());
636 0 : all_private_fields.insert(server_private_fields.begin(), server_private_fields.end());
637 0 : if(!all_private_fields.empty())
638 : {
639 : // note that this value must be quoted in part because it
640 : // can include commas
641 : //
642 0 : bool add_comma(false);
643 0 : QString fields_to_never_cache("private=\"");
644 0 : for(auto t : all_private_fields)
645 : {
646 0 : if(add_comma)
647 : {
648 0 : fields_to_never_cache += ',';
649 : }
650 : // field name must be quoted
651 : //
652 0 : fields_to_never_cache += QString::fromUtf8(t.c_str());
653 0 : add_comma = true;
654 : }
655 : // although thelist may not be empty, each string within
656 : // the list may be empty and in that case we do not want
657 : // to have a "no-cache" field by itself
658 : //
659 0 : if(add_comma)
660 : {
661 : // close the quotation mark
662 : //
663 0 : fields_to_never_cache += "\"";
664 0 : cache_control_fields << fields_to_never_cache;
665 : }
666 : }
667 : }
668 : else
669 : {
670 : // remember, the default is "private"
671 : //
672 0 : public_data = false;
673 : }
674 :
675 : // whether the client should always revalidate with the server
676 : // (which means we get a hit, so try not to use that option!)
677 : //
678 0 : if(f_page_cache_control.get_must_revalidate()
679 0 : || f_server_cache_control.get_must_revalidate())
680 : {
681 0 : cache_control_fields << "must-revalidate";
682 : }
683 0 : else if(f_page_cache_control.get_proxy_revalidate()
684 0 : || f_server_cache_control.get_proxy_revalidate())
685 : {
686 : // if we don't add must-revalidate, we may instead
687 : // add proxy-revalidate which asks the proxy cache
688 : // to always revalidate
689 : //
690 0 : cache_control_fields << "proxy-revalidate";
691 : }
692 :
693 : // so is the data considered public for HTTP/1.0?
694 : //
695 0 : if(public_data)
696 : {
697 : // make sure that the Pragma is not defined
698 : //
699 0 : set_header("Pragma", "", HEADER_MODE_EVERYWHERE);
700 :
701 : // use our start date (which is converted from micro-seconds
702 : // to seconds) plus the max_age value for Expires
703 : //
704 : // note that we force the date to appear in English as the
705 : // HTTP spec. tells us to do
706 : //
707 0 : QDateTime expires(QDateTime().toUTC());
708 0 : expires.setTime_t(f_start_date / 1000000 + max_age - 1);
709 0 : QLocale us_locale(QLocale::English, QLocale::UnitedStates);
710 0 : set_header("Expires", us_locale.toString(expires, "ddd, dd MMM yyyy hh:mm:ss' GMT'"), HEADER_MODE_EVERYWHERE);
711 : }
712 : else
713 : {
714 : // HTTP/1.0 will not understand the "private" properly so we
715 : // have to make sure no caching happens in this case
716 : // (we could check the protocol to make sure we have HTTP/1.0
717 : // but HTTP/1.1 is expected to ignore these two headers when
718 : // Cache-Control is defined)
719 : //
720 0 : set_header("Pragma", "no-cache", HEADER_MODE_EVERYWHERE);
721 0 : set_header("Expires", "Sat, 1 Jan 2000 00:00:00 GMT", HEADER_MODE_EVERYWHERE);
722 : }
723 :
724 : // check whether there are cache tags to add here
725 : //
726 0 : cache_control_settings::tags_t all_tags(f_page_cache_control.get_tags());
727 0 : cache_control_settings::tags_t const server_tags(f_server_cache_control.get_tags());
728 0 : all_tags.insert(server_tags.begin(), server_tags.end());
729 0 : if(!all_tags.empty())
730 : {
731 : // we have tags, let's see whether we have HTTP field names
732 : //
733 0 : QString const cache_tags_param(get_server_parameter("cache_tags"));
734 0 : if(!cache_tags_param.isEmpty())
735 : {
736 0 : QString cache_tags;
737 0 : for(auto t : all_tags)
738 : {
739 0 : if(!t.empty())
740 : {
741 0 : if(!cache_tags.isEmpty())
742 : {
743 0 : cache_tags += ',';
744 : }
745 0 : cache_tags += QString::fromUtf8(t.c_str());
746 : }
747 : }
748 0 : snap_string_list const cache_tag_names(cache_tags_param.split(","));
749 0 : for(auto name : cache_tag_names)
750 : {
751 0 : QString const n(name.trimmed());
752 0 : if(!n.isEmpty())
753 : {
754 0 : set_header(n, cache_tags);
755 : }
756 : }
757 : }
758 : }
759 : }
760 :
761 0 : set_header("Cache-Control", cache_control_fields.join(","), HEADER_MODE_EVERYWHERE);
762 0 : }
763 :
764 :
765 6 : } // namespace snap
766 : // vim: ts=4 sw=4 et
|