Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / servers / exchange / lib / e2k-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /* Copyright (C) 2001-2004 Novell, Inc.
4  *
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.
8  *
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.
13  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23
24 #include "e2k-utils.h"
25 #include "e2k-autoconfig.h"
26 #include "e2k-propnames.h"
27 #include "e2k-rule.h"
28
29 #include <libedataserver/e-time-utils.h>
30
31 #include <ctype.h>
32 #include <stdlib.h>
33 #include <string.h>
34
35 /* Do not internationalize */
36 const char *e2k_rfc822_months [] = {
37         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
38         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
39 };
40
41 /**
42  * e2k_parse_timestamp:
43  * @timestamp: an ISO8601 timestamp returned by the Exchange server
44  *
45  * Converts @timestamp to a %time_t value. @timestamp must be in one
46  * of the two ISO8601 variants used by Exchange.
47  *
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.
52  *
53  * Return value: the %time_t corresponding to @timestamp, or -1 on
54  * error.
55  **/
56 time_t
57 e2k_parse_timestamp (const char *timestamp)
58 {
59         struct tm tm;
60
61         tm.tm_year = strtoul (timestamp, (char **)&timestamp, 10) - 1900;
62         if (*timestamp++ != '-')
63                 return -1;
64         tm.tm_mon = strtoul (timestamp, (char **)&timestamp, 10) - 1;
65         if (*timestamp++ != '-')
66                 return -1;
67         tm.tm_mday = strtoul (timestamp, (char **)&timestamp, 10);
68         if (*timestamp++ != 'T')
69                 return -1;
70         tm.tm_hour = strtoul (timestamp, (char **)&timestamp, 10);
71         if (*timestamp++ != ':')
72                 return -1;
73         tm.tm_min = strtoul (timestamp, (char **)&timestamp, 10);
74         if (*timestamp++ != ':')
75                 return -1;
76         tm.tm_sec = strtoul (timestamp, (char **)&timestamp, 10);
77         if (*timestamp != '.' && *timestamp != 'Z')
78                 return -1;
79
80         return e_mktime_utc (&tm);
81 }
82
83 /**
84  * e2k_make_timestamp:
85  * @when: the %time_t to convert to an ISO8601 timestamp
86  *
87  * Creates an ISO8601 timestamp (in an format acceptable to Exchange)
88  * corresponding to @when.
89  *
90  * Return value: the timestamp, which the caller must free.
91  **/
92 char *
93 e2k_make_timestamp (time_t when)
94 {
95         struct tm *tm;
96
97         tm = gmtime (&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);
101 }
102
103 /**
104  * e2k_make_timestamp_rfc822:
105  * @when: the %time_t to convert to an RFC822 timestamp
106  *
107  * Creates an RFC822 Date header value corresponding to @when, in the
108  * locale timezone.
109  *
110  * Return value: the timestamp, which the caller must free.
111  **/
112 char *
113 e2k_make_timestamp_rfc822 (time_t when)
114 {
115         struct tm tm;
116         int offset;
117
118         e_localtime_with_offset (when, &tm, &offset);
119         offset = (offset / 3600) * 100 + (offset / 60) % 60;
120
121         return g_strdup_printf ("%02d %s %04d %02d:%02d:%02d %+05d",
122                                 tm.tm_mday, e2k_rfc822_months[tm.tm_mon],
123                                 tm.tm_year + 1900,
124                                 tm.tm_hour, tm.tm_min, tm.tm_sec,
125                                 offset);
126 }
127
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.
131  */
132 #define SYSTIME_OFFSET 194074560UL
133
134 /**
135  * e2k_systime_to_time_t:
136  * @systime: a MAPI PT_SYSTIME value (minutes since Windows epoch)
137  *
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).
140  *
141  * Return value: a %time_t corresponding to @systime.
142  **/
143 time_t
144 e2k_systime_to_time_t (guint32 systime)
145 {
146         return (systime - SYSTIME_OFFSET) * 60;
147 }
148
149 /**
150  * e2k_systime_from_time_t:
151  * @tt: a %time_t value
152  *
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
155  * boundary.
156  *
157  * Return value: the Windows systime value corresponding to @tt
158  **/
159 guint32
160 e2k_systime_from_time_t (time_t tt)
161 {
162         return (tt / 60) + SYSTIME_OFFSET;
163 }
164
165 /**
166  * e2k_filetime_to_time_t:
167  * @filetime: a Windows FILETIME value (100ns intervals since
168  * Windows epoch)
169  *
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.
173  *
174  * Return value: a %time_t corresponding to @filetime.
175  **/
176 time_t
177 e2k_filetime_to_time_t (guint64 filetime)
178 {
179         return (time_t)(filetime / 10000000 - SYSTIME_OFFSET * 60);
180 }
181
182 /**
183  * e2k_filetime_from_time_t:
184  * @tt: a %time_t value
185  *
186  * Converts the %time_t value @tt to a corresponding Windows FILETIME
187  * value.
188  *
189  * Return value: the Windows FILETIME value corresponding to @tt
190  **/
191 guint64
192 e2k_filetime_from_time_t (time_t tt)
193 {
194         return (((guint64)tt) + ((guint64)SYSTIME_OFFSET) * 60) * 10000000;
195 }
196
197 /**
198  * e2k_lf_to_crlf:
199  * @in: input text in UNIX ("\n") format
200  *
201  * Creates a copy of @in with all LFs converted to CRLFs.
202  *
203  * Return value: the converted text, which the caller must free.
204  **/
205 char *
206 e2k_lf_to_crlf (const char *in)
207 {
208         int len;
209         const char *s;
210         char *out, *d;
211
212         g_return_val_if_fail (in != NULL, NULL);
213
214         len = strlen (in);
215         for (s = strchr (in, '\n'); s; s = strchr (s + 1, '\n'))
216                 len++;
217
218         out = g_malloc (len + 1);
219         for (s = in, d = out; *s; s++) {
220                 if (*s == '\n')
221                         *d++ = '\r';
222                 *d++ = *s;
223         }
224         *d = '\0';
225
226         return out;
227 }
228
229 /**
230  * e2k_crlf_to_lf:
231  * @in: input text in network ("\r\n") format
232  *
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.)
235  *
236  * Return value: the converted text, which the caller must free.
237  **/
238 char *
239 e2k_crlf_to_lf (const char *in)
240 {
241         int len;
242         const char *s;
243         char *out;
244         GString *str;
245
246         g_return_val_if_fail (in != NULL, NULL);
247
248         str = g_string_new ("");
249
250         len = strlen (in);
251         for (s = in; *s; s++) {
252                 if (*s != '\r')
253                         str = g_string_append_c (str, *s);
254         }
255
256         out = str->str;
257         g_string_free (str, FALSE);
258
259         return out;
260 }
261
262 /**
263  * e2k_strdup_with_trailing_slash:
264  * @path: a URI or path
265  *
266  * Copies @path, appending a "/" to it if and only if it did not
267  * already end in "/".
268  *
269  * Return value: the path, which the caller must free
270  **/
271 char *
272 e2k_strdup_with_trailing_slash (const char *path)
273 {
274         char *p;
275
276         p = strrchr (path, '/');
277         if (p && !p[1])
278                 return g_strdup (path);
279         else
280                 return g_strdup_printf ("%s/", path);
281 }
282
283 /**
284  * e2k_entryid_to_dn:
285  * @entryid: an Exchange entryid
286  *
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).
289  *
290  * Return value: the entryid, which is a pointer into @entryid's data.
291  **/
292 const char *
293 e2k_entryid_to_dn (GByteArray *entryid)
294 {
295         char *p;
296
297         p = ((char *)entryid->data) + entryid->len - 1;
298         if (*p == 0) {
299                 while (*(p - 1) && p > (char *)entryid->data)
300                         p--;
301                 if (*p == '/')
302                         return p;
303         }
304         return NULL;
305 }
306
307 static void
308 append_permanenturl_section (GString *url, guint8 *entryid)
309 {
310         int i = 0;
311
312         /* First part */
313         while (i < 16)
314                 g_string_append_printf (url, "%02x", entryid[i++]);
315
316         /* Replace 0s with a single '-' */
317         g_string_append_c (url, '-');
318         while (i < 22 && entryid[i] == 0)
319                 i++;
320
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
324          * preceding '-'.)
325          */
326         if (i < 22 && entryid[i] < 0x10)
327                 g_string_append_printf (url, "%01x", entryid[i++]);
328         while (i < 22)
329                 g_string_append_printf (url, "%02x", entryid[i++]);
330 }
331
332 #define E2K_PERMANENTURL_INFIX "-FlatUrlSpace-"
333 #define E2K_PERMANENTURL_INFIX_LEN (sizeof (E2K_PERMANENTURL_INFIX) - 1)
334
335 /**
336  * e2k_entryid_to_permanenturl:
337  * @entryid: an ENTRYID (specifically, a PR_SOURCE_KEY)
338  * @base_uri: base URI of the store containing @entryid
339  *
340  * Creates a permanenturl based on @entryid and @base_uri.
341  *
342  * Return value: the permanenturl, which the caller must free.
343  **/
344 char *
345 e2k_entryid_to_permanenturl (GByteArray *entryid, const char *base_uri)
346 {
347         GString *url;
348         char *ret;
349
350         g_return_val_if_fail (entryid->len == 22 || entryid->len == 44, NULL);
351
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, '/');
357
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);
362         }
363
364         ret = url->str;
365         g_string_free (url, FALSE);
366         return ret;
367 }
368
369 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : g_ascii_tolower (c) - 'a' + 10)
370
371 static gboolean
372 append_entryid_section (GByteArray *entryid, const char **permanenturl)
373 {
374         const char *p;
375         char buf[44], byte;
376         int endlen;
377
378         p = *permanenturl;
379         if (strspn (p, "0123456789abcdefABCDEF") != 32)
380                 return FALSE;
381         if (p[32] != '-')
382                 return FALSE;
383         endlen = strspn (p + 33, "0123456789abcdefABCDEF");
384         if (endlen > 6)
385                 return FALSE;
386
387         /* Expand to the full form by replacing the "-" with "0"s */
388         memcpy (buf, p, 32);
389         memset (buf + 32, '0', sizeof (buf) - 32 - endlen);
390         memcpy (buf + sizeof (buf) - endlen, p + 33, endlen);
391
392         p = buf;
393         while (p < buf + sizeof (buf)) {
394                 byte = (HEXVAL (*p) << 4) + HEXVAL (*(p + 1));
395                 g_byte_array_append (entryid, &byte, 1);
396                 p += 2;
397         }
398
399         *permanenturl += 33 + endlen;
400         return TRUE;
401 }
402
403 /**
404  * e2k_permanenturl_to_entryid:
405  * @permanenturl: an Exchange permanenturl
406  *
407  * Creates an ENTRYID (specifically, a PR_SOURCE_KEY) based on
408  * @permanenturl
409  *
410  * Return value: the entryid
411  **/
412 GByteArray *
413 e2k_permanenturl_to_entryid (const char *permanenturl)
414 {
415         GByteArray *entryid;
416
417         permanenturl = strstr (permanenturl, E2K_PERMANENTURL_INFIX);
418         if (!permanenturl)
419                 return NULL;
420         permanenturl += E2K_PERMANENTURL_INFIX_LEN;
421
422         entryid = g_byte_array_new ();
423         while (*permanenturl++ == '/') {
424                 if (!append_entryid_section (entryid, &permanenturl)) {
425                         g_byte_array_free (entryid, TRUE);
426                         return NULL;
427                 }
428         }
429
430         return entryid;
431 }
432
433 /**
434  * e2k_ascii_strcase_equal
435  * @v: a string
436  * @v2: another string
437  *
438  * ASCII-case-insensitive comparison function for use with #GHashTable.
439  *
440  * Return value: %TRUE if @v and @v2 are ASCII-case-insensitively
441  * equal, %FALSE if not.
442  **/
443 gint
444 e2k_ascii_strcase_equal (gconstpointer v, gconstpointer v2)
445 {
446         return !g_ascii_strcasecmp (v, v2);
447 }
448
449 /**
450  * e2k_ascii_strcase_hash
451  * @v: a string
452  *
453  * ASCII-case-insensitive hash function for use with #GHashTable.
454  *
455  * Return value: An ASCII-case-insensitive hashing of @v.
456  **/
457 guint
458 e2k_ascii_strcase_hash (gconstpointer v)
459 {
460         /* case-insensitive g_str_hash */
461
462         const unsigned char *p = v;
463         guint h = g_ascii_tolower (*p);
464
465         if (h) {
466                 for (p += 1; *p != '\0'; p++)
467                         h = (h << 5) - h + g_ascii_tolower (*p);
468         }
469
470         return h;
471 }
472
473 /**
474  * e2k_restriction_folders_only:
475  * @rn: a restriction
476  *
477  * Examines @rn, and determines if it can only return folders
478  *
479  * Return value: %TRUE if @rn will cause only folders to be returned
480  **/
481 gboolean
482 e2k_restriction_folders_only (E2kRestriction *rn)
483 {
484         int i;
485
486         if (!rn)
487                 return FALSE;
488
489         switch (rn->type) {
490         case E2K_RESTRICTION_PROPERTY:
491                 if (strcmp (rn->res.property.pv.prop.name,
492                             E2K_PR_DAV_IS_COLLECTION) != 0)
493                         return FALSE;
494
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);
498
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]))
502                                 return TRUE;
503                 }
504                 return FALSE;
505
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]))
509                                 return FALSE;
510                 }
511                 return TRUE;
512
513         case E2K_RESTRICTION_NOT:
514                 return !e2k_restriction_folders_only (rn->res.not.rn);
515
516         case E2K_RESTRICTION_COMMENT:
517                 return e2k_restriction_folders_only (rn->res.comment.rn);
518
519         default:
520                 return FALSE;
521         }
522 }
523
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
528 };
529 #define MAPI_ONE_OFF_UNICODE      0x8000
530 #define MAPI_ONE_OFF_NO_RICH_INFO 0x0001
531 #define MAPI_ONE_OFF_MYSTERY_FLAG 0x1000
532
533 /**
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.
539  *
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.
543  *
544  * Return value: the recipient ENTRYID
545  **/
546 GByteArray *
547 e2k_entryid_generate_oneoff (const char *display_name, const char *email, gboolean unicode)
548 {
549         GByteArray *entryid;
550
551         entryid = g_byte_array_new ();
552
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));
560
561         if (unicode) {
562                 e2k_rule_append_unicode (entryid, display_name);
563                 e2k_rule_append_unicode (entryid, "SMTP");
564                 e2k_rule_append_unicode (entryid, email);
565         } else {
566                 e2k_rule_append_string (entryid, display_name);
567                 e2k_rule_append_string (entryid, "SMTP");
568                 e2k_rule_append_string (entryid, email);
569         }
570
571         return entryid;
572 }
573
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
577 };
578
579 /**
580  * e2k_entryid_generate_local:
581  * @exchange_dn: the Exchange 5.5-style DN of the local user
582  *
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.
586  *
587  * Return value: the recipient ENTRYID
588  **/
589 GByteArray *
590 e2k_entryid_generate_local (const char *exchange_dn)
591 {
592         GByteArray *entryid;
593
594         entryid = g_byte_array_new ();
595
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);
601
602         return entryid;
603 }
604
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
608 };
609
610 /**
611  * e2k_entryid_generate_contact:
612  * @contact_entryid: the #PR_ENTRYID of an item in the user's Contacts
613  * folder.
614  * @nth_address: which of the contact's email addresses to use.
615  *
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.
619  *
620  * Return value: the recipient ENTRYID
621  **/
622 GByteArray *
623 e2k_entryid_generate_contact (GByteArray *contact_entryid, int nth_address)
624 {
625         GByteArray *entryid;
626
627         entryid = g_byte_array_new ();
628
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);
636
637         return entryid;
638 }
639
640 /**
641  * e2k_search_key_generate:
642  * @addrtype: the type of @address (usually "SMTP" or "EX")
643  * @address: the address data
644  *
645  * Constructs a PR_SEARCH_KEY value for @address
646  *
647  * Return value: the search key
648  **/
649 GByteArray *
650 e2k_search_key_generate (const char *addrtype, const char *address)
651 {
652         GByteArray *search_key;
653         guint8 *p;
654
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);
660
661         for (p = search_key->data; *p; p++)
662                 *p = g_ascii_toupper (*p);
663
664         return search_key;
665 }