]> sjero.net Git - wget/blob - src/iri.c
Do not free/duplicate current/remote encoding string if they aren't changed
[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     {
341       /* Do nothing if already equal */
342       if (!strcasecmp (current, charset))
343         return;
344       xfree (current);
345     }
346
347   current = charset ? xstrdup (charset) : NULL;
348 }
349
350 void set_current_as_locale (void)
351 {
352   /* sXXXav : assert opt.locale NULL ? */
353   /*printf("[ current = locale = `%s'\n", opt.locale);*/
354   if (current)
355     {
356       if (!strcasecmp (current, opt.locale))
357         return;
358       xfree (current);
359     }
360
361   current = xstrdup (opt.locale);
362 }
363
364 void
365 set_remote_charset (char *charset)
366 {
367   /*printf("[ remote = `%s'\n", charset);*/
368   if (remote)
369     {
370       /* Do nothing if already equal */
371       if (!strcasecmp (remote, charset))
372         return;
373       xfree (remote);
374     }
375   remote = charset ? xstrdup (charset) : NULL;
376 }
377
378 void
379 set_remote_as_current (void)
380 {
381   /*printf("[ remote = current = `%s'\n", current);*/
382   if (remote)
383     {
384       /* Do nothing if already equal */
385       if (current && !strcasecmp (remote, current))
386         return;
387       xfree (remote);
388     }
389
390   remote = current ? xstrdup (current) : NULL;
391 }
392
393 void reset_utf8_encode (void)
394 {
395   set_utf8_encode (opt.enable_iri);
396 }
397
398 void set_utf8_encode (bool encode)
399 {
400   utf8_encode = encode;
401 }
402
403 bool get_utf8_encode (void)
404 {
405   return (!ugly_no_encode && utf8_encode);
406 }
407
408 void set_ugly_no_encode (bool ugly)
409 {
410   ugly_no_encode = ugly;
411 }
412