]> sjero.net Git - wget/blob - src/iri.c
067291c7cf5c33dedacccf32e33147047016070e
[wget] / src / iri.c
1 /* IRI related functions.
2    Copyright (C) 2008, 2009, 2010 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 *i, const char *str, const char **new)
260 {
261   iconv_t cd;
262   bool ret = false;
263
264   if (!i->uri_encoding)
265     return false;
266
267   cd = iconv_open ("UTF-8", i->uri_encoding);
268   if (cd == (iconv_t)(-1))
269     return false;
270
271   if (do_conversion (cd, (char *) str, strlen ((char *) str), (char **) new))
272     ret = true;
273
274   iconv_close (cd);
275
276   /* Test if something was converted */
277   if (!strcmp (str, *new))
278     {
279       xfree ((char *) *new);
280       return false;
281     }
282
283   return ret;
284 }
285
286 /* Allocate a new iri structure and return a pointer to it. */
287 struct iri *
288 iri_new (void)
289 {
290   struct iri *i = xmalloc (sizeof *i);
291   i->uri_encoding = opt.encoding_remote ? xstrdup (opt.encoding_remote) : NULL;
292   i->content_encoding = NULL;
293   i->orig_url = NULL;
294   i->utf8_encode = opt.enable_iri;
295   return i;
296 }
297
298 struct iri *iri_dup (const struct iri *src)
299 {
300   struct iri *i = xmalloc (sizeof *i);
301   i->uri_encoding = src->uri_encoding ? xstrdup (src->uri_encoding) : NULL;
302   i->content_encoding = (src->content_encoding ?
303                          xstrdup (src->content_encoding) : NULL);
304   i->orig_url = src->orig_url ? xstrdup (src->orig_url) : NULL;
305   i->utf8_encode = src->utf8_encode;
306   return i;
307 }
308
309 /* Completely free an iri structure. */
310 void
311 iri_free (struct iri *i)
312 {
313   xfree_null (i->uri_encoding);
314   xfree_null (i->content_encoding);
315   xfree_null (i->orig_url);
316   xfree (i);
317 }
318
319 /* Set uri_encoding of struct iri i. If a remote encoding was specified, use
320    it unless force is true. */
321 void
322 set_uri_encoding (struct iri *i, char *charset, bool force)
323 {
324   DEBUGP (("URI encoding = %s\n", charset ? quote (charset) : "None"));
325   if (!force && opt.encoding_remote)
326     return;
327   if (i->uri_encoding)
328     {
329       if (charset && !strcasecmp (i->uri_encoding, charset))
330         return;
331       xfree (i->uri_encoding);
332     }
333
334   i->uri_encoding = charset ? xstrdup (charset) : NULL;
335 }
336
337 /* Set content_encoding of struct iri i. */
338 void
339 set_content_encoding (struct iri *i, char *charset)
340 {
341   DEBUGP (("URI content encoding = %s\n", charset ? quote (charset) : "None"));
342   if (opt.encoding_remote)
343     return;
344   if (i->content_encoding)
345     {
346       if (charset && !strcasecmp (i->content_encoding, charset))
347         return;
348       xfree (i->content_encoding);
349     }
350
351   i->content_encoding = charset ? xstrdup (charset) : NULL;
352 }
353