]> sjero.net Git - wget/blob - src/iri.c
Basic support of IRIs.
[wget] / src / iri.c
1 /* IRI related functions.
2    Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
3    2008 Free Software Foundation, Inc.
4
5 This file is part of GNU Wget.
6
7 GNU Wget is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or (at
10 your option) any later version.
11
12 GNU Wget is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Wget.  If not, see <http://www.gnu.org/licenses/>.
19
20 Additional permission under GNU GPL version 3 section 7
21
22 If you modify this program, or any covered work, by linking or
23 combining it with the OpenSSL project's OpenSSL library (or a
24 modified version of that library), containing parts covered by the
25 terms of the OpenSSL or SSLeay licenses, the Free Software Foundation
26 grants you additional permission to convey the resulting work.
27 Corresponding Source for a non-source form of such a combination
28 shall include the source code for the parts of OpenSSL used as well
29 as that of the covered work.  */
30
31 #include "wget.h"
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <assert.h>
36 #include <string.h>
37 #include <iconv.h>
38 #include <stringprep.h>
39 #include <idna.h>
40 #include <errno.h>
41
42 #include "utils.h"
43 #include "iri.h"
44
45
46 static iconv_t locale2utf8;
47
48
49 static bool open_locale_to_utf8 (void);
50 static bool do_conversion (iconv_t cd, char *in, size_t inlen, char **out);
51
52
53 /* Given a string containing "charset=XXX", return the encoding if found,
54    or NULL otherwise */
55 char *
56 parse_charset (char *str)
57 {
58   char *charset;
59
60   if (!str || !*str)
61     return NULL;
62
63   str = strcasestr (str, "charset=");
64   if (!str)
65     return NULL;
66
67   str += 8;
68   charset = str;
69
70   /* sXXXav: which chars should be banned ??? */
71   while (*charset && !c_isspace (*charset))
72     charset++;
73
74   /* sXXXav: could strdupdelim return NULL ? */
75   charset = strdupdelim (str, charset);
76
77   /* Do a minimum check on the charset value */
78   if (!check_encoding_name (charset))
79     {
80       xfree (charset);
81       return NULL;
82     }
83
84   logprintf (LOG_VERBOSE, "parse_charset: %s\n", quote (charset));
85
86   return charset;
87 }
88
89 /* Find the locale used, or fall back on a default value */
90 char *
91 find_locale (void)
92 {
93   /* sXXXav, made our own function or use libidn one ?! */
94   return (char *) stringprep_locale_charset ();
95 }
96
97 /* Basic check of an encoding name. */
98 bool
99 check_encoding_name (char *encoding)
100 {
101   char *s = encoding;
102
103   while (*s)
104     {
105       if (!c_isascii(*s) || c_isspace(*s))
106         {
107           logprintf (LOG_VERBOSE, "Encoding %s isn't valid\n", quote(encoding));
108           return false;
109         }
110
111       s++;
112     }
113
114   return true;
115 }
116
117 /* Try opening an iconv_t descriptor for conversion from locale to UTF-8 */
118 static bool
119 open_locale_to_utf8 (void)
120 {
121   if (locale2utf8)
122     return true;
123
124   /* sXXXav : That shouldn't happen, just in case */
125   if (!opt.locale)
126     {
127       logprintf (LOG_VERBOSE, "open_locale_to_utf8: locale is unset\n");
128       opt.locale = find_locale ();
129     }
130
131   if (!opt.locale)
132     return false;
133
134   locale2utf8 = iconv_open ("UTF-8", opt.locale);
135   if (locale2utf8 != (iconv_t)(-1))
136     return true;
137
138   logprintf (LOG_VERBOSE, "Conversion from %s to %s isn't supported\n",
139              quote (opt.locale), quote("UTF-8"));
140   locale2utf8 = NULL;
141   return false;
142 }
143
144 /* Return a new string */
145 const char *
146 locale_to_utf8 (const char *str)
147 {
148   char *new;
149
150   if (!strcasecmp (opt.locale, "utf-8"))
151     return str;
152
153   if (!open_locale_to_utf8 ())
154     return str;
155
156   if (do_conversion (locale2utf8, (char *) str, strlen ((char *) str), &new))
157     return (const char *) new;
158
159   return str;
160 }
161
162 /* */
163 static bool
164 do_conversion (iconv_t cd, char *in, size_t inlen, char **out)
165 {
166   /* sXXXav : hummm hard to guess... */
167   size_t len, done, outlen = inlen * 2;
168   int invalid = 0, tooshort = 0;
169   char *s;
170
171   s = xmalloc (outlen + 1);
172   *out = s;
173   len = outlen;
174   done = 0;
175
176   /* sXXXav : put a maximum looping factor ??? */
177   for (;;)
178     {
179       if (iconv (cd, &in, &inlen, out, &outlen) != (size_t)(-1))
180         {
181           *out = s;
182           *(s + len - outlen - done) = '\0';
183           return true;
184         }
185
186       /* Incomplete or invalid multibyte sequence */
187       if (errno == EINVAL || errno == EILSEQ)
188         {
189           invalid++;
190           **out = *in;
191           in++;
192           inlen--;
193           (*out)++;
194           outlen--;
195         }
196       else if (errno == E2BIG) /* Output buffer full */ 
197         {
198           char *new;
199
200           tooshort++;
201           done = len;
202           outlen = done + inlen * 2;
203           new = xmalloc (outlen + 1);
204           memcpy (new, s, done);
205           xfree (s);
206           s = new;
207           len = outlen;
208           *out = s + done;
209         }
210       else /* Weird, we got an unspecified error */
211         {
212           logprintf (LOG_VERBOSE, "Unhandled errno %d\n", errno);
213           break;
214         }
215     }
216
217     return false;
218 }
219
220 /* Try to encode UTF-8 host to ASCII. Return the new domain on success or NULL
221    on error. */
222 char *idn_encode (char *host)
223 {
224   char *new;
225   int ret;
226
227   /* toASCII UTF-8 NULL terminated string */
228   ret = idna_to_ascii_8z (host, &new, 0);
229   if (ret != IDNA_SUCCESS)
230     {
231       logprintf (LOG_VERBOSE, "idn_encode failed (%d): %s\n", ret,
232                  quote (idna_strerror (ret)));
233       return NULL;
234     }
235
236   return new;
237 }
238