262f252d5ffb09f3b2f3733e8d20716774e87cde
[platform/upstream/glibc.git] / libidn / toutf8.c
1 /* toutf8.c     Convert strings from system locale into UTF-8.
2  * Copyright (C) 2002, 2003, 2004  Simon Josefsson
3  *
4  * This file is part of GNU Libidn.
5  *
6  * GNU Libidn is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * GNU Libidn is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with GNU Libidn; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #if HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <sys/param.h>
31
32 #include "stringprep.h"
33
34 #ifdef _LIBC
35 # define HAVE_ICONV 1
36 # define LOCALE_WORKS 1
37 # define ICONV_CONST
38 #endif
39
40 #ifdef HAVE_ICONV
41 # include <iconv.h>
42
43 # if LOCALE_WORKS
44 #  include <langinfo.h>
45 #  include <locale.h>
46 # endif
47
48 # ifdef _LIBC
49 #  define stringprep_locale_charset() nl_langinfo (CODESET)
50 # else
51 /**
52  * stringprep_locale_charset:
53  *
54  * Find out current locale charset.  The function respect the CHARSET
55  * environment variable, but typically uses nl_langinfo(CODESET) when
56  * it is supported.  It fall back on "ASCII" if CHARSET isn't set and
57  * nl_langinfo isn't supported or return anything.
58  *
59  * Note that this function return the application's locale's preferred
60  * charset (or thread's locale's preffered charset, if your system
61  * support thread-specific locales).  It does not return what the
62  * system may be using.  Thus, if you receive data from external
63  * sources you cannot in general use this function to guess what
64  * charset it is encoded in.  Use stringprep_convert from the external
65  * representation into the charset returned by this function, to have
66  * data in the locale encoding.
67  *
68  * Return value: Return the character set used by the current locale.
69  *   It will never return NULL, but use "ASCII" as a fallback.
70  **/
71 const char *
72 stringprep_locale_charset (void)
73 {
74   const char *charset = getenv ("CHARSET");     /* flawfinder: ignore */
75
76   if (charset && *charset)
77     return charset;
78
79 #  ifdef LOCALE_WORKS
80   charset = nl_langinfo (CODESET);
81
82   if (charset && *charset)
83     return charset;
84 #  endif
85
86   return "ASCII";
87 }
88 # endif
89
90 /**
91  * stringprep_convert:
92  * @str: input zero-terminated string.
93  * @to_codeset: name of destination character set.
94  * @from_codeset: name of origin character set, as used by @str.
95  *
96  * Convert the string from one character set to another using the
97  * system's iconv() function.
98  *
99  * Return value: Returns newly allocated zero-terminated string which
100  *   is @str transcoded into to_codeset.
101  **/
102 char *
103 stringprep_convert (const char *str,
104                     const char *to_codeset, const char *from_codeset)
105 {
106   iconv_t cd;
107   char *dest;
108   char *outp;
109   ICONV_CONST char *p;
110   size_t inbytes_remaining;
111   size_t outbytes_remaining;
112   size_t err;
113   size_t outbuf_size;
114   int have_error = 0;
115
116   if (strcmp (to_codeset, from_codeset) == 0)
117     {
118 #if defined HAVE_STRDUP || defined _LIBC
119       return strdup (str);
120 #else
121       char *p;
122       p = malloc (strlen (str) + 1);
123       if (!p)
124         return NULL;
125       return strcpy (p, str);
126 #endif
127     }
128
129   cd = iconv_open (to_codeset, from_codeset);
130
131   if (cd == (iconv_t) - 1)
132     return NULL;
133
134   p = (ICONV_CONST char *) str;
135
136   inbytes_remaining = strlen (p);
137   /* Guess the maximum length the output string can have.  */
138   outbuf_size = (inbytes_remaining + 1) * MAX (7, MB_CUR_MAX);
139
140   outp = dest = malloc (outbuf_size);
141   if (dest == NULL)
142     goto out;
143   outbytes_remaining = outbuf_size - 1; /* -1 for NUL */
144
145 again:
146
147   err = iconv (cd, (ICONV_CONST char **) &p, &inbytes_remaining,
148                &outp, &outbytes_remaining);
149
150   if (err == (size_t) - 1)
151     {
152       switch (errno)
153         {
154         case EINVAL:
155           /* Incomplete text, do not report an error */
156           break;
157
158         case E2BIG:
159           {
160             size_t used = outp - dest;
161             char *newdest;
162
163             outbuf_size *= 2;
164             newdest = realloc (dest, outbuf_size);
165             if (newdest == NULL)
166               {
167                 have_error = 1;
168                 goto out;
169               }
170             dest = newdest;
171
172             outp = dest + used;
173             outbytes_remaining = outbuf_size - used - 1; /* -1 for NUL */
174
175             goto again;
176           }
177           break;
178
179         case EILSEQ:
180           have_error = 1;
181           break;
182
183         default:
184           have_error = 1;
185           break;
186         }
187     }
188
189   *outp = '\0';
190
191   if (*p != '\0')
192     have_error = 1;
193
194  out:
195   iconv_close (cd);
196
197   if (have_error)
198     {
199       free (dest);
200       dest = NULL;
201     }
202
203   return dest;
204 }
205
206 #else /* HAVE_ICONV */
207
208 const char *
209 stringprep_locale_charset ()
210 {
211   return "ASCII";
212 }
213
214 char *
215 stringprep_convert (const char *str,
216                     const char *to_codeset, const char *from_codeset)
217 {
218   char *p;
219   fprintf (stderr, "libidn: warning: libiconv not installed, cannot "
220            "convert data to UTF-8\n");
221   p = malloc (strlen (str) + 1);
222   if (!p)
223     return NULL;
224   strcpy (p, str);
225   return p;
226 }
227
228 #endif /* HAVE_ICONV */
229
230 /**
231  * stringprep_locale_to_utf8:
232  * @str: input zero terminated string.
233  *
234  * Convert string encoded in the locale's character set into UTF-8 by
235  * using stringprep_convert().
236  *
237  * Return value: Returns newly allocated zero-terminated string which
238  *   is @str transcoded into UTF-8.
239  **/
240 char *
241 stringprep_locale_to_utf8 (const char *str)
242 {
243   return stringprep_convert (str, "UTF-8", stringprep_locale_charset ());
244 }
245
246 /**
247  * stringprep_utf8_to_locale:
248  * @str: input zero terminated string.
249  *
250  * Convert string encoded in UTF-8 into the locale's character set by
251  * using stringprep_convert().
252  *
253  * Return value: Returns newly allocated zero-terminated string which
254  *   is @str transcoded into the locale's character set.
255  **/
256 char *
257 stringprep_utf8_to_locale (const char *str)
258 {
259   return stringprep_convert (str, stringprep_locale_charset (), "UTF-8");
260 }