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