Add optional offsets argument to conversion routines.
[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 = alias->encodings_to_try;
167
168             do
169               {
170                 retval = mem_iconveha (src, srclen,
171                                        from_codeset, to_codeset, handler,
172                                        offsets, resultp, lengthp);
173                 if (!(retval < 0 && errno == EILSEQ))
174                   return retval;
175                 encodings++;
176               }
177             while (*encodings != NULL);
178
179             /* Return the last call's result.  */
180             return -1;
181           }
182
183       /* It wasn't an autodetection name.  */
184       errno = EINVAL;
185       return -1;
186     }
187 }
188
189 char *
190 str_iconveha (const char *src,
191               const char *from_codeset, const char *to_codeset,
192               enum iconv_ilseq_handler handler)
193 {
194   char *result = str_iconveh (src, from_codeset, to_codeset, handler);
195
196   if (result != NULL || errno != EINVAL)
197     return result;
198   else
199     {
200       struct autodetect_alias *alias;
201
202       /* Unsupported from_codeset or to_codeset. Check whether the caller
203          requested autodetection.  */
204       for (alias = autodetect_list; alias != NULL; alias = alias->next)
205         if (strcmp (from_codeset, alias->name) == 0)
206           {
207             const char * const *encodings = alias->encodings_to_try;
208
209             do
210               {
211                 result = str_iconveha (src, *encodings, to_codeset, handler);
212                 if (!(result == NULL && errno == EILSEQ))
213                   return result;
214                 encodings++;
215               }
216             while (*encodings != NULL);
217
218             /* Return the last call's result.  */
219             return NULL;
220           }
221
222       /* It wasn't an autodetection name.  */
223       errno = EINVAL;
224       return NULL;
225     }
226 }