]> sjero.net Git - wget/blob - src/iri.c
Use DEBUGP instead of commenting out all the _wonderful_ printfs
[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 static iconv_t locale2utf8;
50
51 static bool open_locale_to_utf8 (void);
52 static bool do_conversion (iconv_t cd, char *in, size_t inlen, char **out);
53
54
55 /* Given a string containing "charset=XXX", return the encoding if found,
56    or NULL otherwise */
57 char *
58 parse_charset (char *str)
59 {
60   char *charset;
61
62   if (!str || !*str)
63     return NULL;
64
65   str = strcasestr (str, "charset=");
66   if (!str)
67     return NULL;
68
69   str += 8;
70   charset = str;
71
72   /* sXXXav: which chars should be banned ??? */
73   while (*charset && !c_isspace (*charset))
74     charset++;
75
76   /* sXXXav: could strdupdelim return NULL ? */
77   charset = strdupdelim (str, charset);
78
79   /* Do a minimum check on the charset value */
80   if (!check_encoding_name (charset))
81     {
82       xfree (charset);
83       return NULL;
84     }
85
86   /*logprintf (LOG_VERBOSE, "parse_charset: %s\n", quote (charset));*/
87
88   return charset;
89 }
90
91 /* Find the locale used, or fall back on a default value */
92 char *
93 find_locale (void)
94 {
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 /* Try converting string str from locale to UTF-8. Return a new string
146    on success, or str on error or if conversion isn't needed. */
147 const char *
148 locale_to_utf8 (const char *str)
149 {
150   char *new;
151
152   if (!strcasecmp (opt.locale, "utf-8"))
153     return str;
154
155   if (!open_locale_to_utf8 ())
156     return str;
157
158   if (do_conversion (locale2utf8, (char *) str, strlen ((char *) str), &new))
159     return (const char *) new;
160
161   return str;
162 }
163
164 /* Do the conversion according to the passed conversion descriptor cd. *out
165    will containes the transcoded string on success. *out content is
166    unspecified otherwise. */
167 static bool
168 do_conversion (iconv_t cd, char *in, size_t inlen, char **out)
169 {
170   /* sXXXav : hummm hard to guess... */
171   size_t len, done, outlen = inlen * 2;
172   int invalid = 0, tooshort = 0;
173   char *s;
174
175   s = xmalloc (outlen + 1);
176   *out = s;
177   len = outlen;
178   done = 0;
179
180   for (;;)
181     {
182       if (iconv (cd, &in, &inlen, out, &outlen) != (size_t)(-1))
183         {
184           *out = s;
185           *(s + len - outlen - done) = '\0';
186           return true;
187         }
188
189       /* Incomplete or invalid multibyte sequence */
190       if (errno == EINVAL || errno == EILSEQ)
191         {
192           if (!invalid)
193             logprintf (LOG_VERBOSE,
194                       "Incomplete or invalide multibyte sequence encountered\n");
195
196           invalid++;
197           **out = *in;
198           in++;
199           inlen--;
200           (*out)++;
201           outlen--;
202         }
203       else if (errno == E2BIG) /* Output buffer full */
204         {
205           char *new;
206
207           tooshort++;
208           done = len;
209           outlen = done + inlen * 2;
210           new = xmalloc (outlen + 1);
211           memcpy (new, s, done);
212           xfree (s);
213           s = new;
214           len = outlen;
215           *out = s + done;
216         }
217       else /* Weird, we got an unspecified error */
218         {
219           logprintf (LOG_VERBOSE, "Unhandled errno %d\n", errno);
220           break;
221         }
222     }
223
224     return false;
225 }
226
227 /* Try to "ASCII encode" UTF-8 host. Return the new domain on success or NULL
228    on error. */
229 char *
230 idn_encode (struct iri *i, char *host)
231 {
232   char *new;
233   int ret;
234
235   /* Encode to UTF-8 if not done */
236   if (!i->utf8_encode)
237     {
238       if (!remote_to_utf8 (i, (const char *) host, (const char **) &new))
239         {
240           /* Nothing to encode or an error occured */
241           return NULL;
242         }
243
244       host = new;
245     }
246
247   /* toASCII UTF-8 NULL terminated string */
248   ret = idna_to_ascii_8z (host, &new, IDNA_FLAGS);
249   if (ret != IDNA_SUCCESS)
250     {
251       /* sXXXav : free new when needed ! */
252       logprintf (LOG_VERBOSE, "idn_encode failed (%d): %s\n", ret,
253                  quote (idna_strerror (ret)));
254       return NULL;
255     }
256
257   return new;
258 }
259
260 /* Try to decode an "ASCII encoded" host. Return the new domain in the locale
261    on success or NULL on error. */
262 char *
263 idn_decode (char *host)
264 {
265   char *new;
266   int ret;
267
268   ret = idna_to_unicode_8zlz (host, &new, IDNA_FLAGS);
269   if (ret != IDNA_SUCCESS)
270     {
271       logprintf (LOG_VERBOSE, "idn_decode failed (%d): %s\n", ret,
272                  quote (idna_strerror (ret)));
273       return NULL;
274     }
275
276   return new;
277 }
278
279 /* Try to transcode string str from remote encoding to UTF-8. On success, *new
280    contains the transcoded string. *new content is unspecified otherwise. */
281 bool
282 remote_to_utf8 (struct iri *i, const char *str, const char **new)
283 {
284   char *r;
285   iconv_t cd;
286   bool ret = false;
287
288   if (opt.encoding_remote)
289     r = opt.encoding_remote;
290   else if (i->uri_encoding)
291     r = i->uri_encoding;
292   else
293     return false;
294
295   cd = iconv_open ("UTF-8", r);
296   if (cd == (iconv_t)(-1))
297     return false;
298
299   if (do_conversion (cd, (char *) str, strlen ((char *) str), (char **) new))
300     ret = true;
301
302   iconv_close (cd);
303
304   /* Test if something was converted */
305   if (!strcmp (str, *new))
306     {
307       xfree ((char *) *new);
308       return false;
309     }
310
311   return ret;
312 }
313
314 struct iri *
315 iri_new (void)
316 {
317   struct iri *i = xmalloc (sizeof (struct iri));
318   i->uri_encoding = opt.encoding_remote ? xstrdup (opt.encoding_remote) : NULL;
319   i->content_encoding = NULL;
320   i->utf8_encode = opt.enable_iri;
321 }
322
323 void
324 iri_free (struct iri *i)
325 {
326   xfree_null (i->uri_encoding);
327   xfree_null (i->content_encoding);
328   xfree (i);
329 }
330
331 void
332 set_uri_encoding (struct iri *i, char *charset)
333 {
334   DEBUGP (("[IRI uri = `%s'\n", quote (charset)));
335   if (opt.encoding_remote)
336     return;
337   if (i->uri_encoding)
338     {
339       if (!strcasecmp (i->uri_encoding, charset))
340         return;
341       xfree (i->uri_encoding);
342     }
343
344   i->uri_encoding = charset ? xstrdup (charset) : NULL;
345 }
346
347 void
348 set_content_encoding (struct iri *i, char *charset)
349 {
350   DEBUGP (("[IRI content = %s\n", quote (charset)));
351   if (opt.encoding_remote)
352     return;
353   if (i->content_encoding)
354     {
355       if (!strcasecmp (i->content_encoding, charset))
356         return;
357       xfree (i->content_encoding);
358     }
359
360   i->content_encoding = charset ? xstrdup (charset) : NULL;
361 }
362