1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /* Copyright (C) 2001-2004 Novell, Inc.
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of version 2 of the GNU Lesser General Public
7 * License as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
24 #include "e2k-utils.h"
25 #include "e2k-autoconfig.h"
26 #include "e2k-propnames.h"
29 #include <libedataserver/e-time-utils.h>
35 /* Do not internationalize */
36 const char *e2k_rfc822_months [] = {
37 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
38 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
42 * e2k_parse_timestamp:
43 * @timestamp: an ISO8601 timestamp returned by the Exchange server
45 * Converts @timestamp to a %time_t value. @timestamp must be in one
46 * of the two ISO8601 variants used by Exchange.
48 * Note that the timestamps used (in most contexts) by Exchange have
49 * millisecond resolution, so converting them to %time_t loses
50 * resolution. Since ISO8601 timestamps can be compared using
51 * strcmp(), it is often best to keep them as strings.
53 * Return value: the %time_t corresponding to @timestamp, or -1 on
57 e2k_parse_timestamp (const char *timestamp)
61 tm.tm_year = strtoul (timestamp, (char **)×tamp, 10) - 1900;
62 if (*timestamp++ != '-')
64 tm.tm_mon = strtoul (timestamp, (char **)×tamp, 10) - 1;
65 if (*timestamp++ != '-')
67 tm.tm_mday = strtoul (timestamp, (char **)×tamp, 10);
68 if (*timestamp++ != 'T')
70 tm.tm_hour = strtoul (timestamp, (char **)×tamp, 10);
71 if (*timestamp++ != ':')
73 tm.tm_min = strtoul (timestamp, (char **)×tamp, 10);
74 if (*timestamp++ != ':')
76 tm.tm_sec = strtoul (timestamp, (char **)×tamp, 10);
77 if (*timestamp != '.' && *timestamp != 'Z')
80 return e_mktime_utc (&tm);
85 * @when: the %time_t to convert to an ISO8601 timestamp
87 * Creates an ISO8601 timestamp (in an format acceptable to Exchange)
88 * corresponding to @when.
90 * Return value: the timestamp, which the caller must free.
93 e2k_make_timestamp (time_t when)
98 return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02dZ",
99 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
100 tm->tm_hour, tm->tm_min, tm->tm_sec);
104 * e2k_make_timestamp_rfc822:
105 * @when: the %time_t to convert to an RFC822 timestamp
107 * Creates an RFC822 Date header value corresponding to @when, in the
110 * Return value: the timestamp, which the caller must free.
113 e2k_make_timestamp_rfc822 (time_t when)
118 e_localtime_with_offset (when, &tm, &offset);
119 offset = (offset / 3600) * 100 + (offset / 60) % 60;
121 return g_strdup_printf ("%02d %s %04d %02d:%02d:%02d %+05d",
122 tm.tm_mday, e2k_rfc822_months[tm.tm_mon],
124 tm.tm_hour, tm.tm_min, tm.tm_sec,
128 /* SYSTIME_OFFSET is the number of minutes between the Windows epoch
129 * (1601-01-01T00:00:00Z) and the time_t epoch (1970-01-01T00:00:00Z):
130 * 369 years, 89 of which are leap years.
132 #define SYSTIME_OFFSET 194074560UL
135 * e2k_systime_to_time_t:
136 * @systime: a MAPI PT_SYSTIME value (minutes since Windows epoch)
138 * Converts the MAPI PT_SYSTIME value @systime to a corresponding
139 * %time_t value (assuming it is within the valid range of a %time_t).
141 * Return value: a %time_t corresponding to @systime.
144 e2k_systime_to_time_t (guint32 systime)
146 return (systime - SYSTIME_OFFSET) * 60;
150 * e2k_systime_from_time_t:
151 * @tt: a %time_t value
153 * Converts the %time_t value @tt to a corresponding MAPI PT_SYSTIME
154 * value, losing some precision if @tt does not fall on a minute
157 * Return value: the Windows systime value corresponding to @tt
160 e2k_systime_from_time_t (time_t tt)
162 return (tt / 60) + SYSTIME_OFFSET;
166 * e2k_filetime_to_time_t:
167 * @filetime: a Windows FILETIME value (100ns intervals since
170 * Converts the Windows FILETIME value @filetime to a corresponding
171 * %time_t value (assuming it is within the valid range of a %time_t),
172 * truncating to a second boundary.
174 * Return value: a %time_t corresponding to @filetime.
177 e2k_filetime_to_time_t (guint64 filetime)
179 return (time_t)(filetime / 10000000 - SYSTIME_OFFSET * 60);
183 * e2k_filetime_from_time_t:
184 * @tt: a %time_t value
186 * Converts the %time_t value @tt to a corresponding Windows FILETIME
189 * Return value: the Windows FILETIME value corresponding to @tt
192 e2k_filetime_from_time_t (time_t tt)
194 return (((guint64)tt) + ((guint64)SYSTIME_OFFSET) * 60) * 10000000;
199 * @in: input text in UNIX ("\n") format
201 * Creates a copy of @in with all LFs converted to CRLFs.
203 * Return value: the converted text, which the caller must free.
206 e2k_lf_to_crlf (const char *in)
212 g_return_val_if_fail (in != NULL, NULL);
215 for (s = strchr (in, '\n'); s; s = strchr (s + 1, '\n'))
218 out = g_malloc (len + 1);
219 for (s = in, d = out; *s; s++) {
231 * @in: input text in network ("\r\n") format
233 * Creates a copy of @in with all CRLFs converted to LFs. (Actually,
234 * it just strips CRs, so any raw CRs will be removed.)
236 * Return value: the converted text, which the caller must free.
239 e2k_crlf_to_lf (const char *in)
246 g_return_val_if_fail (in != NULL, NULL);
248 str = g_string_new ("");
251 for (s = in; *s; s++) {
253 str = g_string_append_c (str, *s);
257 g_string_free (str, FALSE);
263 * e2k_strdup_with_trailing_slash:
264 * @path: a URI or path
266 * Copies @path, appending a "/" to it if and only if it did not
267 * already end in "/".
269 * Return value: the path, which the caller must free
272 e2k_strdup_with_trailing_slash (const char *path)
276 p = strrchr (path, '/');
278 return g_strdup (path);
280 return g_strdup_printf ("%s/", path);
285 * @entryid: an Exchange entryid
287 * Finds an Exchange 5.5 DN inside a binary entryid property (such as
288 * #PR_STORE_ENTRYID or an element of #PR_DELEGATES_ENTRYIDS).
290 * Return value: the entryid, which is a pointer into @entryid's data.
293 e2k_entryid_to_dn (GByteArray *entryid)
297 p = ((char *)entryid->data) + entryid->len - 1;
299 while (*(p - 1) && p > (char *)entryid->data)
308 append_permanenturl_section (GString *url, guint8 *entryid)
314 g_string_append_printf (url, "%02x", entryid[i++]);
316 /* Replace 0s with a single '-' */
317 g_string_append_c (url, '-');
318 while (i < 22 && entryid[i] == 0)
321 /* Last part; note that if the first non-0 byte can be
322 * expressed in a single hex digit, we do so. (ie, the 0
323 * in the 16's place was also accumulated into the
326 if (i < 22 && entryid[i] < 0x10)
327 g_string_append_printf (url, "%01x", entryid[i++]);
329 g_string_append_printf (url, "%02x", entryid[i++]);
332 #define E2K_PERMANENTURL_INFIX "-FlatUrlSpace-"
333 #define E2K_PERMANENTURL_INFIX_LEN (sizeof (E2K_PERMANENTURL_INFIX) - 1)
336 * e2k_entryid_to_permanenturl:
337 * @entryid: an ENTRYID (specifically, a PR_SOURCE_KEY)
338 * @base_uri: base URI of the store containing @entryid
340 * Creates a permanenturl based on @entryid and @base_uri.
342 * Return value: the permanenturl, which the caller must free.
345 e2k_entryid_to_permanenturl (GByteArray *entryid, const char *base_uri)
350 g_return_val_if_fail (entryid->len == 22 || entryid->len == 44, NULL);
352 url = g_string_new (base_uri);
353 if (url->str[url->len - 1] != '/')
354 g_string_append_c (url, '/');
355 g_string_append (url, E2K_PERMANENTURL_INFIX);
356 g_string_append_c (url, '/');
358 append_permanenturl_section (url, entryid->data);
359 if (entryid->len > 22) {
360 g_string_append_c (url, '/');
361 append_permanenturl_section (url, entryid->data + 22);
365 g_string_free (url, FALSE);
369 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : g_ascii_tolower (c) - 'a' + 10)
372 append_entryid_section (GByteArray *entryid, const char **permanenturl)
379 if (strspn (p, "0123456789abcdefABCDEF") != 32)
383 endlen = strspn (p + 33, "0123456789abcdefABCDEF");
387 /* Expand to the full form by replacing the "-" with "0"s */
389 memset (buf + 32, '0', sizeof (buf) - 32 - endlen);
390 memcpy (buf + sizeof (buf) - endlen, p + 33, endlen);
393 while (p < buf + sizeof (buf)) {
394 byte = (HEXVAL (*p) << 4) + HEXVAL (*(p + 1));
395 g_byte_array_append (entryid, &byte, 1);
399 *permanenturl += 33 + endlen;
404 * e2k_permanenturl_to_entryid:
405 * @permanenturl: an Exchange permanenturl
407 * Creates an ENTRYID (specifically, a PR_SOURCE_KEY) based on
410 * Return value: the entryid
413 e2k_permanenturl_to_entryid (const char *permanenturl)
417 permanenturl = strstr (permanenturl, E2K_PERMANENTURL_INFIX);
420 permanenturl += E2K_PERMANENTURL_INFIX_LEN;
422 entryid = g_byte_array_new ();
423 while (*permanenturl++ == '/') {
424 if (!append_entryid_section (entryid, &permanenturl)) {
425 g_byte_array_free (entryid, TRUE);
434 * e2k_ascii_strcase_equal
436 * @v2: another string
438 * ASCII-case-insensitive comparison function for use with #GHashTable.
440 * Return value: %TRUE if @v and @v2 are ASCII-case-insensitively
441 * equal, %FALSE if not.
444 e2k_ascii_strcase_equal (gconstpointer v, gconstpointer v2)
446 return !g_ascii_strcasecmp (v, v2);
450 * e2k_ascii_strcase_hash
453 * ASCII-case-insensitive hash function for use with #GHashTable.
455 * Return value: An ASCII-case-insensitive hashing of @v.
458 e2k_ascii_strcase_hash (gconstpointer v)
460 /* case-insensitive g_str_hash */
462 const unsigned char *p = v;
463 guint h = g_ascii_tolower (*p);
466 for (p += 1; *p != '\0'; p++)
467 h = (h << 5) - h + g_ascii_tolower (*p);
474 * e2k_restriction_folders_only:
477 * Examines @rn, and determines if it can only return folders
479 * Return value: %TRUE if @rn will cause only folders to be returned
482 e2k_restriction_folders_only (E2kRestriction *rn)
490 case E2K_RESTRICTION_PROPERTY:
491 if (strcmp (rn->res.property.pv.prop.name,
492 E2K_PR_DAV_IS_COLLECTION) != 0)
495 /* return TRUE if it's "= TRUE" or "!= FALSE" */
496 return (rn->res.property.relop == E2K_RELOP_EQ) ==
497 (rn->res.property.pv.value != NULL);
499 case E2K_RESTRICTION_AND:
500 for (i = 0; i < rn->res.and.nrns; i++) {
501 if (e2k_restriction_folders_only (rn->res.and.rns[i]))
506 case E2K_RESTRICTION_OR:
507 for (i = 0; i < rn->res.or.nrns; i++) {
508 if (!e2k_restriction_folders_only (rn->res.or.rns[i]))
513 case E2K_RESTRICTION_NOT:
514 return !e2k_restriction_folders_only (rn->res.not.rn);
516 case E2K_RESTRICTION_COMMENT:
517 return e2k_restriction_folders_only (rn->res.comment.rn);
524 /* From MAPIDEFS.H */
525 static const char MAPI_ONE_OFF_UID[] = {
526 0x81, 0x2b, 0x1f, 0xa4, 0xbe, 0xa3, 0x10, 0x19,
527 0x9d, 0x6e, 0x00, 0xdd, 0x01, 0x0f, 0x54, 0x02
529 #define MAPI_ONE_OFF_UNICODE 0x8000
530 #define MAPI_ONE_OFF_NO_RICH_INFO 0x0001
531 #define MAPI_ONE_OFF_MYSTERY_FLAG 0x1000
534 * e2k_entryid_generate_oneoff:
535 * @display_name: the display name of the user
536 * @email: the email address
537 * @unicode: %TRUE to generate a Unicode ENTRYID (in which case
538 * @display_name should be UTF-8), %FALSE for an ASCII ENTRYID.
540 * Constructs a "one-off" ENTRYID value that can be used as a MAPI
541 * recipient (eg, for a message forwarding server-side rule),
542 * corresponding to @display_name and @email.
544 * Return value: the recipient ENTRYID
547 e2k_entryid_generate_oneoff (const char *display_name, const char *email, gboolean unicode)
551 entryid = g_byte_array_new ();
553 e2k_rule_append_uint32 (entryid, 0);
554 g_byte_array_append (entryid, MAPI_ONE_OFF_UID, sizeof (MAPI_ONE_OFF_UID));
555 e2k_rule_append_uint16 (entryid, 0);
556 e2k_rule_append_uint16 (entryid,
557 MAPI_ONE_OFF_NO_RICH_INFO |
558 MAPI_ONE_OFF_MYSTERY_FLAG |
559 (unicode ? MAPI_ONE_OFF_UNICODE : 0));
562 e2k_rule_append_unicode (entryid, display_name);
563 e2k_rule_append_unicode (entryid, "SMTP");
564 e2k_rule_append_unicode (entryid, email);
566 e2k_rule_append_string (entryid, display_name);
567 e2k_rule_append_string (entryid, "SMTP");
568 e2k_rule_append_string (entryid, email);
574 static const char MAPI_LOCAL_UID[] = {
575 0xdc, 0xa7, 0x40, 0xc8, 0xc0, 0x42, 0x10, 0x1a,
576 0xb4, 0xb9, 0x08, 0x00, 0x2b, 0x2f, 0xe1, 0x82
580 * e2k_entryid_generate_local:
581 * @exchange_dn: the Exchange 5.5-style DN of the local user
583 * Constructs an ENTRYID value that can be used as a MAPI
584 * recipient (eg, for a message forwarding server-side rule),
585 * corresponding to the local user identified by @exchange_dn.
587 * Return value: the recipient ENTRYID
590 e2k_entryid_generate_local (const char *exchange_dn)
594 entryid = g_byte_array_new ();
596 e2k_rule_append_uint32 (entryid, 0);
597 g_byte_array_append (entryid, MAPI_LOCAL_UID, sizeof (MAPI_LOCAL_UID));
598 e2k_rule_append_uint16 (entryid, 1);
599 e2k_rule_append_uint16 (entryid, 0);
600 e2k_rule_append_string (entryid, exchange_dn);
605 static const char MAPI_CONTACT_UID[] = {
606 0xfe, 0x42, 0xaa, 0x0a, 0x18, 0xc7, 0x1a, 0x10,
607 0xe8, 0x85, 0x0b, 0x65, 0x1c, 0x24, 0x00, 0x00
611 * e2k_entryid_generate_contact:
612 * @contact_entryid: the #PR_ENTRYID of an item in the user's Contacts
614 * @nth_address: which of the contact's email addresses to use.
616 * Constructs an ENTRYID value that can be used as a MAPI recipient
617 * (eg, for a message forwarding server-side rule), corresponding to
618 * the Contacts folder entry identified by @contact_entryid.
620 * Return value: the recipient ENTRYID
623 e2k_entryid_generate_contact (GByteArray *contact_entryid, int nth_address)
627 entryid = g_byte_array_new ();
629 e2k_rule_append_uint32 (entryid, 0);
630 g_byte_array_append (entryid, MAPI_CONTACT_UID, sizeof (MAPI_CONTACT_UID));
631 e2k_rule_append_uint32 (entryid, 3);
632 e2k_rule_append_uint32 (entryid, 4);
633 e2k_rule_append_uint32 (entryid, nth_address);
634 e2k_rule_append_uint32 (entryid, contact_entryid->len);
635 g_byte_array_append (entryid, contact_entryid->data, contact_entryid->len);
641 * e2k_search_key_generate:
642 * @addrtype: the type of @address (usually "SMTP" or "EX")
643 * @address: the address data
645 * Constructs a PR_SEARCH_KEY value for @address
647 * Return value: the search key
650 e2k_search_key_generate (const char *addrtype, const char *address)
652 GByteArray *search_key;
655 search_key = g_byte_array_new ();
656 g_byte_array_append (search_key, addrtype, strlen (addrtype));
657 g_byte_array_append (search_key, ":", 1);
658 g_byte_array_append (search_key, address, strlen (address));
659 g_byte_array_append (search_key, "", 1);
661 for (p = search_key->data; *p; p++)
662 *p = g_ascii_toupper (*p);