1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * Copyright (C) 2000-2012 Jeffrey Stedfast
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; either version 2.1
8 * of the License, or (at your option) any later version.
10 * This library 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 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free
17 * Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
31 #include "gmime-charset.h"
32 #include "gmime-iconv.h"
35 #ifdef ENABLE_WARNINGS
39 #endif /* ENABLE_WARNINGS */
43 * SECTION: gmime-iconv
45 * @short_description: Low-level routines for converting text from one charset to another
48 * These functions are wrappers around the system iconv(3)
49 * routines. The purpose of these wrappers are two-fold:
51 * 1. Cache iconv_t descriptors for you in order to optimize
52 * opening/closing many descriptors frequently
56 * 2. To use the appropriate system charset alias for the MIME charset
57 * names given as arguments.
61 #define ICONV_CACHE_SIZE (16)
65 guint32 refcount : 31;
71 static Cache *iconv_cache = NULL;
72 static GHashTable *iconv_open_hash = NULL;
74 #ifdef GMIME_ICONV_DEBUG
75 static int cache_misses = 0;
76 static int shutdown = 0;
80 #endif /* GMIME_ICONV_DEBUG */
82 #ifdef G_THREADS_ENABLED
83 static GStaticMutex iconv_cache_lock = G_STATIC_MUTEX_INIT;
84 #define ICONV_CACHE_LOCK() g_static_mutex_lock (&iconv_cache_lock)
85 #define ICONV_CACHE_UNLOCK() g_static_mutex_unlock (&iconv_cache_lock)
87 #define ICONV_CACHE_LOCK()
88 #define ICONV_CACHE_UNLOCK()
89 #endif /* G_THREADS_ENABLED */
92 /* caller *must* hold the iconv_cache_lock to call any of the following functions */
96 * iconv_cache_node_new:
98 * @cd: iconv descriptor
100 * Creates a new cache node, inserts it into the cache and increments
103 * Returns: a pointer to the newly allocated cache node.
105 static IconvCacheNode *
106 iconv_cache_node_new (const char *key, iconv_t cd)
108 IconvCacheNode *node;
110 #ifdef GMIME_ICONV_DEBUG
114 node = (IconvCacheNode *) cache_node_insert (iconv_cache, key);
124 iconv_cache_node_free (CacheNode *node)
126 IconvCacheNode *inode = (IconvCacheNode *) node;
128 #ifdef GMIME_ICONV_DEBUG
130 fprintf (stderr, "%s: open=%d; used=%s\n", node->key,
131 inode->refcount, inode->used ? "yes" : "no");
135 iconv_close (inode->cd);
140 * iconv_cache_node_expire:
143 * Decides whether or not a cache node should be expired.
146 iconv_cache_node_expire (Cache *cache, CacheNode *node)
148 IconvCacheNode *inode = (IconvCacheNode *) node;
150 if (inode->refcount == 0)
158 iconv_open_node_free (gpointer key, gpointer value, gpointer user_data)
160 iconv_t cd = (iconv_t) key;
161 IconvCacheNode *node;
163 node = (IconvCacheNode *) cache_node_lookup (iconv_cache, value, FALSE);
166 if (cd != node->cd) {
174 * g_mime_iconv_shutdown:
176 * Frees internal iconv caches created in g_mime_iconv_init().
178 * Note: this function is called for you by g_mime_shutdown().
181 g_mime_iconv_shutdown (void)
186 #ifdef GMIME_ICONV_DEBUG
187 fprintf (stderr, "There were %d iconv cache misses\n", cache_misses);
188 fprintf (stderr, "The following %d iconv cache buckets are still open:\n", iconv_cache->size);
192 g_hash_table_foreach (iconv_open_hash, iconv_open_node_free, NULL);
193 g_hash_table_destroy (iconv_open_hash);
194 iconv_open_hash = NULL;
196 cache_free (iconv_cache);
204 * Initialize GMime's iconv cache. This *MUST* be called before any
205 * gmime-iconv interfaces will work correctly.
207 * Note: this function is called for you by g_mime_init().
210 g_mime_iconv_init (void)
215 g_mime_charset_map_init ();
217 iconv_open_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
218 iconv_cache = cache_new (iconv_cache_node_expire, iconv_cache_node_free,
219 sizeof (IconvCacheNode), ICONV_CACHE_SIZE);
225 * @to: charset to convert to
226 * @from: charset to convert from
228 * Allocates a coversion descriptor suitable for converting byte
229 * sequences from charset @from to charset @to. The resulting
230 * descriptor can be used with iconv() (or the g_mime_iconv() wrapper) any
231 * number of times until closed using g_mime_iconv_close().
233 * See the manual page for iconv_open(3) for further details.
235 * Returns: a new conversion descriptor for use with g_mime_iconv() on
236 * success or (iconv_t) %-1 on fail as well as setting an appropriate
240 g_mime_iconv_open (const char *to, const char *from)
242 IconvCacheNode *node;
246 if (from == NULL || to == NULL) {
251 if (!g_ascii_strcasecmp (from, "x-unknown"))
252 from = g_mime_locale_charset ();
254 from = g_mime_charset_iconv_name (from);
255 to = g_mime_charset_iconv_name (to);
256 key = g_alloca (strlen (from) + strlen (to) + 2);
257 sprintf (key, "%s:%s", from, to);
261 if ((node = (IconvCacheNode *) cache_node_lookup (iconv_cache, key, TRUE))) {
263 if ((cd = iconv_open (to, from)) == (iconv_t) -1)
266 /* Apparently iconv on Solaris <= 7 segfaults if you pass in
267 * NULL for anything but inbuf; work around that. (NULL outbuf
268 * or NULL *outbuf is allowed by Unix98.)
270 size_t inleft = 0, outleft = 0;
276 /* reset the descriptor */
277 iconv (cd, NULL, &inleft, &outbuf, &outleft);
282 if ((cd = iconv_open (to, from)) == (iconv_t) -1)
285 node = iconv_cache_node_new (key, cd);
288 g_hash_table_insert (iconv_open_hash, cd, ((CacheNode *) node)->key);
290 ICONV_CACHE_UNLOCK ();
296 ICONV_CACHE_UNLOCK ();
300 g_warning ("Conversion from '%s' to '%s' is not supported", from, to);
302 g_warning ("Could not open converter from '%s' to '%s': %s",
303 from, to, strerror (errno));
311 * g_mime_iconv_close:
312 * @cd: iconv conversion descriptor
314 * Closes the iconv descriptor @cd.
316 * See the manual page for iconv_close(3) for further details.
318 * Returns: %0 on success or %-1 on fail as well as setting an
319 * appropriate errno value.
322 g_mime_iconv_close (iconv_t cd)
324 IconvCacheNode *node;
327 if (cd == (iconv_t) -1)
332 if ((key = g_hash_table_lookup (iconv_open_hash, cd))) {
333 g_hash_table_remove (iconv_open_hash, cd);
335 node = (IconvCacheNode *) cache_node_lookup (iconv_cache, key, FALSE);
338 if (iconv_cache->size > ICONV_CACHE_SIZE) {
339 /* expire before unreffing this node so that it wont get uncached */
340 cache_expire_unused (iconv_cache);
350 ICONV_CACHE_UNLOCK ();
352 d(g_warning ("This iconv context wasn't opened using g_mime_iconv_open()"));
354 return iconv_close (cd);
357 ICONV_CACHE_UNLOCK ();