]> sjero.net Git - wget/blob - src/iri.c
Automated merge.
[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 /* RFC3987 section 3.1 mandates STD3 ASCII RULES */
45 #define IDNA_FLAGS  IDNA_USE_STD3_ASCII_RULES
46
47 /* Note: locale encoding is kept in options struct (opt.locale) */
48
49 /* Hold the encoding used for the current fetch */
50 char *remote;
51
52 /* Hold the encoding for the future found links */
53 char *current;
54
55 /* Will/Is the current URL encoded in utf8 ? */
56 bool utf8_encode;
57
58 /* Force no utf8 encoding for url_parse () */
59 bool ugly_no_encode;
60
61 static iconv_t locale2utf8;
62
63 static bool open_locale_to_utf8 (void);
64 static bool do_conversion (iconv_t cd, char *in, size_t inlen, char **out);
65
66
67 /* Given a string containing "charset=XXX", return the encoding if found,
68    or NULL otherwise */
69 char *
70 parse_charset (char *str)
71 {
72   char *charset;
73
74   if (!str || !*str)
75     return NULL;
76
77   str = strcasestr (str, "charset=");
78   if (!str)
79     return NULL;
80
81   str += 8;
82   charset = str;
83
84   /* sXXXav: which chars should be banned ??? */
85   while (*charset && !c_isspace (*charset))
86     charset++;
87
88   /* sXXXav: could strdupdelim return NULL ? */
89   charset = strdupdelim (str, charset);
90
91   /* Do a minimum check on the charset value */
92   if (!check_encoding_name (charset))
93     {
94       xfree (charset);
95       return NULL;
96     }
97
98   /*logprintf (LOG_VERBOSE, "parse_charset: %s\n", quote (charset));*/
99
100   return charset;
101 }
102
103 /* Find the locale used, or fall back on a default value */
104 char *
105 find_locale (void)
106 {
107   return (char *) stringprep_locale_charset ();
108 }
109
110 /* Basic check of an encoding name. */
111 bool
112 check_encoding_name (char *encoding)
113 {
114   char *s = encoding;
115
116   while (*s)
117     {
118       if (!c_isascii (*s) || c_isspace (*s))
119         {
120           logprintf (LOG_VERBOSE, "Encoding %s isn't valid\n", quote (encoding));
121           return false;
122         }
123
124       s++;
125     }
126
127   return true;
128 }
129
130 /* Try opening an iconv_t descriptor for conversion from locale to UTF-8 */
131 static bool
132 open_locale_to_utf8 (void)
133 {
134   if (locale2utf8)
135     return true;
136
137   /* sXXXav : That shouldn't happen, just in case */
138   if (!opt.locale)
139     {
140       logprintf (LOG_VERBOSE, "open_locale_to_utf8: locale is unset\n");
141       opt.locale = find_locale ();
142     }
143
144   if (!opt.locale)
145     return false;
146
147   locale2utf8 = iconv_open ("UTF-8", opt.locale);
148   if (locale2utf8 != (iconv_t)(-1))
149     return true;
150
151   logprintf (LOG_VERBOSE, "Conversion from %s to %s isn't supported\n",
152              quote (opt.locale), quote ("UTF-8"));
153   locale2utf8 = NULL;
154   return false;
155 }
156
157 /* Try converting string str from locale to UTF-8. Return a new string
158    on success, or str on error or if conversion isn't needed. */
159 const char *
160 locale_to_utf8 (const char *str)
161 {
162   char *new;
163
164   if (!strcasecmp (opt.locale, "utf-8"))
165     return str;
166
167   if (!open_locale_to_utf8 ())
168     return str;
169
170   if (do_conversion (locale2utf8, (char *) str, strlen ((char *) str), &new))
171     return (const char *) new;
172
173   return str;
174 }
175
176 /* Do the conversion according to the passed conversion descriptor cd. *out
177    will containes the transcoded string on success. *out content is
178    unspecified otherwise. */
179 static bool
180 do_conversion (iconv_t cd, char *in, size_t inlen, char **out)
181 {
182   /* sXXXav : hummm hard to guess... */
183   size_t len, done, outlen = inlen * 2;
184   int invalid = 0, tooshort = 0;
185   char *s;
186
187   s = xmalloc (outlen + 1);
188   *out = s;
189   len = outlen;
190   done = 0;
191
192   for (;;)
193     {
194       if (iconv (cd, &in, &inlen, out, &outlen) != (size_t)(-1))
195         {
196           *out = s;
197           *(s + len - outlen - done) = '\0';
198           return true;
199         }
200
201       /* Incomplete or invalid multibyte sequence */
202       if (errno == EINVAL || errno == EILSEQ)
203         {
204           if (!invalid)
205             logprintf (LOG_VERBOSE,
206                       "Incomplete or invalide multibyte sequence encountered\n");
207
208           invalid++;
209           **out = *in;
210           in++;
211           inlen--;
212           (*out)++;
213           outlen--;
214         }
215       else if (errno == E2BIG) /* Output buffer full */
216         {
217           char *new;
218
219           tooshort++;
220           done = len;
221           outlen = done + inlen * 2;
222           new = xmalloc (outlen + 1);
223           memcpy (new, s, done);
224           xfree (s);
225           s = new;
226           len = outlen;
227           *out = s + done;
228         }
229       else /* Weird, we got an unspecified error */
230         {
231           logprintf (LOG_VERBOSE, "Unhandled errno %d\n", errno);
232           break;
233         }
234     }
235
236     return false;
237 }
238
239 /* Try to "ASCII encode" UTF-8 host. Return the new domain on success or NULL
240    on error. */
241 char *
242 idn_encode (char *host, bool utf8_encoded)
243 {
244   char *new;
245   int ret;
246
247   /* Encode to UTF-8 if not done using current remote */
248   if (!utf8_encoded)
249     {
250       if (!remote_to_utf8 ((const char *) host, (const char **) &new))
251         {
252           /* Nothing to encode or an error occured */
253           return NULL;
254         }
255
256       host = new;
257     }
258
259   /* toASCII UTF-8 NULL terminated string */
260   ret = idna_to_ascii_8z (host, &new, IDNA_FLAGS);
261   if (ret != IDNA_SUCCESS)
262     {
263       /* sXXXav : free new when needed ! */
264       logprintf (LOG_VERBOSE, "idn_encode failed (%d): %s\n", ret,
265                  quote (idna_strerror (ret)));
266       return NULL;
267     }
268
269   return new;
270 }
271
272 /* Try to decode an "ASCII encoded" host. Return the new domain in the locale
273    on success or NULL on error. */
274 char *
275 idn_decode (char *host)
276 {
277   char *new;
278   int ret;
279
280   ret = idna_to_unicode_8zlz (host, &new, IDNA_FLAGS);
281   if (ret != IDNA_SUCCESS)
282     {
283       logprintf (LOG_VERBOSE, "idn_decode failed (%d): %s\n", ret,
284                  quote (idna_strerror (ret)));
285       return NULL;
286     }
287
288   return new;
289 }
290
291 /* Try to transcode string str from remote encoding to UTF-8. On success, *new
292    contains the transcoded string. *new content is unspecified otherwise. */
293 bool
294 remote_to_utf8 (const char *str, const char **new)
295 {
296   char *r;
297   iconv_t cd;
298   bool ret = false;
299
300   if (opt.encoding_remote)
301     r = opt.encoding_remote;
302   else if (current)
303     r = current;
304   else
305     return false;
306
307   cd = iconv_open ("UTF-8", r);
308   if (cd == (iconv_t)(-1))
309     return false;
310
311   if (do_conversion (cd, (char *) str, strlen ((char *) str), (char **) new))
312     ret = true;
313
314   iconv_close (cd);
315
316   /* Test if something was converted */
317   if (!strcmp (str, *new))
318     {
319       xfree ((char *) *new);
320       return false;
321     }
322
323   return ret;
324 }
325
326 char *get_remote_charset (void)
327 {
328   return remote;
329 }
330
331 char *get_current_charset (void)
332 {
333   return current;
334 }
335
336 void set_current_charset (char *charset)
337 {
338   /*printf("[ current = `%s'\n", charset);*/
339   if (current)
340     xfree (current);
341
342   current = charset ? xstrdup (charset) : NULL;
343 }
344
345 void set_current_as_locale (void)
346 {
347   /*printf("[ current = locale = `%s'\n", opt.locale);*/
348   if (current)
349     xfree (current);
350
351   /* sXXXav : assert opt.locale NULL ? */
352   current = xstrdup (opt.locale);
353 }
354
355 void
356 set_remote_charset (char *charset)
357 {
358   /*printf("[ remote = `%s'\n", charset);*/
359   if (remote)
360     xfree (remote);
361
362   remote = charset ? xstrdup (charset) : NULL;
363 }
364
365 void
366 set_remote_as_current (void)
367 {
368   /*printf("[ remote = current = `%s'\n", current);*/
369   if (remote)
370     xfree (remote);
371
372   remote = current ? xstrdup (current) : NULL;
373 }
374
375 void reset_utf8_encode (void)
376 {
377   set_utf8_encode (opt.enable_iri);
378 }
379
380 void set_utf8_encode (bool encode)
381 {
382   utf8_encode = encode;
383 }
384
385 bool get_utf8_encode (void)
386 {
387   return (!ugly_no_encode && utf8_encode);
388 }
389
390 void set_ugly_no_encode (bool ugly)
391 {
392   ugly_no_encode = ugly;
393 }
394