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