msg_date.c 10.8 KB
Newer Older
Pekka Pessi's avatar
Pekka Pessi committed
1 2 3 4 5 6 7
/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
8
 * This library is free software; you can redistribute it and/or
Pekka Pessi's avatar
Pekka Pessi committed
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
 * 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.
 *
29
 * Parsing functions for @RFC1123 (GMT) date.
Pekka Pessi's avatar
Pekka Pessi committed
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
 *
 * @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>

45
#include <sofia-sip/su_string.h>
46 47
#include <sofia-sip/msg_date.h>
#include <sofia-sip/bnf.h>
Pekka Pessi's avatar
Pekka Pessi committed
48

49
#include <sofia-sip/su_time.h>
Pekka Pessi's avatar
Pekka Pessi committed
50 51 52 53 54 55 56 57 58

/** 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')

59
/**Epoch year. @internal
Pekka Pessi's avatar
Pekka Pessi committed
60 61 62
 *
 * First day of the epoch year should be Monday.
 */
63
#define EPOCH 1900
64
/** Is this year a leap year? @internal */
Pekka Pessi's avatar
Pekka Pessi committed
65
#define LEAP_YEAR(y) ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0))
66
/** Day number of New Year Day of given year. @internal */
Pekka Pessi's avatar
Pekka Pessi committed
67 68 69 70 71 72
#define YEAR_DAYS(y) \
  (((y)-1) * 365 + ((y)-1) / 4 - ((y)-1) / 100 + ((y)-1) / 400)


/* ====================================================================== */

73
static unsigned char const days_per_months[12] =
Pekka Pessi's avatar
Pekka Pessi committed
74 75 76 77 78
  {
    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. */
79
static signed char const first_day_offset[12] =
Pekka Pessi's avatar
Pekka Pessi committed
80 81 82 83
  {
    0, 1, -1, 0, 0, 1, 1, 2, 3, 3, 4, 4
  };

84
static char const wkdays[7 * 4] =
Pekka Pessi's avatar
Pekka Pessi committed
85 86
  "Mon\0" "Tue\0" "Wed\0" "Thu\0" "Fri\0" "Sat\0" "Sun";

87 88
static char const months[12 * 4] =
  "Jan\0" "Feb\0" "Mar\0" "Apr\0" "May\0" "Jun\0"
Pekka Pessi's avatar
Pekka Pessi committed
89 90
  "Jul\0" "Aug\0" "Sep\0" "Oct\0" "Nov\0" "Dec";

91
/** Parse a month name.
Pekka Pessi's avatar
Pekka Pessi committed
92 93 94 95
 *
 * @return The function month_d() returns 0..11 if given first three letters
 * of month name, or -1 if no month name matches.
 */
96
su_inline
Pekka Pessi's avatar
Pekka Pessi committed
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
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 */
129
su_inline
130
int time_d(char const **ss,
Pekka Pessi's avatar
Pekka Pessi committed
131 132 133
	   unsigned long *hour, unsigned long *min, unsigned long *sec)
{
  char const *s = *ss;
134 135
  if (!IS_LWS(*s))
    return -1;
Pekka Pessi's avatar
Pekka Pessi committed
136 137 138
  skip_lws(&s);
  if (!is_digit(*s)) return -1;
  *hour = *s++ - '0'; if (is_digit(*s)) *hour = 10 * (*hour) + *s++ - '0';
139
  if (*s++ != ':' || !is_digit(s[0]) || !is_digit(s[1]))
Pekka Pessi's avatar
Pekka Pessi committed
140 141
    return -1;
  *min = 10 * s[0] + s[1] - 11 * '0'; s += 2;
142
  if (*s++ != ':' || !is_digit(s[0]) || !is_digit(s[1]))
Pekka Pessi's avatar
Pekka Pessi committed
143 144 145 146 147 148 149 150 151 152
    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.
153 154
 *
 * The function msg_date_d() decodes <HTTP-date>, which may have
Pekka Pessi's avatar
Pekka Pessi committed
155
 * different formats.
156
 *
157 158 159
 * @code
 * HTTP-date    = rfc1123-date / rfc850-date / asctime-date
 *
Pekka Pessi's avatar
Pekka Pessi committed
160 161 162
 * rfc1123-date = wkday "," SP date1 SP time SP "GMT"
 * date1        = 2DIGIT SP month SP 4DIGIT
 *                ; day month year (e.g., 02 Jun 1982)
163 164
 *
 * rfc850-date  = weekday "," SP date2 SP time SP "GMT"
Pekka Pessi's avatar
Pekka Pessi committed
165 166
 * date2        = 2DIGIT "-" month "-" 2DIGIT
 *                ; day-month-year (e.g., 02-Jun-82)
167 168 169
 *
 * asctime-date = wkday SP date3 SP time SP 4DIGIT
 * date3        = month SP ( 2DIGIT / ( SP 1DIGIT ))
Pekka Pessi's avatar
Pekka Pessi committed
170
 *                ; month day (e.g., Jun  2)
171
 *
Pekka Pessi's avatar
Pekka Pessi committed
172 173
 * time         = 2DIGIT ":" 2DIGIT ":" 2DIGIT
 *                ; 00:00:00 - 23:59:59
174 175 176 177
 *
 * wkday        = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"
 * weekday      = "Monday" / "Tuesday" / "Wednesday"
 *              / "Thursday" / "Friday" / "Saturday" / "Sunday"
178
 * month        = "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun"
179 180
 *              / "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
 * @endcode
Pekka Pessi's avatar
Pekka Pessi committed
181
 */
182
issize_t msg_date_d(char const **ss, msg_time_t *date)
Pekka Pessi's avatar
Pekka Pessi committed
183 184 185 186
{
  char const *s = *ss;
  char const *wkday;
  char const *tz;
187 188 189
  unsigned long day, year, hour, min, sec;
  int mon;

Pekka Pessi's avatar
Pekka Pessi committed
190 191 192
  if (!IS_TOKEN(*s) || !date)
    return -1;

193
  wkday = s; skip_token(&s); if (*s == ',') s++;
Pekka Pessi's avatar
Pekka Pessi committed
194 195 196 197
  while (IS_LWS(*s)) s++;

  if (is_digit(*s)) {
    day = *s++ - '0'; if (is_digit(*s)) day = 10 * day + *s++ - '0';
198

Pekka Pessi's avatar
Pekka Pessi committed
199
    if (*s == ' ') {
200
      /* rfc1123-date =
Pekka Pessi's avatar
Pekka Pessi committed
201 202 203 204 205
	 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++;
206
      if (!is_digit(s[0]) || !is_digit(s[1]) ||
Pekka Pessi's avatar
Pekka Pessi committed
207 208 209 210 211
	  !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 == '-') {
212
      /* rfc822-date =
Pekka Pessi's avatar
Pekka Pessi committed
213 214 215 216 217 218 219
	 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;
220
      year = 10 * s[4] + s[5] - 11 * '0';
Pekka Pessi's avatar
Pekka Pessi committed
221
      if (is_digit(s[6]) && is_digit(s[7])) {
222
	/* rfc2822-date =
Pekka Pessi's avatar
Pekka Pessi committed
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
	   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);
242
      if (!su_casenmatch(tz, "GMT", 3) && !su_casenmatch(tz, "UCT", 3))
Pekka Pessi's avatar
Pekka Pessi committed
243 244 245 246
	return -1;
    }
  }
  else {
247
    /* actime-date =
Pekka Pessi's avatar
Pekka Pessi committed
248 249 250
       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++;
251
    while (IS_LWS(*s)) s++;
Pekka Pessi's avatar
Pekka Pessi committed
252 253 254
    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;
255
    /* Accept also unix date (if it is GMT) */
Pekka Pessi's avatar
Pekka Pessi committed
256 257 258 259 260
    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;
261
    if (!is_digit(s[0]) || !is_digit(s[1]) ||
Pekka Pessi's avatar
Pekka Pessi committed
262 263 264 265
	!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;
  }
266

Pekka Pessi's avatar
Pekka Pessi committed
267 268 269 270 271 272 273 274
  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;
  }
275

Pekka Pessi's avatar
Pekka Pessi committed
276 277 278 279 280 281 282 283 284
  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);
285

Pekka Pessi's avatar
Pekka Pessi committed
286
#if 0
287 288
    printf("Year %d%s starts %ld = %d - %d days since epoch (%d)\n",
	       year, leap_year ? " (leap year)" : "",
Pekka Pessi's avatar
Pekka Pessi committed
289 290
	   ydays, YEAR_DAYS(year), YEAR_DAYS(EPOCH), EPOCH);
#endif
291 292 293 294 295

    *date = sec + 60 *
      (min + 60 *
	   (hour + 24 *
	    (day - 1 + mon * 30 + first_day_offset[mon] +
Pekka Pessi's avatar
Pekka Pessi committed
296 297 298 299 300 301 302 303 304
	     (leap_year && mon > 2) + ydays)));
  }
  *ss = s;

  return 0;
}


/**Encode RFC1123-date.
305
 *
306
 * The function msg_date_e() prints @e http-date in the <rfc1123-date>
Pekka Pessi's avatar
Pekka Pessi committed
307
 * format. The format is as follows:
308
 *
309 310 311 312 313 314 315 316 317 318 319 320
 * @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
321
 *
Pekka Pessi's avatar
Pekka Pessi committed
322 323 324
 * @param b         buffer to print the date
 * @param bsiz      size of the buffer
 * @param http_date seconds since 01 Jan 1900.
325
 *
Pekka Pessi's avatar
Pekka Pessi committed
326 327
 * @return The function msg_date_e() returns the size of the formatted date.
 */
328
issize_t msg_date_e(char b[], isize_t bsiz, msg_time_t http_date)
Pekka Pessi's avatar
Pekka Pessi committed
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
{
  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);

353
  month = 0, days_per_month = 31;
Pekka Pessi's avatar
Pekka Pessi committed
354 355 356 357 358 359 360
  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",
361
		  wkdays + wkday * 4, day + 1, months + month * 4,
Pekka Pessi's avatar
Pekka Pessi committed
362 363 364 365
		  year, hour, min, sec);
}


366
/**Decode a delta-seconds.
367
 *
368 369 370 371 372 373 374 375
 * 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.
Pekka Pessi's avatar
Pekka Pessi committed
376
 */
377
issize_t msg_delta_d(char const **ss, msg_time_t *delta)
Pekka Pessi's avatar
Pekka Pessi committed
378
{
379 380 381
  char const *s = *ss;

  if (!is_digit(*s))
Pekka Pessi's avatar
Pekka Pessi committed
382 383 384 385
    return -1;

  *delta = strtoul(*ss, (char **)ss, 10);
  skip_lws(ss);
386 387

  return *ss - s;
Pekka Pessi's avatar
Pekka Pessi committed
388 389
}

390
/**Encode @ref msg_delta_d() "<delta-seconds>" field.
Pekka Pessi's avatar
Pekka Pessi committed
391
 */
392
issize_t msg_delta_e(char b[], isize_t bsiz, msg_time_t delta)
Pekka Pessi's avatar
Pekka Pessi committed
393 394 395 396
{
  return snprintf(b, bsiz, "%lu", (unsigned long)delta);
}

397 398 399
/** Decode a HTTP date or delta
 *
 * Decode a @ref msg_date_d() "<http-date>" or
400
 * @ref msg_delta_d() "<delta-seconds>" field.
Pekka Pessi's avatar
Pekka Pessi committed
401
 */
402
issize_t msg_date_delta_d(char const **ss,
403 404
			  msg_time_t *date,
			  msg_time_t *delta)
Pekka Pessi's avatar
Pekka Pessi committed
405 406 407 408 409 410 411 412 413 414
{
  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;
}