Make the striconveha module actually work.
[gnulib.git] / lib / striconveha.c
1 /* Character set conversion with error handling and autodetection.
2    Copyright (C) 2002, 2005, 2007 Free Software Foundation, Inc.
3    Written by Bruno Haible.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
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
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19 #include <config.h>
20
21 /* Specification.  */
22 #include "striconveha.h"
23
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #define SIZEOF(a) (sizeof(a)/sizeof(a[0]))
29
30
31 /* Autodetection list.  */
32
33 struct autodetect_alias
34 {
35   struct autodetect_alias *next;
36   const char *name;
37   const char * const *encodings_to_try;
38 };
39
40 static const char * const autodetect_utf8_try[] =
41 {
42   /* Try UTF-8 first. There are very few ISO-8859-1 inputs that would
43      be valid UTF-8, but many UTF-8 inputs are valid ISO-8859-1.  */
44   "UTF-8", "ISO-8859-1",
45   NULL
46 };
47 static const char * const autodetect_jp_try[] =
48 {
49   /* Try 7-bit encoding first. If the input contains bytes >= 0x80,
50      it will fail.
51      Try EUC-JP next. Short SHIFT_JIS inputs may come out wrong. This
52      is unavoidable. People will condemn SHIFT_JIS.
53      If we tried SHIFT_JIS first, then some short EUC-JP inputs would
54      come out wrong, and people would condemn EUC-JP and Unix, which
55      would not be good.
56      Finally try SHIFT_JIS.  */
57   "ISO-2022-JP-2", "EUC-JP", "SHIFT_JIS",
58   NULL
59 };
60 static const char * const autodetect_kr_try[] =
61 {
62   /* Try 7-bit encoding first. If the input contains bytes >= 0x80,
63      it will fail.
64      Finally try EUC-KR.  */
65   "ISO-2022-KR", "EUC-KR",
66   NULL
67 };
68
69 static struct autodetect_alias autodetect_predefined[] =
70 {
71   { &autodetect_predefined[1], "autodetect_utf8", autodetect_utf8_try },
72   { &autodetect_predefined[2], "autodetect_jp",   autodetect_jp_try },
73   { NULL,                      "autodetect_kr",   autodetect_kr_try }
74 };
75
76 static struct autodetect_alias *autodetect_list = &autodetect_predefined[0];
77 static struct autodetect_alias **autodetect_list_end =
78   &autodetect_predefined[SIZEOF(autodetect_predefined)-1].next;
79
80 int
81 uniconv_register_autodetect (const char *name,
82                              const char * const *try_in_order)
83 {
84   size_t namelen;
85   size_t listlen;
86   size_t memneed;
87   size_t i;
88   char *memory;
89   struct autodetect_alias *new_alias;
90   char *new_name;
91   const char **new_try_in_order;
92
93   /* The TRY_IN_ORDER list must not be empty.  */
94   if (try_in_order[0] == NULL)
95     {
96       errno = EINVAL;
97       return -1;
98     }
99
100   /* We must deep-copy NAME and TRY_IN_ORDER, because they may be allocated
101      with dynamic extent.  */
102   namelen = strlen (name) + 1;
103   memneed = sizeof (struct autodetect_alias) + namelen + sizeof (char *);
104   for (i = 0; try_in_order[i] != NULL; i++)
105     memneed += sizeof (char *) + strlen (try_in_order[i]) + 1;
106   listlen = i;
107
108   memory = (char *) malloc (memneed);
109   if (memory != NULL)
110     {
111       new_alias = (struct autodetect_alias *) memory;
112       memory += sizeof (struct autodetect_alias);
113
114       new_try_in_order = (const char **) memory;
115       memory += (listlen + 1) * sizeof (char *);
116
117       new_name = (char *) memory;
118       memcpy (new_name, name, namelen);
119       memory += namelen;
120
121       for (i = 0; i < listlen; i++)
122         {
123           size_t len = strlen (try_in_order[i]) + 1;
124           memcpy (memory, try_in_order[i], len);
125           new_try_in_order[i] = (const char *) memory;
126           memory += len;
127         }
128       new_try_in_order[i] = NULL;
129
130       /* Now insert the new alias.  */
131       new_alias->name = new_name;
132       new_alias->encodings_to_try = new_try_in_order;
133       new_alias->next = NULL;
134       /* FIXME: Not multithread-safe.  */
135       *autodetect_list_end = new_alias;
136       autodetect_list_end = &new_alias->next;
137       return 0;
138     }
139   else
140     {
141       errno = ENOMEM;
142       return -1;
143     }
144 }
145
146 int
147 mem_iconveha (const char *src, size_t srclen,
148               const char *from_codeset, const char *to_codeset,
149               enum iconv_ilseq_handler handler,
150               size_t *offsets,
151               char **resultp, size_t *lengthp)
152 {
153   int retval = mem_iconveh (src, srclen, from_codeset, to_codeset, handler,
154                             offsets, resultp, lengthp);
155   if (retval >= 0 || errno != EINVAL)
156     return retval;
157   else
158     {
159       struct autodetect_alias *alias;
160
161       /* Unsupported from_codeset or to_codeset. Check whether the caller
162          requested autodetection.  */
163       for (alias = autodetect_list; alias != NULL; alias = alias->next)
164         if (strcmp (from_codeset, alias->name) == 0)
165           {
166             const char * const *encodings;
167
168             if (handler != iconveh_error)
169               {
170                 /* First try all encodings without any forgiving.  */
171                 encodings = alias->encodings_to_try;
172                 do
173                   {
174                     retval = mem_iconveha (src, srclen,
175                                            *encodings, to_codeset,
176                                            iconveh_error, offsets,
177                                            resultp, lengthp);
178                     if (!(retval < 0 && errno == EILSEQ))
179                       return retval;
180                     encodings++;
181                   }
182                 while (*encodings != NULL);
183               }
184
185             encodings = alias->encodings_to_try;
186             do
187               {
188                 retval = mem_iconveha (src, srclen,
189                                        *encodings, to_codeset,
190                                        handler, offsets,
191                                        resultp, lengthp);
192                 if (!(retval < 0 && errno == EILSEQ))
193                   return retval;
194                 encodings++;
195               }
196             while (*encodings != NULL);
197
198             /* Return the last call's result.  */
199             return -1;
200           }
201
202       /* It wasn't an autodetection name.  */
203       errno = EINVAL;
204       return -1;
205     }
206 }
207
208 char *
209 str_iconveha (const char *src,
210               const char *from_codeset, const char *to_codeset,
211               enum iconv_ilseq_handler handler)
212 {
213   char *result = str_iconveh (src, from_codeset, to_codeset, handler);
214
215   if (result != NULL || errno != EINVAL)
216     return result;
217   else
218     {
219       struct autodetect_alias *alias;
220
221       /* Unsupported from_codeset or to_codeset. Check whether the caller
222          requested autodetection.  */
223       for (alias = autodetect_list; alias != NULL; alias = alias->next)
224         if (strcmp (from_codeset, alias->name) == 0)
225           {
226             const char * const *encodings;
227
228             if (handler != iconveh_error)
229               {
230                 /* First try all encodings without any forgiving.  */
231                 encodings = alias->encodings_to_try;
232                 do
233                   {
234                     result = str_iconveha (src,
235                                            *encodings, to_codeset,
236                                            iconveh_error);
237                     if (!(result == NULL && errno == EILSEQ))
238                       return result;
239                     encodings++;
240                   }
241                 while (*encodings != NULL);
242               }
243
244             encodings = alias->encodings_to_try;
245             do
246               {
247                 result = str_iconveha (src,
248                                        *encodings, to_codeset,
249                                        handler);
250                 if (!(result == NULL && errno == EILSEQ))
251                   return result;
252                 encodings++;
253               }
254             while (*encodings != NULL);
255
256             /* Return the last call's result.  */
257             return NULL;
258           }
259
260       /* It wasn't an autodetection name.  */
261       errno = EINVAL;
262       return NULL;
263     }
264 }