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 46
#include <sofia-sip/msg_date.h>
#include <sofia-sip/bnf.h>
Pekka Pessi's avatar
Pekka Pessi committed
47

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

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

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


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

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

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

86 87
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
88 89
  "Jul\0" "Aug\0" "Sep\0" "Oct\0" "Nov\0" "Dec";

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

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

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

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

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

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

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

Pekka Pessi's avatar
Pekka Pessi committed
285
#if 0
286 287
    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
288 289
	   ydays, YEAR_DAYS(year), YEAR_DAYS(EPOCH), EPOCH);
#endif
290 291 292 293 294

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

  return 0;
}


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

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


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

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

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

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

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

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