]> sjero.net Git - wget/blob - src/iri.c
Basic IDN/IRI support
[wget] / src / iri.c
1 /* IRI related functions.
2    Copyright (C) 2008 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or (at
9 your option) any later version.
10
11 GNU Wget 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
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
18
19 Additional permission under GNU GPL version 3 section 7
20
21 If you modify this program, or any covered work, by linking or
22 combining it with the OpenSSL project's OpenSSL library (or a
23 modified version of that library), containing parts covered by the
24 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
25 grants you additional permission to convey the resulting work.
26 Corresponding Source for a non-source form of such a combination
27 shall include the source code for the parts of OpenSSL used as well
28 as that of the covered work.  */
29
30 #include "wget.h"
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <assert.h>
35 #include <string.h>
36 #include <iconv.h>
37 #include <stringprep.h>
38 #include <idna.h>
39 #include <errno.h>
40
41 #include "utils.h"
42 #include "iri.h"
43
44 char *remote;
45 char *current;
46
47 static iconv_t locale2utf8;
48
49
50 static bool open_locale_to_utf8 (void);
51 static bool do_conversion (iconv_t cd, char *in, size_t inlen, char **out);
52
53
54 /* Given a string containing "charset=XXX", return the encoding if found,
55    or NULL otherwise */
56 char *
57 parse_charset (char *str)
58 {
59   char *charset;
60
61   if (!str || !*str)
62     return NULL;
63
64   str = strcasestr (str, "charset=");
65   if (!str)
66     return NULL;
67
68   str += 8;
69   charset = str;
70
71   /* sXXXav: which chars should be banned ??? */
72   while (*charset && !c_isspace (*charset))
73     charset++;
74
75   /* sXXXav: could strdupdelim return NULL ? */
76   charset = strdupdelim (str, charset);
77
78   /* Do a minimum check on the charset value */
79   if (!check_encoding_name (charset))
80     {
81       xfree (charset);
82       return NULL;
83     }
84
85   /*logprintf (LOG_VERBOSE, "parse_charset: %s\n", quote (charset));*/
86
87   return charset;
88 }
89
90 /* Find the locale used, or fall back on a default value */
91 char *
92 find_locale (void)
93 {
94   /* sXXXav, made our own function or use libidn one ?! */
95   return (char *) stringprep_locale_charset ();
96 }
97
98 /* Basic check of an encoding name. */
99 bool
100 check_encoding_name (char *encoding)
101 {
102   char *s = encoding;
103
104   while (*s)
105     {
106       if (!c_isascii (*s) || c_isspace (*s))
107         {
108           logprintf (LOG_VERBOSE, "Encoding %s isn't valid\n", quote (encoding));
109           return false;
110         }
111
112       s++;
113     }
114
115   return true;
116 }
117
118 /* Try opening an iconv_t descriptor for conversion from locale to UTF-8 */
119 static bool
120 open_locale_to_utf8 (void)
121 {
122   if (locale2utf8)
123     return true;
124
125   /* sXXXav : That shouldn't happen, just in case */
126   if (!opt.locale)
127     {
128       logprintf (LOG_VERBOSE, "open_locale_to_utf8: locale is unset\n");
129       opt.locale = find_locale ();
130     }
131
132   if (!opt.locale)
133     return false;
134
135   locale2utf8 = iconv_open ("UTF-8", opt.locale);
136   if (locale2utf8 != (iconv_t)(-1))
137     return true;
138
139   logprintf (LOG_VERBOSE, "Conversion from %s to %s isn't supported\n",
140              quote (opt.locale), quote ("UTF-8"));
141   locale2utf8 = NULL;
142   return false;
143 }
144
145 /* Return a new string */
146 const char *
147 locale_to_utf8 (const char *str)
148 {
149   char *new;
150
151   if (!strcasecmp (opt.locale, "utf-8"))
152     return str;
153
154   if (!open_locale_to_utf8 ())
155     return str;
156
157   if (do_conversion (locale2utf8, (char *) str, strlen ((char *) str), &new))
158     return (const char *) new;
159
160   return str;
161 }
162
163 /* */
164 static bool
165 do_conversion (iconv_t cd, char *in, size_t inlen, char **out)
166 {
167   /* sXXXav : hummm hard to guess... */
168   size_t len, done, outlen = inlen * 2;
169   int invalid = 0, tooshort = 0;
170   char *s;
171
172   s = xmalloc (outlen + 1);
173   *out = s;
174   len = outlen;
175   done = 0;
176
177   /* sXXXav : put a maximum looping factor ??? */
178   for (;;)
179     {
180       if (iconv (cd, &in, &inlen, out, &outlen) != (size_t)(-1))
181         {
182           *out = s;
183           *(s + len - outlen - done) = '\0';
184           return true;
185         }
186
187       /* Incomplete or invalid multibyte sequence */
188       if (errno == EINVAL || errno == EILSEQ)
189         {
190           if (!invalid)
191             logprintf (LOG_VERBOSE,
192                       "Incomplete or invalide multibyte sequence encountered\n");
193
194           invalid++;
195           **out = *in;
196           in++;
197           inlen--;
198           (*out)++;
199           outlen--;
200         }
201       else if (errno == E2BIG) /* Output buffer full */
202         {
203           char *new;
204
205           tooshort++;
206           done = len;
207           outlen = done + inlen * 2;
208           new = xmalloc (outlen + 1);
209           memcpy (new, s, done);
210           xfree (s);
211           s = new;
212           len = outlen;
213           *out = s + done;
214         }
215       else /* Weird, we got an unspecified error */
216         {
217           logprintf (LOG_VERBOSE, "Unhandled errno %d\n", errno);
218           break;
219         }
220     }
221
222     return false;
223 }
224
225 /* Try to ASCII encode UTF-8 host. Return the new domain on success or NULL
226    on error. */
227 char *
228 idn_encode (char *host, bool utf8_encoded)
229 {
230   char *new;
231   int ret;
232
233   /* Encode to UTF-8 if not done using current remote */
234   if (!utf8_encoded)
235     {
236       if (!remote_to_utf8 ((const char *) host, (const char **) &new))
237         {
238           /* Nothing to encode or an error occured */
239           return NULL;
240         }
241
242       host = new;
243     }
244
245   /* toASCII UTF-8 NULL terminated string */
246   ret = idna_to_ascii_8z (host, &new, 0);
247   if (ret != IDNA_SUCCESS)
248     {
249       /* sXXXav : free new when needed ! */
250       logprintf (LOG_VERBOSE, "idn_encode failed (%d): %s\n", ret,
251                  quote (idna_strerror (ret)));
252       return NULL;
253     }
254
255   return new;
256 }
257
258 /* Try to decode an ASCII encoded host. Return the new domain in the locale on
259    success or NULL on error. */
260 char *
261 idn_decode (char *host)
262 {
263   char *new;
264   int ret;
265
266   ret = idna_to_unicode_8zlz (host, &new, 0);
267   if (ret != IDNA_SUCCESS)
268     {
269       logprintf (LOG_VERBOSE, "idn_decode failed (%d): %s\n", ret,
270                  quote (idna_strerror (ret)));
271       return NULL;
272     }
273
274   return new;
275 }
276
277 /* Return a new string */
278 bool
279 remote_to_utf8 (const char *str, const char **new)
280 {
281   char *remote;
282   iconv_t cd;
283   bool ret = false;
284
285   if (opt.encoding_remote)
286     remote = opt.encoding_remote;
287   else if (current)
288     remote = current;
289   else
290     return false;
291
292   cd = iconv_open ("UTF-8", remote);
293   if (cd == (iconv_t)(-1))
294     return false;
295
296   if (do_conversion (cd, (char *) str, strlen ((char *) str), (char **) new))
297     ret = true;
298
299   iconv_close (cd);
300
301   /* Test if something was converted */
302   if (!strcmp (str, *new))
303     {
304       xfree ((char *) *new);
305       return false;
306     }
307
308   return ret;
309 }
310
311 char *get_remote_charset (void)
312 {
313   return remote;
314 }
315
316 char *get_current_charset (void)
317 {
318   return current;
319 }
320
321 void set_current_charset (char *charset)
322 {
323   /*printf("[ current = `%s'\n", charset);*/
324
325   if (current)
326     xfree (current);
327
328   current = charset ? xstrdup (charset) : NULL;
329 }
330
331 void set_current_as_locale (void)
332 {
333   /*printf("[ current = locale = `%s'\n", opt.locale);*/
334   if (current)
335     xfree (current);
336
337   /* sXXXav : assert opt.locale NULL ? */
338   current = xstrdup (opt.locale);
339 }
340
341 void
342 set_remote_charset (char *charset)
343 {
344   /*printf("[ remote = `%s'\n", charset);*/
345   if (remote)
346     xfree (remote);
347
348   remote = charset ? xstrdup (charset) : NULL;
349 }
350
351 void
352 set_remote_as_current (void)
353 {
354   /*printf("[ remote = current = `%s'\n", current);*/
355   if (remote)
356     xfree (remote);
357
358   remote = current ? xstrdup (current) : NULL;
359 }
360