/* * This file is part of the Sofia-SIP package * * Copyright (C) 2005 Nokia Corporation. * * Contact: Pekka Pessi * * 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 * * @date Created: Wed Apr 11 18:57:06 2001 ppessi * */ #include "config.h" #include #include #include #include #include #include #include #include #include /** 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 , 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 * 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 field. * * The is defined as follows: * @code * delta-seconds = 1*DIGIT * @endcode * * Note, however, that 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() "" 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() "" or * @ref msg_delta_d() "" 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; }