Fix compiler warnings
[wget] / src / iri.c
1 /* IRI related functions.
2    Copyright (C) 2008, 2009, 2010, 2011 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 <string.h>
35 #include <iconv.h>
36 #include <stringprep.h>
37 #include <idna.h>
38 #include <errno.h>
39
40 #include "utils.h"
41
42 /* RFC3987 section 3.1 mandates STD3 ASCII RULES */
43 #define IDNA_FLAGS  IDNA_USE_STD3_ASCII_RULES
44
45 /* Note: locale encoding is kept in options struct (opt.locale) */
46
47 static bool do_conversion (iconv_t cd, char *in, size_t inlen, char **out);
48
49
50 /* Given a string containing "charset=XXX", return the encoding if found,
51    or NULL otherwise */
52 char *
53 parse_charset (char *str)
54 {
55   char *charset;
56
57   if (!str || !*str)
58     return NULL;
59
60   str = strcasestr (str, "charset=");
61   if (!str)
62     return NULL;
63
64   str += 8;
65   charset = str;
66
67   /* sXXXav: which chars should be banned ??? */
68   while (*charset && !c_isspace (*charset))
69     charset++;
70
71   /* sXXXav: could strdupdelim return NULL ? */
72   charset = strdupdelim (str, charset);
73
74   /* Do a minimum check on the charset value */
75   if (!check_encoding_name (charset))
76     {
77       xfree (charset);
78       return NULL;
79     }
80
81   /*logprintf (LOG_VERBOSE, "parse_charset: %s\n", quote (charset));*/
82
83   return charset;
84 }
85
86 /* Find the locale used, or fall back on a default value */
87 char *
88 find_locale (void)
89 {
90   return (char *) stringprep_locale_charset ();
91 }
92
93 /* Basic check of an encoding name. */
94 bool
95 check_encoding_name (char *encoding)
96 {
97   char *s = encoding;
98
99   while (*s)
100     {
101       if (!c_isascii (*s) || c_isspace (*s))
102         {
103           logprintf (LOG_VERBOSE, _("Encoding %s isn't valid\n"), quote (encoding));
104           return false;
105         }
106
107       s++;
108     }
109
110   return true;
111 }
112
113 /* Try converting string str from locale to UTF-8. Return a new string
114    on success, or str on error or if conversion isn't needed. */
115 const char *
116 locale_to_utf8 (const char *str)
117 {
118   iconv_t l2u;
119   char *new;
120
121   /* That shouldn't happen, just in case */
122   if (!opt.locale)
123     {
124       logprintf (LOG_VERBOSE, _("locale_to_utf8: locale is unset\n"));
125       opt.locale = find_locale ();
126     }
127
128   if (!opt.locale || !strcasecmp (opt.locale, "utf-8"))
129     return str;
130
131   l2u = iconv_open ("UTF-8", opt.locale);
132   if (l2u != (iconv_t)(-1))
133     {
134       logprintf (LOG_VERBOSE, _("Conversion from %s to %s isn't supported\n"),
135                  quote (opt.locale), quote ("UTF-8"));
136       return str;
137     }
138
139   if (do_conversion (l2u, (char *) str, strlen ((char *) str), &new))
140     return (const char *) new;
141
142   return str;
143 }
144
145 /* Do the conversion according to the passed conversion descriptor cd. *out
146    will contain the transcoded string on success. *out content is
147    unspecified otherwise. */
148 static bool
149 do_conversion (iconv_t cd, char *in, size_t inlen, char **out)
150 {
151   /* sXXXav : hummm hard to guess... */
152   size_t len, done, outlen = inlen * 2;
153   int invalid = 0, tooshort = 0;
154   char *s;
155
156   s = xmalloc (outlen + 1);
157   *out = s;
158   len = outlen;
159   done = 0;
160
161   for (;;)
162     {
163       if (iconv (cd, &in, &inlen, out, &outlen) != (size_t)(-1))
164         {
165           *out = s;
166           *(s + len - outlen - done) = '\0';
167           return true;
168         }
169
170       /* Incomplete or invalid multibyte sequence */
171       if (errno == EINVAL || errno == EILSEQ)
172         {
173           if (!invalid)
174             logprintf (LOG_VERBOSE,
175                       _("Incomplete or invalid multibyte sequence encountered\n"));
176
177           invalid++;
178           **out = *in;
179           in++;
180           inlen--;
181           (*out)++;
182           outlen--;
183         }
184       else if (errno == E2BIG) /* Output buffer full */
185         {
186           char *new;
187
188           tooshort++;
189           done = len;
190           outlen = done + inlen * 2;
191           new = xmalloc (outlen + 1);
192           memcpy (new, s, done);
193           xfree (s);
194           s = new;
195           len = outlen;
196           *out = s + done;
197         }
198       else /* Weird, we got an unspecified error */
199         {
200           logprintf (LOG_VERBOSE, _("Unhandled errno %d\n"), errno);
201           break;
202         }
203     }
204
205     return false;
206 }
207
208 /* Try to "ASCII encode" UTF-8 host. Return the new domain on success or NULL
209    on error. */
210 char *
211 idn_encode (struct iri *i, char *host)
212 {
213   char *new;
214   int ret;
215
216   /* Encode to UTF-8 if not done */
217   if (!i->utf8_encode)
218     {
219       if (!remote_to_utf8 (i, (const char *) host, (const char **) &new))
220           return NULL;  /* Nothing to encode or an error occured */
221       host = new;
222     }
223
224   /* toASCII UTF-8 NULL terminated string */
225   ret = idna_to_ascii_8z (host, &new, IDNA_FLAGS);
226   if (ret != IDNA_SUCCESS)
227     {
228       /* sXXXav : free new when needed ! */
229       logprintf (LOG_VERBOSE, _("idn_encode failed (%d): %s\n"), ret,
230                  quote (idna_strerror (ret)));
231       return NULL;
232     }
233
234   return new;
235 }
236
237 /* Try to decode an "ASCII encoded" host. Return the new domain in the locale
238    on success or NULL on error. */
239 char *
240 idn_decode (char *host)
241 {
242   char *new;
243   int ret;
244
245   ret = idna_to_unicode_8zlz (host, &new, IDNA_FLAGS);
246   if (ret != IDNA_SUCCESS)
247     {
248       logprintf (LOG_VERBOSE, _("idn_decode failed (%d): %s\n"), ret,
249                  quote (idna_strerror (ret)));
250       return NULL;
251     }
252
253   return new;
254 }
255
256 /* Try to transcode string str from remote encoding to UTF-8. On success, *new
257    contains the transcoded string. *new content is unspecified otherwise. */
258 bool
259 remote_to_utf8 (struct iri *iri, const char *str, const char **new)
260 {
261   iconv_t cd;
262   bool ret = false;
263
264   if (!iri->uri_encoding)
265     return false;
266
267   /* When `i->uri_encoding' == "UTF-8" there is nothing to convert.  But we must
268      test for non-ASCII symbols for correct hostname processing in `idn_encode'
269      function. */
270   if (!strcmp (iri->uri_encoding, "UTF-8"))
271     {
272       const char *p = str;
273       for (p = str; *p; p++)
274         if (*p < 0)
275           {
276             *new = strdup (str);
277             return true;
278           }
279       return false;
280     }
281
282   cd = iconv_open ("UTF-8", iri->uri_encoding);
283   if (cd == (iconv_t)(-1))
284     return false;
285
286   if (do_conversion (cd, (char *) str, strlen ((char *) str), (char **) new))
287     ret = true;
288
289   iconv_close (cd);
290
291   /* Test if something was converted */
292   if (!strcmp (str, *new))
293     {
294       xfree ((char *) *new);
295       return false;
296     }
297
298   return ret;
299 }
300
301 /* Allocate a new iri structure and return a pointer to it. */
302 struct iri *
303 iri_new (void)
304 {
305   struct iri *i = xmalloc (sizeof *i);
306   i->uri_encoding = opt.encoding_remote ? xstrdup (opt.encoding_remote) : NULL;
307   i->content_encoding = NULL;
308   i->orig_url = NULL;
309   i->utf8_encode = opt.enable_iri;
310   return i;
311 }
312
313 struct iri *iri_dup (const struct iri *src)
314 {
315   struct iri *i = xmalloc (sizeof *i);
316   i->uri_encoding = src->uri_encoding ? xstrdup (src->uri_encoding) : NULL;
317   i->content_encoding = (src->content_encoding ?
318                          xstrdup (src->content_encoding) : NULL);
319   i->orig_url = src->orig_url ? xstrdup (src->orig_url) : NULL;
320   i->utf8_encode = src->utf8_encode;
321   return i;
322 }
323
324 /* Completely free an iri structure. */
325 void
326 iri_free (struct iri *i)
327 {
328   xfree_null (i->uri_encoding);
329   xfree_null (i->content_encoding);
330   xfree_null (i->orig_url);
331   xfree (i);
332 }
333
334 /* Set uri_encoding of struct iri i. If a remote encoding was specified, use
335    it unless force is true. */
336 void
337 set_uri_encoding (struct iri *i, char *charset, bool force)
338 {
339   DEBUGP (("URI encoding = %s\n", charset ? quote (charset) : "None"));
340   if (!force && opt.encoding_remote)
341     return;
342   if (i->uri_encoding)
343     {
344       if (charset && !strcasecmp (i->uri_encoding, charset))
345         return;
346       xfree (i->uri_encoding);
347     }
348
349   i->uri_encoding = charset ? xstrdup (charset) : NULL;
350 }
351
352 /* Set content_encoding of struct iri i. */
353 void
354 set_content_encoding (struct iri *i, char *charset)
355 {
356   DEBUGP (("URI content encoding = %s\n", charset ? quote (charset) : "None"));
357   if (opt.encoding_remote)
358     return;
359   if (i->content_encoding)
360     {
361       if (charset && !strcasecmp (i->content_encoding, charset))
362         return;
363       xfree (i->content_encoding);
364     }
365
366   i->content_encoding = charset ? xstrdup (charset) : NULL;
367 }
368