Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / camel-internet-address.c
1 /*
2  *  Copyright (C) 2000 Ximian Inc.
3  *
4  *  Authors: Michael Zucchi <notzed@ximian.com>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of version 2 of the GNU Lesser General Public
8  * License as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #include <stdio.h>
22 #include <string.h>
23
24 #include "camel-internet-address.h"
25 #include "camel-mime-utils.h"
26
27 #define d(x)
28
29 static int    internet_decode           (CamelAddress *, const char *raw);
30 static char * internet_encode           (CamelAddress *);
31 static int    internet_unformat         (CamelAddress *, const char *raw);
32 static char * internet_format           (CamelAddress *);
33 static int    internet_cat              (CamelAddress *dest, const CamelAddress *source);
34 static void   internet_remove           (CamelAddress *, int index);
35
36 static void camel_internet_address_class_init (CamelInternetAddressClass *klass);
37 static void camel_internet_address_init       (CamelInternetAddress *obj);
38
39 static CamelAddressClass *camel_internet_address_parent;
40
41 struct _address {
42         char *name;
43         char *address;
44 };
45
46 static void
47 camel_internet_address_class_init(CamelInternetAddressClass *klass)
48 {
49         CamelAddressClass *address = (CamelAddressClass *) klass;
50
51         camel_internet_address_parent = CAMEL_ADDRESS_CLASS(camel_type_get_global_classfuncs(camel_address_get_type()));
52
53         address->decode = internet_decode;
54         address->encode = internet_encode;
55         address->unformat = internet_unformat;
56         address->format = internet_format;
57         address->remove = internet_remove;
58         address->cat = internet_cat;
59 }
60
61 static void
62 camel_internet_address_init(CamelInternetAddress *obj)
63 {
64 }
65
66 CamelType
67 camel_internet_address_get_type(void)
68 {
69         static CamelType type = CAMEL_INVALID_TYPE;
70         
71         if (type == CAMEL_INVALID_TYPE) {
72                 type = camel_type_register(camel_address_get_type(), "CamelInternetAddress",
73                                            sizeof (CamelInternetAddress),
74                                            sizeof (CamelInternetAddressClass),
75                                            (CamelObjectClassInitFunc) camel_internet_address_class_init,
76                                            NULL,
77                                            (CamelObjectInitFunc) camel_internet_address_init,
78                                            NULL);
79         }
80         
81         return type;
82 }
83
84 static int
85 internet_decode (CamelAddress *a, const char *raw)
86 {
87         struct _camel_header_address *ha, *n;
88         int count = a->addresses->len;
89
90         /* Should probably use its own decoder or something */
91         ha = camel_header_address_decode(raw, NULL);
92         if (ha) {
93                 n = ha;
94                 while (n) {
95                         if (n->type == CAMEL_HEADER_ADDRESS_NAME) {
96                                 camel_internet_address_add((CamelInternetAddress *)a, n->name, n->v.addr);
97                         } else if (n->type == CAMEL_HEADER_ADDRESS_GROUP) {
98                                 struct _camel_header_address *g = n->v.members;
99                                 while (g) {
100                                         if (g->type == CAMEL_HEADER_ADDRESS_NAME)
101                                                 camel_internet_address_add((CamelInternetAddress *)a, g->name, g->v.addr);
102                                         /* otherwise, it's an error, infact */
103                                         g = g->next;
104                                 }
105                         }
106                         n = n->next;
107                 }
108                 camel_header_address_list_clear(&ha);
109         }
110         
111         return a->addresses->len - count;
112 }
113
114 static char *
115 internet_encode (CamelAddress *a)
116 {
117         int i;
118         GString *out;
119         char *ret;
120         int len = 6;            /* "From: ", assume longer of the address headers */
121
122         if (a->addresses->len == 0)
123                 return NULL;
124         
125         out = g_string_new("");
126         
127         for (i = 0;i < a->addresses->len; i++) {
128                 struct _address *addr = g_ptr_array_index(a->addresses, i);
129                 char *enc;
130
131                 if (i != 0)
132                         g_string_append(out, ", ");
133
134                 enc = camel_internet_address_encode_address(&len, addr->name, addr->address);
135                 g_string_append(out, enc);
136                 g_free(enc);
137         }
138         
139         ret = out->str;
140         g_string_free(out, FALSE);
141         
142         return ret;
143 }
144
145 static int
146 internet_unformat(CamelAddress *a, const char *raw)
147 {
148         char *buffer, *p, *name, *addr;
149         int c;
150         int count = a->addresses->len;
151
152         if (raw == NULL)
153                 return 0;
154
155         d(printf("unformatting address: %s\n", raw));
156
157         /* we copy, so we can modify as we go */
158         buffer = g_strdup(raw);
159
160         /* this can be simpler than decode, since there are much fewer rules */
161         p = buffer;
162         name = NULL;
163         addr = p;
164         do {
165                 c = (unsigned char)*p++;
166                 switch (c) {
167                         /* removes quotes, they should only be around the total name anyway */
168                 case '"':
169                         p[-1] = ' ';
170                         while (*p)
171                                 if (*p == '"') {
172                                         *p++ = ' ';
173                                         break;
174                                 } else {
175                                         p++;
176                                 }
177                         break;
178                 case '<':
179                         if (name == NULL)
180                                 name = addr;
181                         addr = p;
182                         addr[-1] = 0;
183                         while (*p && *p != '>')
184                                 p++;
185                         if (*p == 0)
186                                 break;
187                         p++;
188                         /* falls through */
189                 case ',':
190                         p[-1] = 0;
191                         /* falls through */
192                 case 0:
193                         if (name)
194                                 name = g_strstrip(name);
195                         addr = g_strstrip(addr);
196                         if (addr[0]) {
197                                 d(printf("found address: '%s' <%s>\n", name, addr));
198                                 camel_internet_address_add((CamelInternetAddress *)a, name, addr);
199                         }
200                         name = NULL;
201                         addr = p;
202                         break;
203                 }
204         } while (c);
205
206         g_free(buffer);
207
208         return a->addresses->len - count;
209 }
210
211 static char *
212 internet_format (CamelAddress *a)
213 {
214         int i;
215         GString *out;
216         char *ret;
217         
218         if (a->addresses->len == 0)
219                 return NULL;
220         
221         out = g_string_new("");
222         
223         for (i = 0;i < a->addresses->len; i++) {
224                 struct _address *addr = g_ptr_array_index(a->addresses, i);
225                 char *enc;
226
227                 if (i != 0)
228                         g_string_append(out, ", ");
229
230                 enc = camel_internet_address_format_address(addr->name, addr->address);
231                 g_string_append(out, enc);
232                 g_free(enc);
233         }
234         
235         ret = out->str;
236         g_string_free(out, FALSE);
237         
238         return ret;
239 }
240
241 static int
242 internet_cat (CamelAddress *dest, const CamelAddress *source)
243 {
244         int i;
245
246         g_assert(CAMEL_IS_INTERNET_ADDRESS(source));
247
248         for (i=0;i<source->addresses->len;i++) {
249                 struct _address *addr = g_ptr_array_index(source->addresses, i);
250                 camel_internet_address_add((CamelInternetAddress *)dest, addr->name, addr->address);
251         }
252
253         return i;
254 }
255
256 static void
257 internet_remove (CamelAddress *a, int index)
258 {
259         struct _address *addr;
260         
261         if (index < 0 || index >= a->addresses->len)
262                 return;
263         
264         addr = g_ptr_array_index(a->addresses, index);
265         g_free(addr->name);
266         g_free(addr->address);
267         g_free(addr);
268         g_ptr_array_remove_index(a->addresses, index);
269 }
270
271
272 /**
273  * camel_internet_address_new:
274  *
275  * Create a new #CamelInternetAddress object.
276  * 
277  * Returns a new #CamelInternetAddress object
278  **/
279 CamelInternetAddress *
280 camel_internet_address_new (void)
281 {
282         CamelInternetAddress *new = CAMEL_INTERNET_ADDRESS(camel_object_new(camel_internet_address_get_type()));
283         return new;
284 }
285
286
287 /**
288  * camel_internet_address_add:
289  * @addr: a #CamelInternetAddress object
290  * @name: name associated with the new address
291  * @address: routing address associated with the new address
292  * 
293  * Add a new internet address to @addr.
294  * 
295  * Returns the index of added entry
296  **/
297 int
298 camel_internet_address_add (CamelInternetAddress *addr, const char *name, const char *address)
299 {
300         struct _address *new;
301         int index;
302
303         g_assert(CAMEL_IS_INTERNET_ADDRESS(addr));
304
305         new = g_malloc(sizeof(*new));
306         new->name = g_strdup(name);
307         new->address = g_strdup(address);
308         index = ((CamelAddress *)addr)->addresses->len;
309         g_ptr_array_add(((CamelAddress *)addr)->addresses, new);
310
311         return index;
312 }
313
314
315 /**
316  * camel_internet_address_get:
317  * @addr: a #CamelInternetAddress object
318  * @index: address's array index
319  * @namep: holder for the returned name, or %NULL, if not required.
320  * @addressp: holder for the returned address, or %NULL, if not required.
321  * 
322  * Get the address at @index.
323  * 
324  * Returns %TRUE if such an address exists, or %FALSE otherwise
325  **/
326 gboolean
327 camel_internet_address_get (const CamelInternetAddress *addr, int index, const char **namep, const char **addressp)
328 {
329         struct _address *a;
330
331         g_assert(CAMEL_IS_INTERNET_ADDRESS(addr));
332
333         if (index < 0 || index >= ((CamelAddress *)addr)->addresses->len)
334                 return FALSE;
335
336         a = g_ptr_array_index (((CamelAddress *)addr)->addresses, index);
337         if (namep)
338                 *namep = a->name;
339         if (addressp)
340                 *addressp = a->address;
341         return TRUE;
342 }
343
344
345 /**
346  * camel_internet_address_find_name:
347  * @addr: a #CamelInternetAddress object
348  * @name: name to lookup
349  * @addressp: holder for address part, or %NULL, if not required.
350  * 
351  * Find address by real name.
352  * 
353  * Returns the index of the address matching the name, or %-1 if no
354  * match was found
355  **/
356 int
357 camel_internet_address_find_name(CamelInternetAddress *addr, const char *name, const char **addressp)
358 {
359         struct _address *a;
360         int i, len;
361
362         g_assert(CAMEL_IS_INTERNET_ADDRESS(addr));
363
364         len = ((CamelAddress *)addr)->addresses->len;
365         for (i=0;i<len;i++) {
366                 a = g_ptr_array_index (((CamelAddress *)addr)->addresses, i);
367                 if (a->name && !strcmp(a->name, name)) {
368                         if (addressp)
369                                 *addressp = a->address;
370                         return i;
371                 }
372         }
373         return -1;
374 }
375
376
377 /**
378  * camel_internet_address_find_address:
379  * @addr: a #CamelInternetAddress object
380  * @address: address to lookup
381  * @namep: holder for the matching name, or %NULL, if not required.
382  * 
383  * Find an address by address.
384  * 
385  * Returns the index of the address, or %-1 if not found
386  **/
387 int
388 camel_internet_address_find_address(CamelInternetAddress *addr, const char *address, const char **namep)
389 {
390         struct _address *a;
391         int i, len;
392
393         g_assert(CAMEL_IS_INTERNET_ADDRESS(addr));
394
395         len = ((CamelAddress *)addr)->addresses->len;
396         for (i=0;i<len;i++) {
397                 a = g_ptr_array_index (((CamelAddress *)addr)->addresses, i);
398                 if (!strcmp(a->address, address)) {
399                         if (namep)
400                                 *namep = a->name;
401                         return i;
402                 }
403         }
404         return -1;
405 }
406
407 static void
408 cia_encode_addrspec(GString *out, const char *addr)
409 {
410         const char *at, *p;
411
412         at = strchr(addr, '@');
413         if (at == NULL)
414                 goto append;
415
416         p = addr;
417         while (p < at) {
418                 char c = *p++;
419
420                 /* strictly by rfc, we should split local parts on dots.
421                    however i think 2822 changes this, and not many clients grok it, so
422                    just quote the whole local part if need be */
423                 if (!(camel_mime_is_atom(c) || c=='.')) {
424                         g_string_append_c(out, '"');
425
426                         p = addr;
427                         while (p < at) {
428                                 c = *p++;
429                                 if (c == '"' || c == '\\')
430                                         g_string_append_c(out, '\\');
431                                 g_string_append_c(out, c);
432                         }
433                         g_string_append_c(out, '"');
434                         g_string_append(out, p);
435
436                         return;
437                 }
438         }
439
440 append:
441         g_string_append(out, addr);
442 }
443
444
445 /**
446  * camel_internet_address_encode_address:
447  * @len: the length of the line the address is being appended to
448  * @name: the unencoded real name associated with the address
449  * @addr: the routing address
450  * 
451  * Encode a single address ready for internet usage.  Header folding
452  * as per rfc822 is also performed, based on the length *@len.  If @len
453  * is %NULL, then no folding will occur.
454  *
455  * Note: The value at *@in will be updated based on any linewrapping done
456  * 
457  * Returns the encoded address
458  **/
459 char *
460 camel_internet_address_encode_address(int *inlen, const char *real, const char *addr)
461 {
462         char *name = camel_header_encode_phrase ((const unsigned char *) real);
463         char *ret = NULL;
464         int len = 0;
465         GString *out = g_string_new("");
466
467         g_assert(addr);
468
469         if (inlen != NULL)
470                 len = *inlen;
471
472         if (name && name[0]) {
473                 if (inlen != NULL && (strlen(name) + len) > CAMEL_FOLD_SIZE) {
474                         char *folded = camel_header_address_fold(name, len);
475                         char *last;
476                         g_string_append(out, folded);
477                         g_free(folded);
478                         last = strrchr(out->str, '\n');
479                         if (last)
480                                 len = last-(out->str+out->len);
481                         else
482                                 len = out->len;
483                 } else {
484                         g_string_append(out, name);
485                         len += strlen(name);
486                 }
487         }
488
489         /* NOTE: Strictly speaking, we could and should split the
490          * internal address up if we need to, on atom or specials
491          * boundaries - however, to aid interoperability with mailers
492          * that will probably not handle this case, we will just move
493          * the whole address to its own line. */
494         if (inlen != NULL && (strlen(addr) + len) > CAMEL_FOLD_SIZE) {
495                 g_string_append(out, "\n\t");
496                 len = 1;
497         }
498
499         len -= out->len;
500
501         if (name && name[0])
502                 g_string_append_printf(out, " <");
503         cia_encode_addrspec(out, addr);
504         if (name && name[0])
505                 g_string_append_printf(out, ">");
506
507         len += out->len;
508         
509         if (inlen != NULL)
510                 *inlen = len;
511
512         g_free(name);
513         
514         ret = out->str;
515         g_string_free(out, FALSE);
516
517         return ret;
518 }
519
520
521 /**
522  * camel_internet_address_format_address:
523  * @name: a name, quotes may be stripped from it
524  * @addr: an rfc822 routing address
525  * 
526  * Function to format a single address, suitable for display.
527  * 
528  * Returns a nicely formatted string containing the rfc822 address
529  **/
530 char *
531 camel_internet_address_format_address(const char *name, const char *addr)
532 {
533         char *ret = NULL;
534
535         g_assert(addr);
536
537         if (name && name[0]) {
538                 const char *p = name;
539                 char *o, c;
540
541                 while ((c = *p++)) {
542                         if (c == '\"' || c == ',') {
543                                 o = ret = g_malloc(strlen(name)+3+strlen(addr)+3 + 1);
544                                 p = name;
545                                 *o++ = '\"';
546                                 while ((c = *p++))
547                                         if (c != '\"')
548                                                 *o++ = c;
549                                 *o++ = '\"';
550                                 sprintf(o, " <%s>", addr);
551                                 d(printf("encoded '%s' => '%s'\n", name, ret));
552                                 return ret;
553                         }
554                 }
555                 ret = g_strdup_printf("%s <%s>", name, addr);
556         } else
557                 ret = g_strdup(addr);
558
559         return ret;
560 }