/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/* Copyright (C) 2001-2004 Novell, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "e2k-utils.h"
#include "e2k-autoconfig.h"
#include "e2k-propnames.h"
#include "e2k-rule.h"

#include <libedataserver/e-time-utils.h>

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

/* Do not internationalize */
const char *e2k_rfc822_months [] = {
	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

/**
 * e2k_parse_timestamp:
 * @timestamp: an ISO8601 timestamp returned by the Exchange server
 *
 * Converts @timestamp to a %time_t value. @timestamp must be in one
 * of the two ISO8601 variants used by Exchange.
 *
 * Note that the timestamps used (in most contexts) by Exchange have
 * millisecond resolution, so converting them to %time_t loses
 * resolution. Since ISO8601 timestamps can be compared using
 * strcmp(), it is often best to keep them as strings.
 *
 * Return value: the %time_t corresponding to @timestamp, or -1 on
 * error.
 **/
time_t
e2k_parse_timestamp (const char *timestamp)
{
	struct tm tm;

	tm.tm_year = strtoul (timestamp, (char **)&timestamp, 10) - 1900;
	if (*timestamp++ != '-')
		return -1;
	tm.tm_mon = strtoul (timestamp, (char **)&timestamp, 10) - 1;
	if (*timestamp++ != '-')
		return -1;
	tm.tm_mday = strtoul (timestamp, (char **)&timestamp, 10);
	if (*timestamp++ != 'T')
		return -1;
	tm.tm_hour = strtoul (timestamp, (char **)&timestamp, 10);
	if (*timestamp++ != ':')
		return -1;
	tm.tm_min = strtoul (timestamp, (char **)&timestamp, 10);
	if (*timestamp++ != ':')
		return -1;
	tm.tm_sec = strtoul (timestamp, (char **)&timestamp, 10);
	if (*timestamp != '.' && *timestamp != 'Z')
		return -1;

	return e_mktime_utc (&tm);
}

/**
 * e2k_make_timestamp:
 * @when: the %time_t to convert to an ISO8601 timestamp
 *
 * Creates an ISO8601 timestamp (in an format acceptable to Exchange)
 * corresponding to @when.
 *
 * Return value: the timestamp, which the caller must free.
 **/
char *
e2k_make_timestamp (time_t when)
{
	struct tm *tm;

	tm = gmtime (&when);
	return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02dZ",
				tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
				tm->tm_hour, tm->tm_min, tm->tm_sec);
}

/**
 * e2k_make_timestamp_rfc822:
 * @when: the %time_t to convert to an RFC822 timestamp
 *
 * Creates an RFC822 Date header value corresponding to @when, in the
 * locale timezone.
 *
 * Return value: the timestamp, which the caller must free.
 **/
char *
e2k_make_timestamp_rfc822 (time_t when)
{
	struct tm tm;
	int offset;

	e_localtime_with_offset (when, &tm, &offset);
	offset = (offset / 3600) * 100 + (offset / 60) % 60;

	return g_strdup_printf ("%02d %s %04d %02d:%02d:%02d %+05d",
				tm.tm_mday, e2k_rfc822_months[tm.tm_mon],
				tm.tm_year + 1900,
				tm.tm_hour, tm.tm_min, tm.tm_sec,
				offset);
}

/* SYSTIME_OFFSET is the number of minutes between the Windows epoch
 * (1601-01-01T00:00:00Z) and the time_t epoch (1970-01-01T00:00:00Z):
 * 369 years, 89 of which are leap years.
 */
#define SYSTIME_OFFSET 194074560UL

/**
 * e2k_systime_to_time_t:
 * @systime: a MAPI PT_SYSTIME value (minutes since Windows epoch)
 *
 * Converts the MAPI PT_SYSTIME value @systime to a corresponding
 * %time_t value (assuming it is within the valid range of a %time_t).
 *
 * Return value: a %time_t corresponding to @systime.
 **/
time_t
e2k_systime_to_time_t (guint32 systime)
{
	return (systime - SYSTIME_OFFSET) * 60;
}

/**
 * e2k_systime_from_time_t:
 * @tt: a %time_t value
 *
 * Converts the %time_t value @tt to a corresponding MAPI PT_SYSTIME
 * value, losing some precision if @tt does not fall on a minute
 * boundary.
 *
 * Return value: the Windows systime value corresponding to @tt
 **/
guint32
e2k_systime_from_time_t (time_t tt)
{
	return (tt / 60) + SYSTIME_OFFSET;
}

/**
 * e2k_filetime_to_time_t:
 * @filetime: a Windows FILETIME value (100ns intervals since
 * Windows epoch)
 *
 * Converts the Windows FILETIME value @filetime to a corresponding
 * %time_t value (assuming it is within the valid range of a %time_t),
 * truncating to a second boundary.
 *
 * Return value: a %time_t corresponding to @filetime.
 **/
time_t
e2k_filetime_to_time_t (guint64 filetime)
{
	return (time_t)(filetime / 10000000 - SYSTIME_OFFSET * 60);
}

/**
 * e2k_filetime_from_time_t:
 * @tt: a %time_t value
 *
 * Converts the %time_t value @tt to a corresponding Windows FILETIME
 * value.
 *
 * Return value: the Windows FILETIME value corresponding to @tt
 **/
guint64
e2k_filetime_from_time_t (time_t tt)
{
	return (((guint64)tt) + ((guint64)SYSTIME_OFFSET) * 60) * 10000000;
}

/**
 * e2k_lf_to_crlf:
 * @in: input text in UNIX ("\n") format
 *
 * Creates a copy of @in with all LFs converted to CRLFs.
 *
 * Return value: the converted text, which the caller must free.
 **/
char *
e2k_lf_to_crlf (const char *in)
{
	int len;
	const char *s;
	char *out, *d;

	g_return_val_if_fail (in != NULL, NULL);

	len = strlen (in);
	for (s = strchr (in, '\n'); s; s = strchr (s + 1, '\n'))
		len++;

	out = g_malloc (len + 1);
	for (s = in, d = out; *s; s++) {
		if (*s == '\n')
			*d++ = '\r';
		*d++ = *s;
	}
	*d = '\0';

	return out;
}

/**
 * e2k_crlf_to_lf:
 * @in: input text in network ("\r\n") format
 *
 * Creates a copy of @in with all CRLFs converted to LFs. (Actually,
 * it just strips CRs, so any raw CRs will be removed.)
 *
 * Return value: the converted text, which the caller must free.
 **/
char *
e2k_crlf_to_lf (const char *in)
{
	int len;
	const char *s;
	char *out;
	GString *str;

	g_return_val_if_fail (in != NULL, NULL);

	str = g_string_new ("");

	len = strlen (in);
	for (s = in; *s; s++) {
		if (*s != '\r')
			str = g_string_append_c (str, *s);
	}

	out = str->str;
	g_string_free (str, FALSE);

	return out;
}

/**
 * e2k_strdup_with_trailing_slash:
 * @path: a URI or path
 *
 * Copies @path, appending a "/" to it if and only if it did not
 * already end in "/".
 *
 * Return value: the path, which the caller must free
 **/
char *
e2k_strdup_with_trailing_slash (const char *path)
{
	char *p;

	p = strrchr (path, '/');
	if (p && !p[1])
		return g_strdup (path);
	else
		return g_strdup_printf ("%s/", path);
}

/**
 * e2k_entryid_to_dn:
 * @entryid: an Exchange entryid
 *
 * Finds an Exchange 5.5 DN inside a binary entryid property (such as
 * #PR_STORE_ENTRYID or an element of #PR_DELEGATES_ENTRYIDS).
 *
 * Return value: the entryid, which is a pointer into @entryid's data.
 **/
const char *
e2k_entryid_to_dn (GByteArray *entryid)
{
	char *p;

	p = ((char *)entryid->data) + entryid->len - 1;
	if (*p == 0) {
		while (*(p - 1) && p > (char *)entryid->data)
			p--;
		if (*p == '/')
			return p;
	}
	return NULL;
}

static void
append_permanenturl_section (GString *url, guint8 *entryid)
{
	int i = 0;

	/* First part */
	while (i < 16)
		g_string_append_printf (url, "%02x", entryid[i++]);

	/* Replace 0s with a single '-' */
	g_string_append_c (url, '-');
	while (i < 22 && entryid[i] == 0)
		i++;

	/* Last part; note that if the first non-0 byte can be
	 * expressed in a single hex digit, we do so. (ie, the 0
	 * in the 16's place was also accumulated into the
	 * preceding '-'.)
	 */
	if (i < 22 && entryid[i] < 0x10)
		g_string_append_printf (url, "%01x", entryid[i++]);
	while (i < 22)
		g_string_append_printf (url, "%02x", entryid[i++]);
}

#define E2K_PERMANENTURL_INFIX "-FlatUrlSpace-"
#define E2K_PERMANENTURL_INFIX_LEN (sizeof (E2K_PERMANENTURL_INFIX) - 1)

/**
 * e2k_entryid_to_permanenturl:
 * @entryid: an ENTRYID (specifically, a PR_SOURCE_KEY)
 * @base_uri: base URI of the store containing @entryid
 *
 * Creates a permanenturl based on @entryid and @base_uri.
 *
 * Return value: the permanenturl, which the caller must free.
 **/
char *
e2k_entryid_to_permanenturl (GByteArray *entryid, const char *base_uri)
{
	GString *url;
	char *ret;

	g_return_val_if_fail (entryid->len == 22 || entryid->len == 44, NULL);

	url = g_string_new (base_uri);
	if (url->str[url->len - 1] != '/')
		g_string_append_c (url, '/');
	g_string_append (url, E2K_PERMANENTURL_INFIX);
	g_string_append_c (url, '/');

	append_permanenturl_section (url, entryid->data);
	if (entryid->len > 22) {
		g_string_append_c (url, '/');
		append_permanenturl_section (url, entryid->data + 22);
	}

	ret = url->str;
	g_string_free (url, FALSE);
	return ret;
}

#define HEXVAL(c) (isdigit (c) ? (c) - '0' : g_ascii_tolower (c) - 'a' + 10)

static gboolean
append_entryid_section (GByteArray *entryid, const char **permanenturl)
{
	const char *p;
	char buf[44], byte;
	int endlen;

	p = *permanenturl;
	if (strspn (p, "0123456789abcdefABCDEF") != 32)
		return FALSE;
	if (p[32] != '-')
		return FALSE;
	endlen = strspn (p + 33, "0123456789abcdefABCDEF");
	if (endlen > 6)
		return FALSE;

	/* Expand to the full form by replacing the "-" with "0"s */
	memcpy (buf, p, 32);
	memset (buf + 32, '0', sizeof (buf) - 32 - endlen);
	memcpy (buf + sizeof (buf) - endlen, p + 33, endlen);

	p = buf;
	while (p < buf + sizeof (buf)) {
		byte = (HEXVAL (*p) << 4) + HEXVAL (*(p + 1));
		g_byte_array_append (entryid, &byte, 1);
		p += 2;
	}

	*permanenturl += 33 + endlen;
	return TRUE;
}

/**
 * e2k_permanenturl_to_entryid:
 * @permanenturl: an Exchange permanenturl
 *
 * Creates an ENTRYID (specifically, a PR_SOURCE_KEY) based on
 * @permanenturl
 *
 * Return value: the entryid
 **/
GByteArray *
e2k_permanenturl_to_entryid (const char *permanenturl)
{
	GByteArray *entryid;

	permanenturl = strstr (permanenturl, E2K_PERMANENTURL_INFIX);
	if (!permanenturl)
		return NULL;
	permanenturl += E2K_PERMANENTURL_INFIX_LEN;

	entryid = g_byte_array_new ();
	while (*permanenturl++ == '/') {
		if (!append_entryid_section (entryid, &permanenturl)) {
			g_byte_array_free (entryid, TRUE);
			return NULL;
		}
	}

	return entryid;
}

/**
 * e2k_ascii_strcase_equal
 * @v: a string
 * @v2: another string
 *
 * ASCII-case-insensitive comparison function for use with #GHashTable.
 *
 * Return value: %TRUE if @v and @v2 are ASCII-case-insensitively
 * equal, %FALSE if not.
 **/
gint
e2k_ascii_strcase_equal (gconstpointer v, gconstpointer v2)
{
	return !g_ascii_strcasecmp (v, v2);
}

/**
 * e2k_ascii_strcase_hash
 * @v: a string
 *
 * ASCII-case-insensitive hash function for use with #GHashTable.
 *
 * Return value: An ASCII-case-insensitive hashing of @v.
 **/
guint
e2k_ascii_strcase_hash (gconstpointer v)
{
	/* case-insensitive g_str_hash */

	const unsigned char *p = v;
	guint h = g_ascii_tolower (*p);

	if (h) {
		for (p += 1; *p != '\0'; p++)
			h = (h << 5) - h + g_ascii_tolower (*p);
	}

	return h;
}

/**
 * e2k_restriction_folders_only:
 * @rn: a restriction
 *
 * Examines @rn, and determines if it can only return folders
 *
 * Return value: %TRUE if @rn will cause only folders to be returned
 **/
gboolean
e2k_restriction_folders_only (E2kRestriction *rn)
{
	int i;

	if (!rn)
		return FALSE;

	switch (rn->type) {
	case E2K_RESTRICTION_PROPERTY:
		if (strcmp (rn->res.property.pv.prop.name,
			    E2K_PR_DAV_IS_COLLECTION) != 0)
			return FALSE;

		/* return TRUE if it's "= TRUE" or "!= FALSE" */
		return (rn->res.property.relop == E2K_RELOP_EQ) ==
			(rn->res.property.pv.value != NULL);

	case E2K_RESTRICTION_AND:
		for (i = 0; i < rn->res.and.nrns; i++) {
			if (e2k_restriction_folders_only (rn->res.and.rns[i]))
				return TRUE;
		}
		return FALSE;

	case E2K_RESTRICTION_OR:
		for (i = 0; i < rn->res.or.nrns; i++) {
			if (!e2k_restriction_folders_only (rn->res.or.rns[i]))
				return FALSE;
		}
		return TRUE;

	case E2K_RESTRICTION_NOT:
		return !e2k_restriction_folders_only (rn->res.not.rn);

	case E2K_RESTRICTION_COMMENT:
		return e2k_restriction_folders_only (rn->res.comment.rn);

	default:
		return FALSE;
	}
}

/* From MAPIDEFS.H */
static const char MAPI_ONE_OFF_UID[] = {
	0x81, 0x2b, 0x1f, 0xa4, 0xbe, 0xa3, 0x10, 0x19,
	0x9d, 0x6e, 0x00, 0xdd, 0x01, 0x0f, 0x54, 0x02
};
#define MAPI_ONE_OFF_UNICODE	  0x8000
#define MAPI_ONE_OFF_NO_RICH_INFO 0x0001
#define MAPI_ONE_OFF_MYSTERY_FLAG 0x1000

/**
 * e2k_entryid_generate_oneoff:
 * @display_name: the display name of the user
 * @email: the email address
 * @unicode: %TRUE to generate a Unicode ENTRYID (in which case
 * @display_name should be UTF-8), %FALSE for an ASCII ENTRYID.
 *
 * Constructs a "one-off" ENTRYID value that can be used as a MAPI
 * recipient (eg, for a message forwarding server-side rule),
 * corresponding to @display_name and @email.
 *
 * Return value: the recipient ENTRYID
 **/
GByteArray *
e2k_entryid_generate_oneoff (const char *display_name, const char *email, gboolean unicode)
{
	GByteArray *entryid;

	entryid = g_byte_array_new ();

	e2k_rule_append_uint32 (entryid, 0);
	g_byte_array_append (entryid, MAPI_ONE_OFF_UID, sizeof (MAPI_ONE_OFF_UID));
	e2k_rule_append_uint16 (entryid, 0);
	e2k_rule_append_uint16 (entryid,
				MAPI_ONE_OFF_NO_RICH_INFO |
				MAPI_ONE_OFF_MYSTERY_FLAG |
				(unicode ? MAPI_ONE_OFF_UNICODE : 0));

	if (unicode) {
		e2k_rule_append_unicode (entryid, display_name);
		e2k_rule_append_unicode (entryid, "SMTP");
		e2k_rule_append_unicode (entryid, email);
	} else {
		e2k_rule_append_string (entryid, display_name);
		e2k_rule_append_string (entryid, "SMTP");
		e2k_rule_append_string (entryid, email);
	}

	return entryid;
}

static const char MAPI_LOCAL_UID[] = {
	0xdc, 0xa7, 0x40, 0xc8, 0xc0, 0x42, 0x10, 0x1a,
	0xb4, 0xb9, 0x08, 0x00, 0x2b, 0x2f, 0xe1, 0x82
};

/**
 * e2k_entryid_generate_local:
 * @exchange_dn: the Exchange 5.5-style DN of the local user
 *
 * Constructs an ENTRYID value that can be used as a MAPI
 * recipient (eg, for a message forwarding server-side rule),
 * corresponding to the local user identified by @exchange_dn.
 *
 * Return value: the recipient ENTRYID
 **/
GByteArray *
e2k_entryid_generate_local (const char *exchange_dn)
{
	GByteArray *entryid;

	entryid = g_byte_array_new ();

	e2k_rule_append_uint32 (entryid, 0);
	g_byte_array_append (entryid, MAPI_LOCAL_UID, sizeof (MAPI_LOCAL_UID));
	e2k_rule_append_uint16 (entryid, 1);
	e2k_rule_append_uint16 (entryid, 0);
	e2k_rule_append_string (entryid, exchange_dn);

	return entryid;
}

static const char MAPI_CONTACT_UID[] = {
	0xfe, 0x42, 0xaa, 0x0a, 0x18, 0xc7, 0x1a, 0x10,
	0xe8, 0x85, 0x0b, 0x65, 0x1c, 0x24, 0x00, 0x00
};

/**
 * e2k_entryid_generate_contact:
 * @contact_entryid: the #PR_ENTRYID of an item in the user's Contacts
 * folder.
 * @nth_address: which of the contact's email addresses to use.
 *
 * Constructs an ENTRYID value that can be used as a MAPI recipient
 * (eg, for a message forwarding server-side rule), corresponding to
 * the Contacts folder entry identified by @contact_entryid.
 *
 * Return value: the recipient ENTRYID
 **/
GByteArray *
e2k_entryid_generate_contact (GByteArray *contact_entryid, int nth_address)
{
	GByteArray *entryid;

	entryid = g_byte_array_new ();

	e2k_rule_append_uint32 (entryid, 0);
	g_byte_array_append (entryid, MAPI_CONTACT_UID, sizeof (MAPI_CONTACT_UID));
	e2k_rule_append_uint32 (entryid, 3);
	e2k_rule_append_uint32 (entryid, 4);
	e2k_rule_append_uint32 (entryid, nth_address);
	e2k_rule_append_uint32 (entryid, contact_entryid->len);
	g_byte_array_append (entryid, contact_entryid->data, contact_entryid->len);

	return entryid;
}

/**
 * e2k_search_key_generate:
 * @addrtype: the type of @address (usually "SMTP" or "EX")
 * @address: the address data
 *
 * Constructs a PR_SEARCH_KEY value for @address
 *
 * Return value: the search key
 **/
GByteArray *
e2k_search_key_generate (const char *addrtype, const char *address)
{
	GByteArray *search_key;
	guint8 *p;

	search_key = g_byte_array_new ();
	g_byte_array_append (search_key, addrtype, strlen (addrtype));
	g_byte_array_append (search_key, ":", 1);
	g_byte_array_append (search_key, address, strlen (address));
	g_byte_array_append (search_key, "", 1);

	for (p = search_key->data; *p; p++)
		*p = g_ascii_toupper (*p);

	return search_key;
}
