view src/lib-mail/rfc822-date.c @ 49:6be018ca51ef HEAD

Year was set wrong in date parser. We'll also now do full checking for date string to make sure we don't overflow/underflow anything.
author Timo Sirainen <tss@iki.fi>
date Tue, 27 Aug 2002 20:35:00 +0300
parents 3b1985cbc908
children 8abce0f7d065
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "lib.h"
#include "gmtoff.h"
#include "rfc822-date.h"
#include "rfc822-tokenize.h"

#include <ctype.h>

static const char *month_names[] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

static const char *weekday_names[] = {
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};

static int parse_timezone(const char *str, unsigned int len)
{
	int offset;
	char chr;

	if (len == 5 && (*str == '+' || *str == '-')) {
		/* numeric offset */
		if (!i_isdigit(str[0]) || !i_isdigit(str[1]) ||
		    !i_isdigit(str[1]) || !i_isdigit(str[2]))
			return FALSE;

		offset = (str[1]-'0') * 1000 + (str[2]-'0') * 100 +
			(str[3]-'0') * 10 + (str[4]-'0');
		return *str == '+' ? offset : -offset;
	}

	if (len == 1) {
		/* military zone - handle them the correct way, not as
		   RFC822 says. RFC2822 though suggests that they'd be
		   considered as unspecified.. */
		chr = i_toupper(*str);
		if (chr < 'J')
			return (*str-'A'+1) * 60;
		if (chr == 'J')
			return 0;
		if (chr <= 'M')
			return (*str-'A') * 60;
		if (chr < 'Z')
			return ('M'-*str) * 60;
		return 0;
	}

	if (len == 2 && i_toupper(str[0]) == 'U' && i_toupper(str[1]) == 'T') {
		/* UT - Universal Time */
		return 0;
	}

	if (len == 3) {
		/* GMT | [ECMP][DS]T */
		if (str[2] != 'T')
			return 0;

		switch (i_toupper(*str)) {
		case 'E':
			offset = -5 * 60;
			break;
		case 'C':
			offset = -6 * 60;
			break;
		case 'M':
			offset = -7 * 60;
			break;
		case 'P':
			offset = -8 * 60;
			break;
		default:
			/* GMT and others */
			return 0;
		}

		if (i_toupper(str[1]) == 'D')
			return offset + 60;
		if (i_toupper(str[1] == 'S'))
			return offset;
	}

	return 0;
}

static const Rfc822Token *next_token(const Rfc822Token **tokens)
{
	const Rfc822Token *ret;

	if ((*tokens)->token == 0)
		return NULL;

	ret = *tokens;
	(*tokens)++;
	return ret;
}

int rfc822_parse_date(const char *str, time_t *time)
{
	struct tm tm;
	const Rfc822Token *tokens, *tok;
	unsigned int i;
	int zone_offset;

	if (str == NULL || *str == '\0')
		return FALSE;

	/* [weekday_name "," ] dd month_name [yy]yy hh:mi[:ss] timezone

	   we support comments here even while no-one ever uses them */

	tokens = rfc822_tokenize(str, NULL, NULL, NULL);

	memset(&tm, 0, sizeof(tm));

	/* skip the optional weekday */
	tok = next_token(&tokens);
	if (tok != NULL && tok->token == 'A' && tok->len == 3) {
		tok = next_token(&tokens);
		if (tok == NULL || tok->token != ',')
			return FALSE;

		tok = next_token(&tokens);
	}

	/* dd */
	if (tok == NULL || tok->token != 'A' || tok->len != 2 ||
	    !i_isdigit(tok->ptr[0]) || !i_isdigit(tok->ptr[1]))
		return FALSE;
	tm.tm_mday = (tok->ptr[0]-'0') * 10 + (tok->ptr[1]-'0');

	/* month name */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != 'A' || tok->len != 3)
		return FALSE;

	for (i = 0; i < 12; i++) {
		if (strncasecmp(month_names[i], tok->ptr, 3) == 0) {
			tm.tm_mon = i;
			break;
		}
	}
	if (i == 12)
		return FALSE;

	/* [yy]yy */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != 'A' ||
	    (tok->len != 2 && tok->len != 4))
		return FALSE;

	for (i = 0; i < tok->len; i++) {
		if (!i_isdigit(tok->ptr[i]))
			return FALSE;
		tm.tm_year = tm.tm_year * 10 + (tok->ptr[i]-'0');
	}

	if (tok->len == 2) {
		/* two digit year, assume 1970+ */
		if (tm.tm_year < 70)
			tm.tm_year += 100;
	} else {
		if (tm.tm_year < 1900)
			return FALSE;
		tm.tm_year -= 1900;
	}

	/* hh */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != 'A' || tok->len != 2 ||
	    !i_isdigit(tok->ptr[0]) || !i_isdigit(tok->ptr[1]))
		return FALSE;
	tm.tm_hour = (tok->ptr[0]-'0') * 10 + (tok->ptr[1]-'0');

	/* :mm */
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != ':')
		return FALSE;
	tok = next_token(&tokens);
	if (tok == NULL || tok->token != 'A' || tok->len != 2 ||
	    !i_isdigit(tok->ptr[0]) || !i_isdigit(tok->ptr[1]))
		return FALSE;
	tm.tm_min = (tok->ptr[0]-'0') * 10 + (tok->ptr[1]-'0');

	/* [:ss] */
	tok = next_token(&tokens);
	if (tok != NULL && tok->token == ':') {
		tok = next_token(&tokens);
		if (tok == NULL || tok->token != 'A' || tok->len != 2 ||
		    !i_isdigit(tok->ptr[0]) || !i_isdigit(tok->ptr[1]))
			return FALSE;
		tm.tm_sec = (tok->ptr[0]-'0') * 10 + (tok->ptr[1]-'0');
	}

	/* timezone */
	if (tok == NULL || tok->token != 'A')
		return FALSE;

	zone_offset = parse_timezone(tok->ptr, tok->len);

	tm.tm_isdst = -1;
	*time = mktime(&tm);
	if (*time < 0)
		return FALSE;

	*time -= zone_offset * 60;
	return TRUE;
}

const char *rfc822_to_date(time_t time)
{
	struct tm *tm;
	int offset, negative;

	tm = localtime(&time);
	offset = gmtoff(tm, time);
	if (offset >= 0)
		negative = 0;
	else {
		negative = 1;
		offset = -offset;
	}
	offset /= 60;

	return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %c%02d%02d",
			       weekday_names[tm->tm_wday],
			       tm->tm_mday,
			       month_names[tm->tm_mon],
			       tm->tm_year+1900,
			       tm->tm_hour, tm->tm_min, tm->tm_sec,
			       negative ? '-' : '+', offset / 60, offset % 60);
}