415 lines
11 KiB
C
415 lines
11 KiB
C
/*
|
|
* This file is part of the Sofia-SIP package
|
|
*
|
|
* Copyright (C) 2005 Nokia Corporation.
|
|
*
|
|
* Contact: Pekka Pessi <pekka.pessi@nokia.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation; either version 2.1 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
/**@ingroup msg_parser
|
|
* @CFILE msg_date.c
|
|
* @brief Parser for HTTP-Date and HTTP-Delta.
|
|
*
|
|
* Parsing functions for @RFC1123 (GMT) date.
|
|
*
|
|
* @author Pekka Pessi <Pekka.Pessi@nokia.com>
|
|
*
|
|
* @date Created: Wed Apr 11 18:57:06 2001 ppessi
|
|
*
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include <sofia-sip/su_string.h>
|
|
#include <sofia-sip/msg_date.h>
|
|
#include <sofia-sip/bnf.h>
|
|
|
|
#include <sofia-sip/su_time.h>
|
|
|
|
/** Return current time as seconds since Mon, 01 Jan 1900 00:00:00 GMT. */
|
|
msg_time_t msg_now(void)
|
|
{
|
|
return su_now().tv_sec;
|
|
}
|
|
|
|
#define is_digit(c) ((c) >= '0' && (c) <= '9')
|
|
|
|
/**Epoch year. @internal
|
|
*
|
|
* First day of the epoch year should be Monday.
|
|
*/
|
|
#define EPOCH 1900
|
|
/** Is this year a leap year? @internal */
|
|
#define LEAP_YEAR(y) ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0))
|
|
/** Day number of New Year Day of given year. @internal */
|
|
#define YEAR_DAYS(y) \
|
|
(((y)-1) * 365 + ((y)-1) / 4 - ((y)-1) / 100 + ((y)-1) / 400)
|
|
|
|
|
|
/* ====================================================================== */
|
|
|
|
static unsigned char const days_per_months[12] =
|
|
{
|
|
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
|
|
};
|
|
|
|
/** Offset of first day of the month with formula day = 30 * month + offset. */
|
|
static signed char const first_day_offset[12] =
|
|
{
|
|
0, 1, -1, 0, 0, 1, 1, 2, 3, 3, 4, 4
|
|
};
|
|
|
|
static char const wkdays[7 * 4] =
|
|
"Mon\0" "Tue\0" "Wed\0" "Thu\0" "Fri\0" "Sat\0" "Sun";
|
|
|
|
static char const months[12 * 4] =
|
|
"Jan\0" "Feb\0" "Mar\0" "Apr\0" "May\0" "Jun\0"
|
|
"Jul\0" "Aug\0" "Sep\0" "Oct\0" "Nov\0" "Dec";
|
|
|
|
/** Parse a month name.
|
|
*
|
|
* @return The function month_d() returns 0..11 if given first three letters
|
|
* of month name, or -1 if no month name matches.
|
|
*/
|
|
su_inline
|
|
int month_d(char const *s)
|
|
{
|
|
unsigned const uc = ('a' - 'A') << 16 | ('a' - 'A') << 8 | ('a' - 'A');
|
|
unsigned m;
|
|
|
|
if (!s[0] || !s[1] || !s[2])
|
|
return -1;
|
|
|
|
#define MONTH_V(s) (uc | (((s[0]) << 16) + ((s[1]) << 8) + ((s[2]))))
|
|
m = MONTH_V(s);
|
|
|
|
#define MONTH_D(n) \
|
|
if (m == (uc | (months[4*(n)]<<16)|(months[4*(n)+1]<<8)|(months[4*(n)+2])))\
|
|
return (n)
|
|
|
|
MONTH_D(0);
|
|
MONTH_D(1);
|
|
MONTH_D(2);
|
|
MONTH_D(3);
|
|
MONTH_D(4);
|
|
MONTH_D(5);
|
|
MONTH_D(6);
|
|
MONTH_D(7);
|
|
MONTH_D(8);
|
|
MONTH_D(9);
|
|
MONTH_D(10);
|
|
MONTH_D(11);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Parse SP 2DIGIT ":" 2DIGIT ":" 2DIGIT SP */
|
|
su_inline
|
|
int time_d(char const **ss,
|
|
unsigned long *hour, unsigned long *min, unsigned long *sec)
|
|
{
|
|
char const *s = *ss;
|
|
if (!IS_LWS(*s))
|
|
return -1;
|
|
skip_lws(&s);
|
|
if (!is_digit(*s)) return -1;
|
|
*hour = *s++ - '0'; if (is_digit(*s)) *hour = 10 * (*hour) + *s++ - '0';
|
|
if (*s++ != ':' || !is_digit(s[0]) || !is_digit(s[1]))
|
|
return -1;
|
|
*min = 10 * s[0] + s[1] - 11 * '0'; s += 2;
|
|
if (*s++ != ':' || !is_digit(s[0]) || !is_digit(s[1]))
|
|
return -1;
|
|
*sec = 10 * s[0] + s[1] - 11 * '0'; s += 2;
|
|
if (*s) {
|
|
if (!IS_LWS(*s)) return -1; skip_lws(&s);
|
|
}
|
|
*ss = s;
|
|
return 0;
|
|
}
|
|
|
|
/**Decode RFC1123-date, RFC822-date or asctime-date.
|
|
*
|
|
* The function msg_date_d() decodes <HTTP-date>, which may have
|
|
* different formats.
|
|
*
|
|
* @code
|
|
* HTTP-date = rfc1123-date / rfc850-date / asctime-date
|
|
*
|
|
* rfc1123-date = wkday "," SP date1 SP time SP "GMT"
|
|
* date1 = 2DIGIT SP month SP 4DIGIT
|
|
* ; day month year (e.g., 02 Jun 1982)
|
|
*
|
|
* rfc850-date = weekday "," SP date2 SP time SP "GMT"
|
|
* date2 = 2DIGIT "-" month "-" 2DIGIT
|
|
* ; day-month-year (e.g., 02-Jun-82)
|
|
*
|
|
* asctime-date = wkday SP date3 SP time SP 4DIGIT
|
|
* date3 = month SP ( 2DIGIT / ( SP 1DIGIT ))
|
|
* ; month day (e.g., Jun 2)
|
|
*
|
|
* time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
|
|
* ; 00:00:00 - 23:59:59
|
|
*
|
|
* wkday = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
|
|
* weekday = "Monday" / "Tuesday" / "Wednesday"
|
|
* / "Thursday" / "Friday" / "Saturday" / "Sunday"
|
|
* month = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun"
|
|
* / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
|
|
* @endcode
|
|
*/
|
|
issize_t msg_date_d(char const **ss, msg_time_t *date)
|
|
{
|
|
char const *s = *ss;
|
|
char const *wkday;
|
|
char const *tz;
|
|
unsigned long day, year, hour, min, sec;
|
|
int mon;
|
|
|
|
if (!IS_TOKEN(*s) || !date)
|
|
return -1;
|
|
|
|
wkday = s; skip_token(&s); if (*s == ',') s++;
|
|
while (IS_LWS(*s)) s++;
|
|
|
|
if (is_digit(*s)) {
|
|
day = *s++ - '0'; if (is_digit(*s)) day = 10 * day + *s++ - '0';
|
|
|
|
if (*s == ' ') {
|
|
/* rfc1123-date =
|
|
wkday "," SP 2DIGIT SP month SP 4DIGIT SP time SP "GMT"
|
|
*/
|
|
mon = month_d(++s); skip_token(&s);
|
|
if (mon < 0 || !IS_LWS(*s)) return -1;
|
|
s++;
|
|
if (!is_digit(s[0]) || !is_digit(s[1]) ||
|
|
!is_digit(s[2]) || !is_digit(s[3]))
|
|
return -1;
|
|
year = 1000 * s[0] + 100 * s[1] + 10 * s[2] + s[3] - 1111 * '0'; s += 4;
|
|
}
|
|
else if (*s == '-') {
|
|
/* rfc822-date =
|
|
weekday "," SP 2DIGIT "-" month "-" 2DIGIT SP time SP "GMT"
|
|
*/
|
|
|
|
mon = month_d(++s);
|
|
if (mon < 0 || s[3] != '-' ||
|
|
!is_digit(s[4]) || !is_digit(s[5]))
|
|
return -1;
|
|
year = 10 * s[4] + s[5] - 11 * '0';
|
|
if (is_digit(s[6]) && is_digit(s[7])) {
|
|
/* rfc2822-date =
|
|
weekday "," SP 2DIGIT "-" month "-" 4DIGIT SP time SP "GMT"
|
|
*/
|
|
year = year * 100 + 10 * s[6] + s[7] - 11 * '0';
|
|
s += 8;
|
|
}
|
|
else {
|
|
if (year >= 70)
|
|
year = 1900 + year;
|
|
else
|
|
year = 2000 + year;
|
|
s += 6;
|
|
}
|
|
}
|
|
else
|
|
return -1;
|
|
|
|
if (time_d(&s, &hour, &min, &sec) < 0) return -1;
|
|
if (*s) {
|
|
tz = s; skip_token(&s); skip_lws(&s);
|
|
if (!su_casenmatch(tz, "GMT", 3) && !su_casenmatch(tz, "UCT", 3))
|
|
return -1;
|
|
}
|
|
}
|
|
else {
|
|
/* actime-date =
|
|
wkday SP month SP ( 2DIGIT | ( SP 1DIGIT )) SP time SP 4DIGIT */
|
|
mon = month_d(s); skip_token(&s);
|
|
if (mon < 0 || !IS_LWS(*s)) return -1; s++;
|
|
while (IS_LWS(*s)) s++;
|
|
if (!is_digit(*s)) return -1;
|
|
day = *s++ - '0'; if (is_digit(*s)) day = 10 * day + *s++ - '0';
|
|
if (time_d(&s, &hour, &min, &sec) < 0) return -1;
|
|
/* Accept also unix date (if it is GMT) */
|
|
if ((s[0] == 'G' && s[1] == 'M' && s[2] == 'T' && s[3] == ' ') ||
|
|
(s[0] == 'U' && s[1] == 'T' && s[2] == 'C' && s[3] == ' '))
|
|
s += 4;
|
|
else if (s[0] == 'U' && s[1] == 'T' && s[2] == ' ')
|
|
s += 3;
|
|
if (!is_digit(s[0]) || !is_digit(s[1]) ||
|
|
!is_digit(s[2]) || !is_digit(s[3]))
|
|
return -1;
|
|
year = 1000 * s[0] + 100 * s[1] + 10 * s[2] + s[3] - 1111 * '0'; s += 4;
|
|
}
|
|
|
|
if (hour > 24 || min >= 60 || sec >= 60 ||
|
|
(hour == 24 && min > 0 && sec > 0))
|
|
return -1;
|
|
|
|
if (day == 0 || day > days_per_months[mon]) {
|
|
if (day != 29 || mon != 1 || !LEAP_YEAR(year))
|
|
return -1;
|
|
}
|
|
|
|
if (year < EPOCH) {
|
|
*date = 0;
|
|
}
|
|
else if (year > EPOCH + 135) {
|
|
*date = 0xfdeefb80; /* XXX: EPOCH + 135 years */
|
|
}
|
|
else {
|
|
int leap_year = LEAP_YEAR(year);
|
|
msg_time_t ydays = YEAR_DAYS(year) - YEAR_DAYS(EPOCH);
|
|
|
|
#if 0
|
|
printf("Year %d%s starts %ld = %d - %d days since epoch (%d)\n",
|
|
year, leap_year ? " (leap year)" : "",
|
|
ydays, YEAR_DAYS(year), YEAR_DAYS(EPOCH), EPOCH);
|
|
#endif
|
|
|
|
*date = sec + 60 *
|
|
(min + 60 *
|
|
(hour + 24 *
|
|
(day - 1 + mon * 30 + first_day_offset[mon] +
|
|
(leap_year && mon > 2) + ydays)));
|
|
}
|
|
*ss = s;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**Encode RFC1123-date.
|
|
*
|
|
* The function msg_date_e() prints @e http-date in the <rfc1123-date>
|
|
* format. The format is as follows:
|
|
*
|
|
* @code
|
|
* rfc1123-date = wkday "," SP date SP time SP "GMT"
|
|
* wkday = "Mon" | "Tue" | "Wed"
|
|
* | "Thu" | "Fri" | "Sat" | "Sun"
|
|
* date = 2DIGIT SP month SP 4DIGIT
|
|
* ; day month year (e.g., 02 Jun 1982)
|
|
* month = "Jan" | "Feb" | "Mar" | "Apr"
|
|
* | "May" | "Jun" | "Jul" | "Aug"
|
|
* | "Sep" | "Oct" | "Nov" | "Dec"
|
|
* time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
|
|
* ; 00:00:00 - 23:59:59
|
|
* @endcode
|
|
*
|
|
* @param b buffer to print the date
|
|
* @param bsiz size of the buffer
|
|
* @param http_date seconds since 01 Jan 1900.
|
|
*
|
|
* @return The function msg_date_e() returns the size of the formatted date.
|
|
*/
|
|
issize_t msg_date_e(char b[], isize_t bsiz, msg_time_t http_date)
|
|
{
|
|
msg_time_t sec, min, hour, wkday, day, month, year;
|
|
msg_time_t days_per_month, leap_year;
|
|
|
|
sec = http_date % 60; http_date /= 60;
|
|
min = http_date % 60; http_date /= 60;
|
|
hour = http_date % 24; http_date /= 24;
|
|
|
|
wkday = http_date % 7;
|
|
day = http_date + YEAR_DAYS(EPOCH);
|
|
year = EPOCH + http_date / 365;
|
|
|
|
for (;;) {
|
|
if (day >= YEAR_DAYS(year + 1))
|
|
year++;
|
|
else if (day < YEAR_DAYS(year))
|
|
year--;
|
|
else
|
|
break;
|
|
}
|
|
|
|
day -= YEAR_DAYS(year);
|
|
leap_year = LEAP_YEAR(year);
|
|
|
|
month = 0, days_per_month = 31;
|
|
while (day >= days_per_month) {
|
|
day -= days_per_month;
|
|
month++;
|
|
days_per_month = days_per_months[month] + (leap_year && month == 2);
|
|
}
|
|
|
|
return snprintf(b, bsiz, "%s, %02ld %s %04ld %02ld:%02ld:%02ld GMT",
|
|
wkdays + wkday * 4, day + 1, months + month * 4,
|
|
year, hour, min, sec);
|
|
}
|
|
|
|
|
|
/**Decode a delta-seconds.
|
|
*
|
|
* The function msg_delta_d() decodes a <delta-seconds> field.
|
|
*
|
|
* The <delta-seconds> is defined as follows:
|
|
* @code
|
|
* delta-seconds = 1*DIGIT
|
|
* @endcode
|
|
*
|
|
* Note, however, that <delta-seconds> may not be larger than #MSG_TIME_MAX.
|
|
*/
|
|
issize_t msg_delta_d(char const **ss, msg_time_t *delta)
|
|
{
|
|
char const *s = *ss;
|
|
|
|
if (!is_digit(*s))
|
|
return -1;
|
|
|
|
*delta = strtoul(*ss, (char **)ss, 10);
|
|
skip_lws(ss);
|
|
|
|
return *ss - s;
|
|
}
|
|
|
|
/**Encode @ref msg_delta_d() "<delta-seconds>" field.
|
|
*/
|
|
issize_t msg_delta_e(char b[], isize_t bsiz, msg_time_t delta)
|
|
{
|
|
return snprintf(b, bsiz, "%lu", (unsigned long)delta);
|
|
}
|
|
|
|
/** Decode a HTTP date or delta
|
|
*
|
|
* Decode a @ref msg_date_d() "<http-date>" or
|
|
* @ref msg_delta_d() "<delta-seconds>" field.
|
|
*/
|
|
issize_t msg_date_delta_d(char const **ss,
|
|
msg_time_t *date,
|
|
msg_time_t *delta)
|
|
{
|
|
if (delta && is_digit(**ss)) {
|
|
return msg_delta_d(ss, delta);
|
|
}
|
|
else if (date && IS_TOKEN(**ss)) {
|
|
return msg_date_d(ss, date);
|
|
}
|
|
return -1;
|
|
}
|
|
|