LCOV - code coverage report
Current view: top level - snapwebsites - snap_child_cache_control.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 1 172 0.6 %
Date: 2019-12-15 17:13:15 Functions: 2 8 25.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.13