LCOV - code coverage report
Current view: top level - edhttp - http_date.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 266 0.0 %
Date: 2022-07-09 10:44:38 Functions: 0 14 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2011-2022  Made to Order Software Corp.  All Rights Reserved
       2             : //
       3             : // https://snapwebsites.org/project/edhttp
       4             : // contact@m2osw.com
       5             : //
       6             : // This program is free software: you can redistribute it and/or modify
       7             : // it under the terms of the GNU General Public License as published by
       8             : // the Free Software Foundation, either version 3 of the License, or
       9             : // (at your option) any later version.
      10             : //
      11             : // This program is distributed in the hope that it will be useful,
      12             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      13             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14             : // GNU General Public License for more details.
      15             : //
      16             : // You should have received a copy of the GNU General Public License
      17             : // along with this program.  If not, see <https://www.gnu.org/licenses/>.
      18             : 
      19             : 
      20             : // self
      21             : //
      22             : #include    "edhttp/http_date.h"
      23             : 
      24             : #include    "edhttp/exception.h"
      25             : #include    "edhttp/mkgmtime.h"
      26             : 
      27             : 
      28             : // snapdev
      29             : //
      30             : #include    <snapdev/to_lower.h>
      31             : #include    <snapdev/trim_string.h>
      32             : 
      33             : 
      34             : // C++
      35             : //
      36             : #include    <iomanip>
      37             : #include    <sstream>
      38             : 
      39             : 
      40             : // C
      41             : //
      42             : #include    <string.h>
      43             : 
      44             : 
      45             : // last include
      46             : //
      47             : #include    <snapdev/poison.h>
      48             : 
      49             : 
      50             : 
      51             : namespace edhttp
      52             : {
      53             : 
      54             : 
      55             : namespace
      56             : {
      57             : 
      58             : 
      59             : char const * g_week_day_name[] =
      60             : {
      61             :     "Sunday", "Monday", "Tuesday", "Wedneday", "Thursday", "Friday", "Saturday"
      62             : };
      63             : 
      64             : int const g_week_day_length[] = { 6, 6, 7, 8, 8, 6, 8 }; // strlen() of g_week_day_name's
      65             : 
      66             : char const * g_month_name[] =
      67             : {
      68             :     "January", "February", "Marsh", "April", "May", "June",
      69             :     "July", "August", "September", "October", "November", "December"
      70             : };
      71             : 
      72             : int const g_month_length[] = { 7, 8, 5, 5, 3, 4, 4, 6, 9, 7, 8, 8 }; // strlen() of g_month_name
      73             : 
      74             : int const g_month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
      75             : 
      76             : signed char const g_timezone_adjust[26] =
      77             : {
      78             :     /* A */ -1,
      79             :     /* B */ -2,
      80             :     /* C */ -3,
      81             :     /* D */ -4,
      82             :     /* E */ -5,
      83             :     /* F */ -6,
      84             :     /* G */ -7,
      85             :     /* H */ -8,
      86             :     /* I */ -9,
      87             :     /* J */ 0, // not used
      88             :     /* K */ -10,
      89             :     /* L */ -11,
      90             :     /* M */ -12,
      91             :     /* N */ 1,
      92             :     /* O */ 2,
      93             :     /* P */ 3,
      94             :     /* Q */ 4,
      95             :     /* R */ 5,
      96             :     /* S */ 6,
      97             :     /* T */ 7,
      98             :     /* U */ 8,
      99             :     /* V */ 9,
     100             :     /* W */ 10,
     101             :     /* X */ 11,
     102             :     /* Y */ 12,
     103             :     /* Z */ 0, // Zulu time is zero
     104             : };
     105             : 
     106             : 
     107             : }
     108             : // noname namespace
     109             : 
     110             : /** \brief Convert a time/date value to a string.
     111             :  *
     112             :  * This function transform a date such as the content::modified field
     113             :  * to a format that is useful to the XSL parser. It supports various
     114             :  * formats:
     115             :  *
     116             :  * \li DATE_FORMAT_SHORT -- YYYY-MM-DD
     117             :  * \li DATE_FORMAT_LONG  -- YYYY-MM-DDTHH:MM:SSZ
     118             :  * \li DATE_FORMAT_TIME  -- HH:MM:SS
     119             :  * \li DATE_FORMAT_EMAIL -- dd MMM yyyy hh:mm:ss +0000
     120             :  * \li DATE_FORMAT_HTTP  -- ddd, dd MMM yyyy hh:mm:ss +0000
     121             :  *
     122             :  * The long format includes the time.
     123             :  *
     124             :  * The HTTP format uses the day and month names in English only since
     125             :  * the HTTP protocol only expects English.
     126             :  *
     127             :  * The date is always output as UTC (opposed to local time.)
     128             :  *
     129             :  * \note
     130             :  * In order to display a date to the end user (in the HTML for the users)
     131             :  * you may want to setup the timezone information first and then use
     132             :  * the various functions supplied by the locale plugin to generate the
     133             :  * date. The locale plugin also supports formatting numbers, time,
     134             :  * messages, etc. going both ways (from the formatted data to internal
     135             :  * data and vice versa.) There is also JavaScript support for editor
     136             :  * widgets.
     137             :  *
     138             :  * \warning
     139             :  * The input value is now seconds instead of microseconds.
     140             :  *
     141             :  * \param[in] seconds  A time & date value in seconds.
     142             :  * \param[in] date_format  Which format should be used.
     143             :  *
     144             :  * \return The formatted date and time.
     145             :  */
     146           0 : std::string date_to_string(time_t seconds, date_format_t date_format)
     147             : {
     148           0 :     struct tm time_info;
     149           0 :     gmtime_r(&seconds, &time_info);
     150             : 
     151           0 :     char buf[256];
     152           0 :     buf[0] = '\0';
     153             : 
     154           0 :     switch(date_format)
     155             :     {
     156           0 :     case date_format_t::DATE_FORMAT_SHORT:
     157           0 :         strftime(buf, sizeof(buf), "%Y-%m-%d", &time_info);
     158           0 :         break;
     159             : 
     160           0 :     case date_format_t::DATE_FORMAT_SHORT_US:
     161           0 :         strftime(buf, sizeof(buf), "%m-%d-%Y", &time_info);
     162           0 :         break;
     163             : 
     164           0 :     case date_format_t::DATE_FORMAT_LONG:
     165             :         // TBD do we want the Z when generating time for HTML headers?
     166             :         // (it is useful for the sitemap.xml at this point)
     167           0 :         strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%SZ", &time_info);
     168           0 :         break;
     169             : 
     170           0 :     case date_format_t::DATE_FORMAT_TIME:
     171           0 :         strftime(buf, sizeof(buf), "%H:%M:%S", &time_info);
     172           0 :         break;
     173             : 
     174           0 :     case date_format_t::DATE_FORMAT_EMAIL:
     175             :         { // dd MMM yyyy hh:mm:ss +0000
     176             :             // do it manually so the date is ALWAYS in English
     177           0 :             std::stringstream ss;
     178             :             ss << std::setfill('0')
     179           0 :                << std::setw(2) << time_info.tm_mday
     180             :                << ' '
     181           0 :                << g_month_name[time_info.tm_mon][0]
     182           0 :                << g_month_name[time_info.tm_mon][1]
     183           0 :                << g_month_name[time_info.tm_mon][2]
     184             :                << ' '
     185           0 :                << std::setw(2) << time_info.tm_year
     186             :                << ' '
     187           0 :                << std::setw(2) << time_info.tm_hour
     188             :                << ':'
     189           0 :                << std::setw(2) << time_info.tm_min
     190             :                << ':'
     191           0 :                << std::setw(2) << time_info.tm_sec
     192           0 :                << " +0000";
     193           0 :             return ss.str();
     194             : 
     195             :             //return QString("%1 %2 %3 %4:%5:%6 +0000")
     196             :             //    .arg(time_info.tm_mday, 2, 10, QChar('0'))
     197             :             //    .arg(QString::fromLatin1(g_month_name[time_info.tm_mon], 3))
     198             :             //    .arg(time_info.tm_year + 1900, 4, 10, QChar('0'))
     199             :             //    .arg(time_info.tm_hour, 2, 10, QChar('0'))
     200             :             //    .arg(time_info.tm_min, 2, 10, QChar('0'))
     201             :             //    .arg(time_info.tm_sec, 2, 10, QChar('0'));
     202             :         }
     203             :         break;
     204             : 
     205           0 :     case date_format_t::DATE_FORMAT_HTTP:
     206             :         { // ddd, dd MMM yyyy hh:mm:ss GMT
     207             :             // do it manually so the date is ALWAYS in English
     208           0 :             std::stringstream ss;
     209             :             ss << std::setfill('0')
     210           0 :                << g_week_day_name[time_info.tm_wday][0]
     211           0 :                << g_week_day_name[time_info.tm_wday][1]
     212           0 :                << g_week_day_name[time_info.tm_wday][2]
     213             :                << ", "
     214           0 :                << std::setw(2) << time_info.tm_mday
     215             :                << ' '
     216           0 :                << g_month_name[time_info.tm_mon][0]
     217           0 :                << g_month_name[time_info.tm_mon][1]
     218           0 :                << g_month_name[time_info.tm_mon][2]
     219             :                << ' '
     220           0 :                << std::setw(4) << time_info.tm_year + 1900
     221             :                << ' '
     222           0 :                << std::setw(2) << time_info.tm_hour
     223             :                << ':'
     224           0 :                << std::setw(2) << time_info.tm_min
     225             :                << ':'
     226           0 :                << std::setw(2) << time_info.tm_sec
     227           0 :                << " +0000";
     228           0 :             return ss.str();
     229             : 
     230             :             //return QString("%1, %2 %3 %4 %5:%6:%7 +0000")
     231             :             //    .arg(QString::fromLatin1(g_week_day_name[time_info.tm_wday], 3))
     232             :             //    .arg(time_info.tm_mday, 2, 10, QChar('0'))
     233             :             //    .arg(QString::fromLatin1(g_month_name[time_info.tm_mon], 3))
     234             :             //    .arg(time_info.tm_year + 1900, 4, 10, QChar('0'))
     235             :             //    .arg(time_info.tm_hour, 2, 10, QChar('0'))
     236             :             //    .arg(time_info.tm_min, 2, 10, QChar('0'))
     237             :             //    .arg(time_info.tm_sec, 2, 10, QChar('0'));
     238             :         }
     239             :         break;
     240             : 
     241             :     }
     242             : 
     243           0 :     return buf;
     244             : }
     245             : 
     246             : 
     247             : /** \brief Convert a date from a string to a time_t.
     248             :  *
     249             :  * This function transforms a date received by the client to a Unix
     250             :  * time_t value. We programmed our own because several fields are
     251             :  * optional and the strptime() function does not support such. Also
     252             :  * the strptime() uses the locale() for the day and month check
     253             :  * which is not expected for HTTP. The QDateTime object has similar
     254             :  * flaws.
     255             :  *
     256             :  * The function supports the RFC822, RFC850, and ANSI formats. On top
     257             :  * of these formats, the function understands the month name in full,
     258             :  * and the week day name and timezone parameters are viewed as optional.
     259             :  *
     260             :  * See the document we used to make sure we'd support pretty much all the
     261             :  * dates that a client might send to us:
     262             :  * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
     263             :  *
     264             :  * Formats that we support:
     265             :  *
     266             :  * \code
     267             :  *      YYYY-MM-DD
     268             :  *      DD-MMM-YYYY HH:MM:SS TZ
     269             :  *      DD-MMM-YYYY HH:MM:SS TZ
     270             :  *      WWW, DD-MMM-YYYY HH:MM:SS TZ
     271             :  *      MMM-DD-YYYY HH:MM:SS TZ
     272             :  *      WWW MMM-DD HH:MM:SS YYYY
     273             :  * \endcode
     274             :  *
     275             :  * The month and weekday may be a 3 letter abbreviation or the full English
     276             :  * name. The month must use letters. We support the ANSI format which must
     277             :  * start with the month or week name in letters. We distinguish the ANSI
     278             :  * format from the other RFC-2616 date if it starts with a week day and is
     279             :  * followed by a space, or it directly starts with the month name.
     280             :  *
     281             :  * The year may be 2 or 4 digits.
     282             :  *
     283             :  * The timezone is optional. It may use a space or a + or a - as a separator.
     284             :  * The timezone may be an abbreviation or a 4 digit number.
     285             :  *
     286             :  * \param[in] date  The date to convert to a time_t.
     287             :  *
     288             :  * \return The date and time as a Unix time_t number, -1 if the convertion fails.
     289             :  */
     290           0 : time_t string_to_date(std::string const & date)
     291             : {
     292             : #pragma GCC diagnostic push
     293             : #pragma GCC diagnostic ignored "-Weffc++"
     294           0 :     struct parser_t
     295             :     {
     296           0 :         parser_t(std::string const & date)
     297           0 :             : f_date(snapdev::to_lower(snapdev::trim_string(date, true, true, true)))
     298           0 :             , f_s(f_date.c_str())
     299             :         {
     300           0 :         }
     301             : 
     302             :         parser_t(parser_t const & rhs) = delete;
     303             :         parser_t & operator = (parser_t const & rhs) = delete;
     304             : 
     305           0 :         void skip_spaces()
     306             :         {
     307           0 :             while(isspace(*f_s))
     308             :             {
     309           0 :                 ++f_s;
     310             :             }
     311           0 :         }
     312             : 
     313           0 :         bool parse_week_day()
     314             :         {
     315             :             // day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"
     316             :             //             /  "Fri"  / "Sat" /  "Sun"
     317           0 :             if(f_s[0] == 'm' && f_s[1] == 'o' && f_s[2] == 'n')
     318             :             {
     319           0 :                 f_time_info.tm_wday = 1;
     320             :             }
     321           0 :             else if(f_s[0] == 't' && f_s[1] == 'u' && f_s[2] == 'e')
     322             :             {
     323           0 :                 f_time_info.tm_wday = 2;
     324             :             }
     325           0 :             else if(f_s[0] == 'w' && f_s[1] == 'e' && f_s[2] == 'd')
     326             :             {
     327           0 :                 f_time_info.tm_wday = 3;
     328             :             }
     329           0 :             else if(f_s[0] == 't' && f_s[1] == 'h' && f_s[2] == 'u')
     330             :             {
     331           0 :                 f_time_info.tm_wday = 4;
     332             :             }
     333           0 :             else if(f_s[0] == 'f' && f_s[1] == 'r' && f_s[2] == 'i')
     334             :             {
     335           0 :                 f_time_info.tm_wday = 5;
     336             :             }
     337           0 :             else if(f_s[0] == 's' && f_s[1] == 'a' && f_s[2] == 't')
     338             :             {
     339           0 :                 f_time_info.tm_wday = 6;
     340             :             }
     341           0 :             else if(f_s[0] == 's' && f_s[1] == 'u' && f_s[2] == 'n')
     342             :             {
     343           0 :                 f_time_info.tm_wday = 0;
     344             :             }
     345             :             else
     346             :             {
     347           0 :                 return false;
     348             :             }
     349             : 
     350             :             // check whether the other characters follow
     351           0 :             if(strncmp(f_s + 3, g_week_day_name[f_time_info.tm_wday] + 3, g_week_day_length[f_time_info.tm_wday] - 3) == 0)
     352             :             {
     353             :                 // full day (RFC850)
     354           0 :                 f_s += g_week_day_length[f_time_info.tm_wday];
     355             :             }
     356             :             else
     357             :             {
     358           0 :                 f_s += 3;
     359             :             }
     360             : 
     361           0 :             return true;
     362             :         }
     363             : 
     364           0 :         bool parse_month()
     365             :         {
     366             :             // month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"
     367             :             //             /  "May"  /  "Jun" /  "Jul"  /  "Aug"
     368             :             //             /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"
     369           0 :             if(f_s[0] == 'j' && f_s[1] == 'a' && f_s[2] == 'n')
     370             :             {
     371           0 :                 f_time_info.tm_mon = 0;
     372             :             }
     373           0 :             else if(f_s[0] == 'f' && f_s[1] == 'e' && f_s[2] == 'b')
     374             :             {
     375           0 :                 f_time_info.tm_mon = 1;
     376             :             }
     377           0 :             else if(f_s[0] == 'm' && f_s[1] == 'a' && f_s[2] == 'r')
     378             :             {
     379           0 :                 f_time_info.tm_mon = 2;
     380             :             }
     381           0 :             else if(f_s[0] == 'a' && f_s[1] == 'p' && f_s[2] == 'r')
     382             :             {
     383           0 :                 f_time_info.tm_mon = 3;
     384             :             }
     385           0 :             else if(f_s[0] == 'm' && f_s[1] == 'a' && f_s[2] == 'y')
     386             :             {
     387           0 :                 f_time_info.tm_mon = 4;
     388             :             }
     389           0 :             else if(f_s[0] == 'j' && f_s[1] == 'u' && f_s[2] == 'n')
     390             :             {
     391           0 :                 f_time_info.tm_mon = 5;
     392             :             }
     393           0 :             else if(f_s[0] == 'j' && f_s[1] == 'u' && f_s[2] == 'l')
     394             :             {
     395           0 :                 f_time_info.tm_mon = 6;
     396             :             }
     397           0 :             else if(f_s[0] == 'a' && f_s[1] == 'u' && f_s[2] == 'g')
     398             :             {
     399           0 :                 f_time_info.tm_mon = 7;
     400             :             }
     401           0 :             else if(f_s[0] == 's' && f_s[1] == 'e' && f_s[2] == 'p')
     402             :             {
     403           0 :                 f_time_info.tm_mon = 8;
     404             :             }
     405           0 :             else if(f_s[0] == 'o' && f_s[1] == 'c' && f_s[2] == 't')
     406             :             {
     407           0 :                 f_time_info.tm_mon = 9;
     408             :             }
     409           0 :             else if(f_s[0] == 'n' && f_s[1] == 'o' && f_s[2] == 'v')
     410             :             {
     411           0 :                 f_time_info.tm_mon = 10;
     412             :             }
     413           0 :             else if(f_s[0] == 'd' && f_s[1] == 'e' && f_s[2] == 'c')
     414             :             {
     415           0 :                 f_time_info.tm_mon = 11;
     416             :             }
     417             :             else
     418             :             {
     419           0 :                 return false;
     420             :             }
     421             : 
     422             :             // check whether the other characters follow
     423           0 :             if(strncmp(f_s + 3, g_month_name[f_time_info.tm_mon] + 3, g_month_length[f_time_info.tm_mon] - 3) == 0)
     424             :             {
     425             :                 // full month (not in the specs)
     426           0 :                 f_s += g_month_length[f_time_info.tm_mon];
     427             :             }
     428             :             else
     429             :             {
     430           0 :                 f_s += 3;
     431             :             }
     432             : 
     433           0 :             skip_spaces();
     434           0 :             return true;
     435             :         }
     436             : 
     437           0 :         bool integer(
     438             :               unsigned int min_len
     439             :             , unsigned int max_len
     440             :             , unsigned int min_value
     441             :             , unsigned int max_value
     442             :             , int & result)
     443             :         {
     444           0 :             unsigned int u_result = 0;
     445           0 :             unsigned int count(0);
     446           0 :             for(; *f_s >= '0' && *f_s <= '9'; ++f_s, ++count)
     447             :             {
     448           0 :                 u_result = u_result * 10 + *f_s - '0';
     449             :             }
     450           0 :             if(count < min_len || count > max_len
     451           0 :             || u_result < min_value || u_result > max_value)
     452             :             {
     453           0 :                 result = static_cast<int>(u_result);
     454           0 :                 return false;
     455             :             }
     456           0 :             result = static_cast<int>(u_result);
     457           0 :             return true;
     458             :         }
     459             : 
     460           0 :         bool parse_time()
     461             :         {
     462           0 :             if(!integer(2, 2, 0, 23, f_time_info.tm_hour))
     463             :             {
     464           0 :                 return false;
     465             :             }
     466           0 :             if(*f_s != ':')
     467             :             {
     468           0 :                 return false;
     469             :             }
     470           0 :             ++f_s;
     471           0 :             if(!integer(2, 2, 0, 59, f_time_info.tm_min))
     472             :             {
     473           0 :                 return false;
     474             :             }
     475           0 :             if(*f_s != ':')
     476             :             {
     477           0 :                 return false;
     478             :             }
     479           0 :             ++f_s;
     480           0 :             if(!integer(2, 2, 0, 60, f_time_info.tm_sec))
     481             :             {
     482           0 :                 return false;
     483             :             }
     484           0 :             skip_spaces();
     485           0 :             return true;
     486             :         }
     487             : 
     488           0 :         bool parse_timezone()
     489             :         {
     490             :             // any timezone?
     491           0 :             if(*f_s == '\0')
     492             :             {
     493           0 :                 return true;
     494             :             }
     495             : 
     496             :             // XXX not too sure that the zone is properly handled at this point
     497             :             // (i.e. should I do += or -=, it may be wrong in many places...)
     498             :             //
     499             :             // The newest HTTP format is to only support "+/-####"
     500             :             //
     501             :             // zone        =  "UT"  / "GMT"
     502             :             //             /  "EST" / "EDT"
     503             :             //             /  "CST" / "CDT"
     504             :             //             /  "MST" / "MDT"
     505             :             //             /  "PST" / "PDT"
     506             :             //             /  1ALPHA
     507             :             //             / ( ("+" / "-") 4DIGIT )
     508           0 :             if((f_s[0] == 'u' && f_s[1] == 't' && f_s[2] == '\0')                 // UT
     509           0 :             || (f_s[0] == 'u' && f_s[1] == 't' && f_s[2] == 'c' && f_s[3] == '\0')  // UTC (not in the spec...)
     510           0 :             || (f_s[0] == 'g' && f_s[1] == 'm' && f_s[2] == 't' && f_s[3] == '\0')) // GMT
     511             :             {
     512             :                 // no adjustment for UTC (GMT)
     513             :             }
     514           0 :             else if(f_s[0] == 'e' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // EST
     515             :             {
     516           0 :                 f_time_info.tm_hour -= 5;
     517             :             }
     518           0 :             else if(f_s[0] == 'e' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // EDT
     519             :             {
     520           0 :                 f_time_info.tm_hour -= 4;
     521             :             }
     522           0 :             else if(f_s[0] == 'c' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // CST
     523             :             {
     524           0 :                 f_time_info.tm_hour -= 6;
     525             :             }
     526           0 :             else if(f_s[0] == 'c' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // CDT
     527             :             {
     528           0 :                 f_time_info.tm_hour -= 5;
     529             :             }
     530           0 :             else if(f_s[0] == 'm' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // MST
     531             :             {
     532           0 :                 f_time_info.tm_hour -= 7;
     533             :             }
     534           0 :             else if(f_s[0] == 'm' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // MDT
     535             :             {
     536           0 :                 f_time_info.tm_hour -= 6;
     537             :             }
     538           0 :             else if(f_s[0] == 'p' && f_s[1] == 's' && f_s[2] == 't' && f_s[3] == '\0') // PST
     539             :             {
     540           0 :                 f_time_info.tm_hour -= 8;
     541             :             }
     542           0 :             else if(f_s[0] == 'p' && f_s[1] == 'd' && f_s[2] == 't' && f_s[3] == '\0') // PDT
     543             :             {
     544           0 :                 f_time_info.tm_hour -= 7;
     545             :             }
     546           0 :             else if(f_s[0] >= 'a' && f_s[0] <= 'z' && f_s[0] != 'j' && f_s[1] == '\0')
     547             :             {
     548           0 :                 f_time_info.tm_hour += g_timezone_adjust[f_s[0] - 'a'];
     549             :             }
     550           0 :             else if((f_s[0] == '+' || f_s[0] == '-')
     551           0 :                   && f_s[1] >= '0' && f_s[1] <= '9'
     552           0 :                   && f_s[2] >= '0' && f_s[2] <= '9'
     553           0 :                   && f_s[3] >= '0' && f_s[3] <= '9'
     554           0 :                   && f_s[4] >= '0' && f_s[4] <= '9'
     555           0 :                   && f_s[5] == '\0')
     556             :             {
     557           0 :                 f_time_info.tm_hour += ((f_s[1] - '0') * 10 + f_s[2] - '0') * (f_s[0] == '+' ? 1 : -1);
     558           0 :                 f_time_info.tm_min  += ((f_s[3] - '0') * 10 + f_s[4] - '0') * (f_s[0] == '+' ? 1 : -1);
     559             :             }
     560             :             else
     561             :             {
     562             :                 // invalid time zone
     563           0 :                 return false;
     564             :             }
     565             : 
     566             :             // WARNING: the time zone doesn't get skipped!
     567           0 :             return true;
     568             :         }
     569             : 
     570           0 :         bool parse_ansi()
     571             :         {
     572           0 :             skip_spaces();
     573           0 :             if(!parse_month())
     574             :             {
     575           0 :                 return false;
     576             :             }
     577           0 :             if(!integer(1, 2, 1, 31, f_time_info.tm_mday))
     578             :             {
     579           0 :                 return false;
     580             :             }
     581           0 :             skip_spaces();
     582           0 :             if(!parse_time())
     583             :             {
     584           0 :                 return false;
     585             :             }
     586           0 :             if(!integer(2, 4, 0, 3000, f_time_info.tm_year))
     587             :             {
     588           0 :                 return false;
     589             :             }
     590           0 :             skip_spaces();
     591           0 :             return parse_timezone();
     592             :         }
     593             : 
     594           0 :         bool parse_us()
     595             :         {
     596           0 :             skip_spaces();
     597           0 :             if(!parse_month())
     598             :             {
     599           0 :                 return false;
     600             :             }
     601           0 :             skip_spaces();
     602           0 :             if(!integer(1, 2, 1, 31, f_time_info.tm_mday))
     603             :             {
     604           0 :                 return false;
     605             :             }
     606           0 :             skip_spaces();
     607           0 :             if(!integer(2, 4, 0, 3000, f_time_info.tm_year))
     608             :             {
     609           0 :                 return false;
     610             :             }
     611           0 :             skip_spaces();
     612           0 :             return parse_time();
     613             :         }
     614             : 
     615           0 :         bool parse()
     616             :         {
     617             :             // support for YYYY-MM-DD
     618           0 :             if(f_date.size() == 10
     619           0 :             && f_s[4] == '-'
     620           0 :             && f_s[7] == '-')
     621             :             {
     622           0 :                 if(!integer(4, 4, 0, 3000, f_time_info.tm_year))
     623             :                 {
     624           0 :                     return false;
     625             :                 }
     626           0 :                 if(*f_s != '-')
     627             :                 {
     628           0 :                     return false;
     629             :                 }
     630           0 :                 ++f_s;
     631           0 :                 if(!integer(2, 2, 1, 12, f_time_info.tm_mon))
     632             :                 {
     633           0 :                     return false;
     634             :                 }
     635           0 :                 --f_time_info.tm_mon; // expect 0 to 11 in final structure
     636           0 :                 if(*f_s != '-')
     637             :                 {
     638           0 :                     return false;
     639             :                 }
     640           0 :                 ++f_s;
     641           0 :                 if(!integer(2, 2, 1, 31, f_time_info.tm_mday))
     642             :                 {
     643           0 :                     return false;
     644             :                 }
     645           0 :                 return true;
     646             :             }
     647             : 
     648             :             // week day (optional in RFC822)
     649           0 :             if(*f_s >= 'a' && *f_s <= 'z')
     650             :             {
     651           0 :                 if(!parse_week_day())
     652             :                 {
     653             :                     // maybe that was the month, not the day
     654             :                     // if the time is last, we have a preprocessor date/time
     655             :                     // the second test is needed because the string gets
     656             :                     // simplified and thus numbers 1 to 9 generate a string
     657             :                     // one shorter
     658           0 :                     if((strlen(f_s) == 11 + 1 + 8
     659           0 :                      && f_s[11 + 1 + 8 - 6] == ':'
     660           0 :                      && f_s[11 + 1 + 8 - 3] == ':')
     661           0 :                     ||
     662           0 :                        (strlen(f_s) == 10 + 1 + 8
     663           0 :                      && f_s[10 + 1 + 8 - 6] == ':'
     664           0 :                      && f_s[10 + 1 + 8 - 3] == ':'))
     665             :                     {
     666           0 :                         return parse_us();
     667             :                     }
     668           0 :                     return parse_ansi();
     669             :                 }
     670             : 
     671           0 :                 if(f_s[0] == ' ')
     672             :                 {
     673             :                     // the ANSI format is completely random!
     674           0 :                     return parse_ansi();
     675             :                 }
     676             : 
     677           0 :                 if(f_s[0] != ',')
     678             :                 {
     679           0 :                     return false;
     680             :                 }
     681           0 :                 ++f_s; // skip the comma
     682           0 :                 skip_spaces();
     683             :             }
     684             : 
     685           0 :             if(!integer(1, 2, 1, 31, f_time_info.tm_mday))
     686             :             {
     687           0 :                 return false;
     688             :             }
     689             : 
     690           0 :             if(*f_s == '-')
     691             :             {
     692           0 :                 ++f_s;
     693             :             }
     694           0 :             skip_spaces();
     695             : 
     696           0 :             if(!parse_month())
     697             :             {
     698           0 :                 return false;
     699             :             }
     700           0 :             if(*f_s == '-')
     701             :             {
     702           0 :                 ++f_s;
     703           0 :                 skip_spaces();
     704             :             }
     705           0 :             if(!integer(2, 4, 0, 3000, f_time_info.tm_year))
     706             :             {
     707           0 :                 return false;
     708             :             }
     709           0 :             skip_spaces();
     710           0 :             if(!parse_time())
     711             :             {
     712           0 :                 return false;
     713             :             }
     714             : 
     715           0 :             return parse_timezone();
     716             :         }
     717             : 
     718             :         struct tm       f_time_info = tm();
     719             :         std::string     f_date = std::string();
     720             :         char const *    f_s = nullptr;
     721           0 :     } parser(date);
     722             : #pragma GCC diagnostic pop
     723             : 
     724           0 :     if(!parser.parse())
     725             :     {
     726           0 :         return -1;
     727             :     }
     728             : 
     729             :     // 2 digit year?
     730             :     // How to handle this one? At this time I do not expect our software
     731             :     // to work beyond 2070 which is probably short sighted (ha! ha!)
     732             :     // However, that way we avoid calling time() and transform that in
     733             :     // a tm structure and check that date
     734           0 :     if(parser.f_time_info.tm_year < 100)
     735             :     {
     736           0 :         parser.f_time_info.tm_year += 1900;
     737           0 :         if(parser.f_time_info.tm_year < 1970)
     738             :         {
     739           0 :             parser.f_time_info.tm_year += 100;
     740             :         }
     741             :     }
     742             : 
     743             :     // make sure the day is valid for that month/year
     744           0 :     if(parser.f_time_info.tm_mday > last_day_of_month(parser.f_time_info.tm_mon + 1, parser.f_time_info.tm_year))
     745             :     {
     746           0 :         return -1;
     747             :     }
     748             : 
     749             :     // now we have a time_info which is fully adjusted except for DST...
     750             :     // let's make time
     751           0 :     parser.f_time_info.tm_year -= 1900;
     752           0 :     return mkgmtime(&parser.f_time_info);
     753             : }
     754             : 
     755             : 
     756             : /** \brief From a month and year, get the last day of the month.
     757             :  *
     758             :  * This function gives you the number of the last day of the month.
     759             :  * In all cases, except February, it returns 30 or 31.
     760             :  *
     761             :  * For the month of February, we first compute the leap year flag.
     762             :  * If the year is a leap year, then it returns 29, otherwise it
     763             :  * returns 28.
     764             :  *
     765             :  * The leap year formula is:
     766             :  *
     767             :  * \code
     768             :  *      leap = !(year % 4) && (year % 100 || !(year % 400));
     769             :  * \endcode
     770             :  *
     771             :  * \warning
     772             :  * This function throws if called with September 1752 because the
     773             :  * month has missing days within the month (days 3 to 13).
     774             :  *
     775             :  * \exception edhttp_client_server_logic_error
     776             :  * This exception is raised if the month is not between 1 and 12 inclusive
     777             :  * or if the month/year is September 1752 (because that month never existed).
     778             :  *
     779             :  * \param[in] month  A number from 1 to 12 representing a month.
     780             :  * \param[in] year  A year, including the century.
     781             :  *
     782             :  * \return Last day of month, 30, 31, or in February, 28 or 29.
     783             :  */
     784           0 : int last_day_of_month(int month, int year)
     785             : {
     786           0 :     if(month < 1 || month > 12)
     787             :     {
     788             :         throw edhttp_client_server_logic_error(
     789             :               "last_day_of_month called with "
     790           0 :             + std::to_string(month)
     791           0 :             + " as the month number");
     792             :     }
     793             : 
     794           0 :     if(month == 2)
     795             :     {
     796             :         // special case for February
     797             :         //
     798             :         // The time when people switch from Julian to Gregorian is country
     799             :         // dependent, Great Britain changed on September 2, 1752, but some
     800             :         // countries changed as late as 1952...
     801             :         //
     802             :         // For now, we use the GB date. Once we have a valid way to handle
     803             :         // this with the locale, we can look into updating the code. That
     804             :         // being said, it should not matter too much because most dates on
     805             :         // the Internet are past 2000.
     806             :         //
     807           0 :         if(year <= 1752)
     808             :         {
     809           0 :             return year % 4 == 0 ? 29 : 28;
     810             :         }
     811           0 :         return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) ? 29 : 28;
     812             :     }
     813             : 
     814           0 :     if(month == 9 && year == 1752)
     815             :     {
     816             :         // we cannot handle this nice one here, days 3 to 13 are missing on
     817             :         // this month... (to adjust the calendar all at once!)
     818             :         throw edhttp_client_server_logic_error(
     819             :               "last_day_of_month called with "
     820           0 :             + std::to_string(year)
     821           0 :             + " as the year number");
     822             :     }
     823             : 
     824           0 :     return g_month_days[month - 1];
     825             : }
     826             : 
     827             : 
     828             : 
     829             : } // namespace edhttp
     830             : // vim: ts=4 sw=4 et

Generated by: LCOV version 1.13