Extending test-client-custom-summary to try e_book_client_get_contacts_uids()
[platform/upstream/evolution-data-server.git] / camel / camel-iconv.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * camel-iconv.c
4  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5  *
6  * Authors:
7  *   Michael Zucchi <notzed@ximian.com>
8  *   Jeffery Stedfast <fejj@ximian.com>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License, version 2, as published by the Free Software Foundation.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22  * 02111-1307, USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <errno.h>
33
34 #include <locale.h>
35
36 #ifdef HAVE_CODESET
37 #include <langinfo.h>
38 #endif
39
40 #include "camel-iconv.h"
41 #include "iconv-detect.h"
42
43 #define cd(x)
44
45 G_LOCK_DEFINE_STATIC (iconv);
46
47 struct _iconv_cache_node {
48         struct _iconv_cache *parent;
49
50         gint busy;
51         iconv_t ip;
52 };
53
54 struct _iconv_cache {
55         gchar *conv;
56         GQueue open;    /* stores iconv_cache_nodes, busy ones up front */
57 };
58
59 #define E_ICONV_CACHE_SIZE (16)
60
61 static GQueue iconv_cache_list = G_QUEUE_INIT;
62 static GHashTable *iconv_cache;
63 static GHashTable *iconv_cache_open;
64
65 static GHashTable *iconv_charsets = NULL;
66 static gchar *locale_charset = NULL;
67 static gchar *locale_lang = NULL;
68
69 struct {
70         const gchar *charset;
71         const gchar *iconv_name;
72 } known_iconv_charsets[] = {
73 #if 0
74         /* charset name, iconv-friendly charset name */
75         { "iso-8859-1",     "iso-8859-1" },
76         { "iso8859-1",      "iso-8859-1" },
77         /* the above mostly serves as an example for iso-style charsets,
78          * but we have code that will populate the iso-*'s if/when they
79          * show up in camel_iconv_charset_name () so I'm
80          * not going to bother putting them all in here... */
81         { "windows-cp1251", "cp1251"     },
82         { "windows-1251",   "cp1251"     },
83         { "cp1251",         "cp1251"     },
84         /* the above mostly serves as an example for windows-style
85          * charsets, but we have code that will parse and convert them
86          * to their cp#### equivalents if/when they show up in
87          * camel_iconv_charset_name () so I'm not going to bother
88          * putting them all in here either... */
89 #endif
90         /* charset name (lowercase!), iconv-friendly name (sometimes case sensitive) */
91         { "utf-8",          "UTF-8"      },
92
93         /* 10646 is a special case, its usually UCS-2 big endian */
94         /* This might need some checking but should be ok for solaris/linux */
95         { "iso-10646-1",    "UCS-2BE"    },
96         { "iso_10646-1",    "UCS-2BE"    },
97         { "iso10646-1",     "UCS-2BE"    },
98         { "iso-10646",      "UCS-2BE"    },
99         { "iso_10646",      "UCS-2BE"    },
100         { "iso10646",       "UCS-2BE"    },
101
102         { "ks_c_5601-1987", "EUC-KR"     },
103
104         /* FIXME: Japanese/Korean/Chinese stuff needs checking */
105         { "euckr-0",        "EUC-KR"     },
106         { "5601",           "EUC-KR"     },
107         { "zh_TW-euc",      "EUC-TW"     },
108         { "zh_CN.euc",      "gb18030"    },
109         { "zh_TW-big5",     "BIG5"       },
110         { "euc-cn",         "gb18030"    },
111         { "big5-0",         "BIG5"       },
112         { "big5.eten-0",    "BIG5"       },
113         { "big5hkscs-0",    "BIG5HKSCS"  },
114         { "gb2312-0",       "gb18030"    },
115         { "gb2312.1980-0",  "gb18030"    },
116         { "gb-2312",        "gb18030"    },
117         { "gb2312",         "gb18030"    },
118         { "gb18030-0",      "gb18030"    },
119         { "gbk-0",          "GBK"        },
120
121         { "eucjp-0",        "eucJP"      },
122         { "ujis-0",         "ujis"       },
123         { "jisx0208.1983-0","SJIS"       },
124         { "jisx0212.1990-0","SJIS"       },
125         { "pck",            "SJIS"       },
126         { NULL,             NULL         }
127 };
128
129 static const gchar *
130 e_strdown (gchar *str)
131 {
132         register gchar *s = str;
133
134         while (*s) {
135                 if (*s >= 'A' && *s <= 'Z')
136                         *s += 0x20;
137                 s++;
138         }
139
140         return str;
141 }
142
143 static const gchar *
144 e_strup (gchar *str)
145 {
146         register gchar *s = str;
147
148         while (*s) {
149                 if (*s >= 'a' && *s <= 'z')
150                         *s -= 0x20;
151                 s++;
152         }
153
154         return str;
155 }
156
157 static void
158 locale_parse_lang (const gchar *locale)
159 {
160         gchar *codeset, *lang;
161
162         if ((codeset = strchr (locale, '.')))
163                 lang = g_strndup (locale, codeset - locale);
164         else
165                 lang = g_strdup (locale);
166
167         /* validate the language */
168         if (strlen (lang) >= 2) {
169                 if (lang[2] == '-' || lang[2] == '_') {
170                         /* canonicalise the lang */
171                         e_strdown (lang);
172
173                         /* validate the country code */
174                         if (strlen (lang + 3) > 2) {
175                                 /* invalid country code */
176                                 lang[2] = '\0';
177                         } else {
178                                 lang[2] = '-';
179                                 e_strup (lang + 3);
180                         }
181                 } else if (lang[2] != '\0') {
182                         /* invalid language */
183                         g_free (lang);
184                         lang = NULL;
185                 }
186
187                 locale_lang = lang;
188         } else {
189                 /* invalid language */
190                 locale_lang = NULL;
191                 g_free (lang);
192         }
193 }
194
195 /* NOTE: Owns the lock on return if keep is TRUE !*/
196 static void
197 iconv_init (gint keep)
198 {
199         gchar *from, *to, *locale;
200         gint i;
201
202         G_LOCK (iconv);
203
204         if (iconv_charsets != NULL) {
205                 if (!keep)
206                         G_UNLOCK (iconv);
207                 return;
208         }
209
210         iconv_charsets = g_hash_table_new (g_str_hash, g_str_equal);
211
212         for (i = 0; known_iconv_charsets[i].charset != NULL; i++) {
213                 from = g_strdup (known_iconv_charsets[i].charset);
214                 to = g_strdup (known_iconv_charsets[i].iconv_name);
215                 e_strdown (from);
216                 g_hash_table_insert (iconv_charsets, from, to);
217         }
218
219         iconv_cache = g_hash_table_new (g_str_hash, g_str_equal);
220         iconv_cache_open = g_hash_table_new (NULL, NULL);
221
222 #ifndef G_OS_WIN32
223         locale = setlocale (LC_ALL, NULL);
224 #else
225         locale = g_win32_getlocale ();
226 #endif
227
228         if (!locale || !strcmp (locale, "C") || !strcmp (locale, "POSIX")) {
229                 /* The locale "C"  or  "POSIX"  is  a  portable  locale;  its
230                  * LC_CTYPE  part  corresponds  to  the 7-bit ASCII character
231                  * set.
232                  */
233
234                 locale_charset = NULL;
235                 locale_lang = NULL;
236         } else {
237 #ifdef G_OS_WIN32
238                 g_get_charset (&locale_charset);
239                 locale_charset = g_strdup (locale_charset);
240                 e_strdown (locale_charset);
241 #else
242 #ifdef HAVE_CODESET
243                 locale_charset = g_strdup (nl_langinfo (CODESET));
244                 e_strdown (locale_charset);
245 #else
246                 /* A locale name is typically of  the  form  language[_terri-
247                  * tory][.codeset][@modifier],  where  language is an ISO 639
248                  * language code, territory is an ISO 3166 country code,  and
249                  * codeset  is  a  character  set or encoding identifier like
250                  * ISO-8859-1 or UTF-8.
251                  */
252                 gchar *codeset, *p;
253
254                 codeset = strchr (locale, '.');
255                 if (codeset) {
256                         codeset++;
257
258                         /* ; is a hack for debian systems and / is a hack for Solaris systems */
259                         for (p = codeset; *p && !strchr ("@;/", *p); p++);
260                         locale_charset = g_strndup (codeset, p - codeset);
261                         e_strdown (locale_charset);
262                 } else {
263                         /* charset unknown */
264                         locale_charset = NULL;
265                 }
266 #endif
267 #endif  /* !G_OS_WIN32 */
268
269                 /* parse the locale lang */
270                 locale_parse_lang (locale);
271
272         }
273
274 #ifdef G_OS_WIN32
275         g_free (locale);
276 #endif
277         if (!keep)
278                 G_UNLOCK (iconv);
279 }
280
281 const gchar *
282 camel_iconv_charset_name (const gchar *charset)
283 {
284         gchar *name, *ret, *tmp;
285
286         if (charset == NULL)
287                 return NULL;
288
289         name = g_alloca (strlen (charset) + 1);
290         strcpy (name, charset);
291         e_strdown (name);
292
293         iconv_init (TRUE);
294         ret = g_hash_table_lookup (iconv_charsets, name);
295         if (ret != NULL) {
296                 G_UNLOCK (iconv);
297                 return ret;
298         }
299
300         /* Unknown, try canonicalise some basic charset types to something that should work */
301         if (strncmp (name, "iso", 3) == 0) {
302                 /* Convert iso-nnnn-n or isonnnn-n or iso_nnnn-n to iso-nnnn-n or isonnnn-n */
303                 gint iso, codepage;
304                 gchar *p;
305
306                 tmp = name + 3;
307                 if (*tmp == '-' || *tmp == '_')
308                         tmp++;
309
310                 iso = strtoul (tmp, &p, 10);
311
312                 if (iso == 10646) {
313                         /* they all become ICONV_10646 */
314                         ret = g_strdup (ICONV_10646);
315                 } else {
316                         tmp = p;
317                         if (*tmp == '-' || *tmp == '_')
318                                 tmp++;
319
320                         codepage = strtoul (tmp, &p, 10);
321
322                         if (p > tmp) {
323                                 /* codepage is numeric */
324 #ifdef __aix__
325                                 if (codepage == 13)
326                                         ret = g_strdup ("IBM-921");
327                                 else
328 #endif /* __aix__ */
329                                         ret = g_strdup_printf (ICONV_ISO_D_FORMAT, iso, codepage);
330                         } else {
331                                 /* codepage is a string - probably iso-2022-jp or something */
332                                 ret = g_strdup_printf (ICONV_ISO_S_FORMAT, iso, p);
333                         }
334                 }
335         } else if (strncmp (name, "windows-", 8) == 0) {
336                 /* Convert windows-nnnnn or windows-cpnnnnn to cpnnnn */
337                 tmp = name + 8;
338                 if (!strncmp (tmp, "cp", 2))
339                         tmp+=2;
340                 ret = g_strdup_printf ("CP%s", tmp);
341         } else if (strncmp (name, "microsoft-", 10) == 0) {
342                 /* Convert microsoft-nnnnn or microsoft-cpnnnnn to cpnnnn */
343                 tmp = name + 10;
344                 if (!strncmp (tmp, "cp", 2))
345                         tmp+=2;
346                 ret = g_strdup_printf ("CP%s", tmp);
347         } else {
348                 /* Just assume its ok enough as is, case and all */
349                 ret = g_strdup (charset);
350         }
351
352         g_hash_table_insert (iconv_charsets, g_strdup (name), ret);
353         G_UNLOCK (iconv);
354
355         return ret;
356 }
357
358 static void
359 flush_entry (struct _iconv_cache *ic)
360 {
361         struct _iconv_cache_node *in;
362
363         while ((in = g_queue_pop_head (&ic->open)) != NULL) {
364                 if (in->ip != (iconv_t) - 1) {
365                         g_hash_table_remove (iconv_cache_open, in->ip);
366                         iconv_close (in->ip);
367                 }
368                 g_free (in);
369         }
370
371         g_free (ic->conv);
372         g_free (ic);
373 }
374
375 /* This should run pretty quick, its called a lot */
376 iconv_t
377 camel_iconv_open (const gchar *oto,
378                   const gchar *ofrom)
379 {
380         const gchar *to, *from;
381         gchar *tofrom;
382         struct _iconv_cache *ic;
383         struct _iconv_cache_node *in;
384         gint errnosav;
385         iconv_t ip;
386
387         if (oto == NULL || ofrom == NULL) {
388                 errno = EINVAL;
389                 return (iconv_t) -1;
390         }
391
392         to = camel_iconv_charset_name (oto);
393         from = camel_iconv_charset_name (ofrom);
394         tofrom = g_alloca (strlen (to) + strlen (from) + 2);
395         sprintf (tofrom, "%s%%%s", to, from);
396
397         G_LOCK (iconv);
398
399         ic = g_hash_table_lookup (iconv_cache, tofrom);
400         if (ic) {
401                 g_queue_remove (&iconv_cache_list, ic);
402         } else {
403                 GList *link;
404
405                 link = g_queue_peek_tail_link (&iconv_cache_list);
406
407                 while (link != NULL && iconv_cache_list.length > E_ICONV_CACHE_SIZE) {
408                         GList *prev = g_list_previous (link);
409
410                         ic = (struct _iconv_cache *) link->data;
411                         in = g_queue_peek_head (&ic->open);
412
413                         if (in != NULL && !in->busy) {
414                                 cd (printf ("Flushing iconv converter '%s'\n", ic->conv));
415                                 g_queue_delete_link (&iconv_cache_list, link);
416                                 g_hash_table_remove (iconv_cache, ic->conv);
417                                 flush_entry (ic);
418                         }
419
420                         link = prev;
421                 }
422
423                 ic = g_malloc (sizeof (*ic));
424                 g_queue_init (&ic->open);
425                 ic->conv = g_strdup (tofrom);
426                 g_hash_table_insert (iconv_cache, ic->conv, ic);
427
428                 cd (printf ("Creating iconv converter '%s'\n", ic->conv));
429         }
430
431         g_queue_push_head (&iconv_cache_list, ic);
432
433         /* If we have a free iconv, use it */
434         in = g_queue_peek_tail (&ic->open);
435         if (in != NULL && !in->busy) {
436                 cd (printf ("using existing iconv converter '%s'\n", ic->conv));
437                 ip = in->ip;
438                 if (ip != (iconv_t) - 1) {
439                         /* work around some broken iconv implementations
440                          * that die if the length arguments are NULL
441                          */
442                         gsize buggy_iconv_len = 0;
443                         gchar *buggy_iconv_buf = NULL;
444
445                         /* resets the converter */
446                         iconv (ip, &buggy_iconv_buf, &buggy_iconv_len, &buggy_iconv_buf, &buggy_iconv_len);
447                         in->busy = TRUE;
448                         g_queue_remove (&ic->open, in);
449                         g_queue_push_head (&ic->open, in);
450                 }
451         } else {
452                 cd (printf ("creating new iconv converter '%s'\n", ic->conv));
453                 ip = iconv_open (to, from);
454                 in = g_malloc (sizeof (*in));
455                 in->ip = ip;
456                 in->parent = ic;
457                 g_queue_push_head (&ic->open, in);
458                 if (ip != (iconv_t) - 1) {
459                         g_hash_table_insert (iconv_cache_open, ip, in);
460                         in->busy = TRUE;
461                 } else {
462                         errnosav = errno;
463                         g_warning ("Could not open converter for '%s' to '%s' charset", from, to);
464                         in->busy = FALSE;
465                         errno = errnosav;
466                 }
467         }
468
469         G_UNLOCK (iconv);
470
471         return ip;
472 }
473
474 gsize
475 camel_iconv (iconv_t cd,
476              const gchar **inbuf,
477              gsize *inbytesleft,
478              gchar **outbuf,
479              gsize *outbytesleft)
480 {
481         return iconv (cd, (gchar **) inbuf, inbytesleft, outbuf, outbytesleft);
482 }
483
484 void
485 camel_iconv_close (iconv_t ip)
486 {
487         struct _iconv_cache_node *in;
488
489         if (ip == (iconv_t) - 1)
490                 return;
491
492         G_LOCK (iconv);
493         in = g_hash_table_lookup (iconv_cache_open, ip);
494         if (in) {
495                 cd (printf ("closing iconv converter '%s'\n", in->parent->conv));
496                 g_queue_remove (&in->parent->open, in);
497                 in->busy = FALSE;
498                 g_queue_push_tail (&in->parent->open, in);
499         } else {
500                 g_warning ("trying to close iconv i dont know about: %p", ip);
501                 iconv_close (ip);
502         }
503         G_UNLOCK (iconv);
504 }
505
506 const gchar *
507 camel_iconv_locale_charset (void)
508 {
509         iconv_init (FALSE);
510
511         return locale_charset;
512 }
513
514 const gchar *
515 camel_iconv_locale_language (void)
516 {
517         iconv_init (FALSE);
518
519         return locale_lang;
520 }
521
522 /* map CJKR charsets to their language code */
523 /* NOTE: only support charset names that will be returned by
524  * camel_iconv_charset_name() so that we don't have to keep track of all
525  * the aliases too. */
526 static struct {
527         const gchar *charset;
528         const gchar *lang;
529 } cjkr_lang_map[] = {
530         { "Big5",        "zh" },
531         { "BIG5HKSCS",   "zh" },
532         { "gb2312",      "zh" },
533         { "gb18030",     "zh" },
534         { "gbk",         "zh" },
535         { "euc-tw",      "zh" },
536         { "iso-2022-jp", "ja" },
537         { "sjis",        "ja" },
538         { "ujis",        "ja" },
539         { "eucJP",       "ja" },
540         { "euc-jp",      "ja" },
541         { "euc-kr",      "ko" },
542         { "koi8-r",      "ru" },
543         { "koi8-u",      "uk" }
544 };
545
546 const gchar *
547 camel_iconv_charset_language (const gchar *charset)
548 {
549         gint i;
550
551         if (!charset)
552                 return NULL;
553
554         charset = camel_iconv_charset_name (charset);
555         for (i = 0; i < G_N_ELEMENTS (cjkr_lang_map); i++) {
556                 if (!g_ascii_strcasecmp (cjkr_lang_map[i].charset, charset))
557                         return cjkr_lang_map[i].lang;
558         }
559
560         return NULL;
561 }