Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2012 Tim Ruehsen |
3 | | * Copyright (c) 2015-2026 Free Software Foundation, Inc. |
4 | | * |
5 | | * This file is part of libwget. |
6 | | * |
7 | | * Libwget is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as published by |
9 | | * the Free Software Foundation, either version 3 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * Libwget 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 Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libwget. If not, see <https://www.gnu.org/licenses/>. |
19 | | * |
20 | | * |
21 | | * URI/IRI routines |
22 | | * about encoding see http://nikitathespider.com/articles/EncodingDivination.html |
23 | | * about GET encoding see https://stackoverflow.com/questions/1549213/whats-the-correct-encoding-of-http-get-request-strings |
24 | | * RFC 3986: URI generic syntax |
25 | | * |
26 | | * |
27 | | * Changelog |
28 | | * 25.04.2012 Tim Ruehsen created |
29 | | * |
30 | | */ |
31 | | |
32 | | #include <config.h> |
33 | | |
34 | | #include <string.h> |
35 | | #include <errno.h> |
36 | | #include "c-ctype.h" |
37 | | |
38 | | #include <wget.h> |
39 | | #include "private.h" |
40 | | |
41 | | static char *create_safe_uri(wget_iri *iri); |
42 | | |
43 | | /** |
44 | | * \file |
45 | | * \brief Functions to work with URIs and IRIs |
46 | | * \defgroup libwget-iri URIs/IRIs |
47 | | * |
48 | | * @{ |
49 | | * |
50 | | * URI/IRI parsing and manipulation functions. |
51 | | * |
52 | | * IRIs are processed according to [RFC 3987](https://datatracker.ietf.org/doc/rfc3987/). |
53 | | * Functions that escape certain characters (such as wget_iri_escape()) work according to |
54 | | * [RFC 3986](https://datatracker.ietf.org/doc/rfc3986/). |
55 | | * |
56 | | * The \ref wget_iri_st "wget_iri" structure represents an IRI. You generate one from a string with wget_iri_parse() or |
57 | | * wget_iri_parse_base(). You can use wget_iri_clone() to generate another identical \ref wget_iri_st "wget_iri". |
58 | | * |
59 | | * You can access each of the fields of a \ref wget_iri_st "wget_iri" (such as `path`) independently, and you can use |
60 | | * the getters here to escape each of those parts, or for convenience (e.g wget_iri_get_escaped_host(), |
61 | | * wget_iri_get_escaped_resource(), etc.). |
62 | | * |
63 | | * URIs/IRIs are all internally treated in UTF-8. The parsing functions that generate a \ref wget_iri_st "wget_iri" structure |
64 | | * (wget_iri_parse() and wget_iri_parse_base()) thus convert the input string to UTF-8 before anything else. |
65 | | * These functions take an `encoding` parameter that tells which is the original encoding of that string. |
66 | | * |
67 | | * Conversely, the getters (for example, wget_iri_get_path()) can convert the output string from UTF-8 |
68 | | * to an encoding of choice. The desired encoding is also specified in the `encoding` parameter. |
69 | | * |
70 | | * The `encoding` parameter, in all functions that accept it, is a string with the name of a character set |
71 | | * supported by GNU libiconv. You can find such a list elsewhere, but popular examples are "utf-8", "utf-16" or "iso-8859-1". |
72 | | */ |
73 | | |
74 | | static const char |
75 | | *default_page = "index.html"; |
76 | | static size_t |
77 | | default_page_length = 10; |
78 | | |
79 | | static struct iri_scheme { |
80 | | uint16_t port; |
81 | | const char name[6]; |
82 | | } schemes[] = { |
83 | | [WGET_IRI_SCHEME_HTTP] = { 80, "http" }, |
84 | | [WGET_IRI_SCHEME_HTTPS] = { 443, "https" }, |
85 | | }; |
86 | | |
87 | | static size_t WGET_GCC_NONNULL_ALL normalize_path(char *path); |
88 | | |
89 | | /** |
90 | | * \param[in] scheme Scheme to get name for |
91 | | * \return Name of \p scheme (e.g. "http" or "https") or NULL is not supported |
92 | | * |
93 | | * Maps \p scheme to it's string representation. |
94 | | */ |
95 | | const char *wget_iri_scheme_get_name(wget_iri_scheme scheme) |
96 | 846 | { |
97 | 846 | if ((unsigned) scheme < countof(schemes)) |
98 | 846 | return schemes[scheme].name; |
99 | | |
100 | 0 | return NULL; |
101 | 846 | } |
102 | | |
103 | | /** |
104 | | * \param[in] iri An IRI |
105 | | * \return 1 if the scheme is supported, 0 if not |
106 | | * |
107 | | * Tells whether the IRI's scheme is supported or not. |
108 | | */ |
109 | | bool wget_iri_supported(const wget_iri *iri) |
110 | 46.5k | { |
111 | 46.5k | return (unsigned) iri->scheme < countof(schemes); |
112 | 46.5k | } |
113 | | |
114 | | |
115 | | /* \cond _hide_internal_symbols */ |
116 | 4.50k | #define IRI_CTYPE_GENDELIM (1<<0) |
117 | 4.50k | #define iri_isgendelim(c) (iri_ctype[(unsigned char)(c)] & IRI_CTYPE_GENDELIM) |
118 | | |
119 | 990k | #define IRI_CTYPE_SUBDELIM (1<<1) |
120 | 2.18M | #define iri_issubdelim(c) (iri_ctype[(unsigned char)(c)] & IRI_CTYPE_SUBDELIM) |
121 | | |
122 | 3.80M | #define IRI_CTYPE_UNRESERVED (1<<2) |
123 | 5.92M | #define iri_isunreserved(c) (iri_ctype[(unsigned char)(c)] & IRI_CTYPE_UNRESERVED) |
124 | | |
125 | 291k | #define iri_isscheme(c) (c_isalnum(c) || c == '+' || c == '-' || c == '.') |
126 | | /* \endcond */ |
127 | | |
128 | | static const unsigned char |
129 | | iri_ctype[256] = { |
130 | | [':'] = IRI_CTYPE_GENDELIM, |
131 | | ['/'] = IRI_CTYPE_GENDELIM, |
132 | | ['?'] = IRI_CTYPE_GENDELIM, |
133 | | ['#'] = IRI_CTYPE_GENDELIM, |
134 | | ['['] = IRI_CTYPE_GENDELIM, |
135 | | [']'] = IRI_CTYPE_GENDELIM, |
136 | | ['@'] = IRI_CTYPE_GENDELIM, |
137 | | |
138 | | ['!'] = IRI_CTYPE_SUBDELIM, |
139 | | ['$'] = IRI_CTYPE_SUBDELIM, |
140 | | ['&'] = IRI_CTYPE_SUBDELIM, |
141 | | ['\''] = IRI_CTYPE_SUBDELIM, |
142 | | ['('] = IRI_CTYPE_SUBDELIM, |
143 | | [')'] = IRI_CTYPE_SUBDELIM, |
144 | | ['*'] = IRI_CTYPE_SUBDELIM, |
145 | | ['+'] = IRI_CTYPE_SUBDELIM, |
146 | | [','] = IRI_CTYPE_SUBDELIM, |
147 | | [';'] = IRI_CTYPE_SUBDELIM, |
148 | | ['='] = IRI_CTYPE_SUBDELIM, |
149 | | |
150 | | ['0'] = IRI_CTYPE_UNRESERVED, |
151 | | ['1'] = IRI_CTYPE_UNRESERVED, |
152 | | ['2'] = IRI_CTYPE_UNRESERVED, |
153 | | ['3'] = IRI_CTYPE_UNRESERVED, |
154 | | ['4'] = IRI_CTYPE_UNRESERVED, |
155 | | ['5'] = IRI_CTYPE_UNRESERVED, |
156 | | ['6'] = IRI_CTYPE_UNRESERVED, |
157 | | ['7'] = IRI_CTYPE_UNRESERVED, |
158 | | ['8'] = IRI_CTYPE_UNRESERVED, |
159 | | ['9'] = IRI_CTYPE_UNRESERVED, |
160 | | ['a'] = IRI_CTYPE_UNRESERVED, |
161 | | ['b'] = IRI_CTYPE_UNRESERVED, |
162 | | ['c'] = IRI_CTYPE_UNRESERVED, |
163 | | ['d'] = IRI_CTYPE_UNRESERVED, |
164 | | ['e'] = IRI_CTYPE_UNRESERVED, |
165 | | ['f'] = IRI_CTYPE_UNRESERVED, |
166 | | ['g'] = IRI_CTYPE_UNRESERVED, |
167 | | ['h'] = IRI_CTYPE_UNRESERVED, |
168 | | ['i'] = IRI_CTYPE_UNRESERVED, |
169 | | ['j'] = IRI_CTYPE_UNRESERVED, |
170 | | ['k'] = IRI_CTYPE_UNRESERVED, |
171 | | ['l'] = IRI_CTYPE_UNRESERVED, |
172 | | ['m'] = IRI_CTYPE_UNRESERVED, |
173 | | ['n'] = IRI_CTYPE_UNRESERVED, |
174 | | ['o'] = IRI_CTYPE_UNRESERVED, |
175 | | ['p'] = IRI_CTYPE_UNRESERVED, |
176 | | ['q'] = IRI_CTYPE_UNRESERVED, |
177 | | ['r'] = IRI_CTYPE_UNRESERVED, |
178 | | ['s'] = IRI_CTYPE_UNRESERVED, |
179 | | ['t'] = IRI_CTYPE_UNRESERVED, |
180 | | ['u'] = IRI_CTYPE_UNRESERVED, |
181 | | ['v'] = IRI_CTYPE_UNRESERVED, |
182 | | ['w'] = IRI_CTYPE_UNRESERVED, |
183 | | ['x'] = IRI_CTYPE_UNRESERVED, |
184 | | ['y'] = IRI_CTYPE_UNRESERVED, |
185 | | ['z'] = IRI_CTYPE_UNRESERVED, |
186 | | ['A'] = IRI_CTYPE_UNRESERVED, |
187 | | ['B'] = IRI_CTYPE_UNRESERVED, |
188 | | ['C'] = IRI_CTYPE_UNRESERVED, |
189 | | ['D'] = IRI_CTYPE_UNRESERVED, |
190 | | ['E'] = IRI_CTYPE_UNRESERVED, |
191 | | ['F'] = IRI_CTYPE_UNRESERVED, |
192 | | ['G'] = IRI_CTYPE_UNRESERVED, |
193 | | ['H'] = IRI_CTYPE_UNRESERVED, |
194 | | ['I'] = IRI_CTYPE_UNRESERVED, |
195 | | ['J'] = IRI_CTYPE_UNRESERVED, |
196 | | ['K'] = IRI_CTYPE_UNRESERVED, |
197 | | ['L'] = IRI_CTYPE_UNRESERVED, |
198 | | ['M'] = IRI_CTYPE_UNRESERVED, |
199 | | ['N'] = IRI_CTYPE_UNRESERVED, |
200 | | ['O'] = IRI_CTYPE_UNRESERVED, |
201 | | ['P'] = IRI_CTYPE_UNRESERVED, |
202 | | ['Q'] = IRI_CTYPE_UNRESERVED, |
203 | | ['R'] = IRI_CTYPE_UNRESERVED, |
204 | | ['S'] = IRI_CTYPE_UNRESERVED, |
205 | | ['T'] = IRI_CTYPE_UNRESERVED, |
206 | | ['U'] = IRI_CTYPE_UNRESERVED, |
207 | | ['V'] = IRI_CTYPE_UNRESERVED, |
208 | | ['W'] = IRI_CTYPE_UNRESERVED, |
209 | | ['X'] = IRI_CTYPE_UNRESERVED, |
210 | | ['Y'] = IRI_CTYPE_UNRESERVED, |
211 | | ['Z'] = IRI_CTYPE_UNRESERVED, |
212 | | ['-'] = IRI_CTYPE_UNRESERVED, |
213 | | ['.'] = IRI_CTYPE_UNRESERVED, |
214 | | ['_'] = IRI_CTYPE_UNRESERVED, |
215 | | ['~'] = IRI_CTYPE_UNRESERVED |
216 | | }; |
217 | | |
218 | | /** |
219 | | * \param[in] c A character |
220 | | * \return 1 if \p c is a generic delimiter, 0 if not |
221 | | * |
222 | | * Tests whether \p c is a generic delimiter (gen-delim), |
223 | | * according to [RFC 3986, sect. 2.2](https://tools.ietf.org/html/rfc3986#section-2.2). |
224 | | */ |
225 | | bool wget_iri_isgendelim(char c) |
226 | 4.50k | { |
227 | | // return strchr(":/?#[]@",c)!=NULL; |
228 | 4.50k | return iri_isgendelim(c); |
229 | 4.50k | } |
230 | | |
231 | | /** |
232 | | * \param[in] c A character |
233 | | * \return 1 if \p c is a subcomponent delimiter, 0 if not |
234 | | * |
235 | | * Tests whether \p c is a subcomponent delimiter (sub-delim) |
236 | | * according to [RFC 3986, sect. 2.2](https://tools.ietf.org/html/rfc3986#section-2.2). |
237 | | */ |
238 | | bool wget_iri_issubdelim(char c) |
239 | 4.50k | { |
240 | | // return strchr("!$&\'()*+,;=",c)!=NULL; |
241 | 4.50k | return iri_issubdelim(c); |
242 | 4.50k | } |
243 | | |
244 | | /** |
245 | | * \param[in] c A character |
246 | | * \return 1 if \p c is a reserved character, 0 if not |
247 | | * |
248 | | * Tests whether \p c is a reserved character. |
249 | | * |
250 | | * According to [RFC 3986, sect. 2.2](https://tools.ietf.org/html/rfc3986#section-2.2), |
251 | | * the set of reserved characters is formed |
252 | | * by the generic delimiters (gen-delims, wget_iri_isgendelim()) and the |
253 | | * subcomponent delimiters (sub-delims, wget_iri_is_subdelim()). |
254 | | * |
255 | | * This function is thus equivalent to: |
256 | | * |
257 | | * return wget_iri_isgendelim(c) || wget_iri_issubdelim(c); |
258 | | * |
259 | | */ |
260 | | bool wget_iri_isreserved(char c) |
261 | 4.50k | { |
262 | 4.50k | return wget_iri_isgendelim(c) || wget_iri_issubdelim(c); |
263 | 4.50k | } |
264 | | |
265 | | /** |
266 | | * \param[in] c A character |
267 | | * \return 1 if \p c is an unreserved character, 0 if not |
268 | | * |
269 | | * Tests whether \p c is an unreserved character. |
270 | | */ |
271 | | bool wget_iri_isunreserved(char c) |
272 | 0 | { |
273 | 0 | return iri_isunreserved(c); |
274 | 0 | } |
275 | | |
276 | | static unsigned char WGET_GCC_CONST unhex(unsigned char c) |
277 | 53.2k | { |
278 | 53.2k | return c <= '9' ? c - '0' : (c <= 'F' ? c - 'A' + 10 : c - 'a' + 10); |
279 | 53.2k | } |
280 | | |
281 | | static char *iri_unescape_inline(char *src, int ctype) |
282 | 142k | { |
283 | 142k | char *ret = NULL; |
284 | 142k | unsigned char *s = (unsigned char *)src; // just a helper to avoid casting a lot |
285 | 142k | unsigned char *d = s; |
286 | | |
287 | 3.21M | while (*s) { |
288 | 3.07M | if (*s == '%') { |
289 | 46.8k | if (c_isxdigit(s[1]) && c_isxdigit(s[2])) { |
290 | 16.8k | unsigned char c = (unsigned char) (unhex(s[1]) << 4) | unhex(s[2]); |
291 | 16.8k | if (!ctype || (!(iri_ctype[(unsigned char)(c)] & ctype) && c != '%')) { |
292 | 16.8k | *d++ = c; |
293 | 16.8k | s += 3; |
294 | 16.8k | ret = src; |
295 | 16.8k | continue; |
296 | 16.8k | } |
297 | 16.8k | } |
298 | 3.02M | } else if (*s == '#') { |
299 | 18.2k | uint32_t value = 0; |
300 | | |
301 | 18.2k | if (s[1] == 'x') { |
302 | 7.01k | unsigned char *p = s + 2; |
303 | 26.4k | while (c_isxdigit(*p)) { |
304 | 19.4k | value = ((value & 0x0FFFFFFF) << 4) | unhex(*p); |
305 | 19.4k | p++; |
306 | 19.4k | } |
307 | 7.01k | if (*p == ';') { |
308 | 4.28k | if (value > 0 && value < 128) { |
309 | 1.82k | *d++ = (unsigned char) value; |
310 | 1.82k | s = p + 1; |
311 | 1.82k | continue; |
312 | 1.82k | } |
313 | | // else: we have to convert the unicode value to whatever encoding the URL is in (likely UTF-8) |
314 | | // this cannot be done inline since the URL's length may increase |
315 | 4.28k | } |
316 | 11.2k | } else { |
317 | 11.2k | unsigned char *p = s + 1; |
318 | 21.5k | while (c_isdigit(*p) && value <= 0x10FFFF) { // max. Unicode value |
319 | 10.3k | value = value * 10 + (*p - '0'); |
320 | 10.3k | p++; |
321 | 10.3k | } |
322 | 11.2k | if (*p == ';') { |
323 | 4.59k | if (value > 0 && value < 128) { |
324 | 2.65k | *d++ = (unsigned char) value; |
325 | 2.65k | s = p + 1; |
326 | 2.65k | continue; |
327 | 2.65k | } |
328 | | // else: we have to convert the unicode value to whatever encoding the URL is in (likely UTF-8) |
329 | | // this cannot be done inline since the URL's length may increase |
330 | 4.59k | } |
331 | 11.2k | } |
332 | 3.01M | } else if (*s == '\r' || *s == '\n') { |
333 | | // Ignore / remove CR and LF from URLs. See https://gitlab.com/gnuwget/wget2/-/issues/522 |
334 | 2.77k | s++; |
335 | 2.77k | continue; |
336 | 2.77k | } |
337 | | |
338 | 3.05M | *d++ = *s++; |
339 | 3.05M | } |
340 | 142k | *d = 0; |
341 | | |
342 | 142k | return ret; |
343 | 142k | } |
344 | | |
345 | | /** |
346 | | * \param[in] src A string |
347 | | * \return A pointer to \p src, after the transformation is done |
348 | | * |
349 | | * Unescape a string. All the percent-encoded characters (`%XX`) are converted |
350 | | * back to their original form. |
351 | | * |
352 | | * **The transformation is done inline**, so `src` will be modified after this function returns. |
353 | | * If no percent-encoded characters are found, the string is left untouched. |
354 | | */ |
355 | | char *wget_iri_unescape_inline(char *src) |
356 | 142k | { |
357 | 142k | return iri_unescape_inline(src, 0); |
358 | 142k | } |
359 | | |
360 | | /** |
361 | | * \param[in] src A string |
362 | | * \return A pointer to \p src, after the transformation is done |
363 | | * |
364 | | * Unescape a string except escaped generic delimiters (and escaped '%'. |
365 | | * The percent-encoded characters (`%XX`) are converted back to their original form. |
366 | | * |
367 | | * This variant of unescaping is helpful before an URL is being parsed, so that |
368 | | * the parser recognizes e.g. 'http%3A//' as relative URL (path) and not as a scheme. |
369 | | * |
370 | | * **The transformation is done inline**, so `src` will be modified after this function returns. |
371 | | * If no characters were unescaped, the string is left untouched. |
372 | | */ |
373 | | char *wget_iri_unescape_url_inline(char *src) |
374 | 0 | { |
375 | 0 | return iri_unescape_inline(src, IRI_CTYPE_GENDELIM); |
376 | 0 | } |
377 | | |
378 | | /** |
379 | | * \param[in] iri An IRI |
380 | | * |
381 | | * Free the heap-allocated content of the provided IRI, but leave the rest |
382 | | * of the fields. |
383 | | * |
384 | | * This function frees the following fields of \ref wget_iri_st "wget_iri": |
385 | | * |
386 | | * - `host` |
387 | | * - `path` |
388 | | * - `query` |
389 | | * - `fragment` |
390 | | * - `connection_part` |
391 | | */ |
392 | | void wget_iri_free_content(wget_iri *iri) |
393 | 120k | { |
394 | 120k | if (iri) { |
395 | 120k | if (iri->userinfo) |
396 | 6.62k | xfree(iri->safe_uri); |
397 | 113k | else |
398 | 113k | iri->safe_uri = NULL; |
399 | 120k | if (iri->host_allocated) |
400 | 40.1k | xfree(iri->host); |
401 | 120k | if (iri->path_allocated) |
402 | 7.45k | xfree(iri->path); |
403 | 120k | if (iri->query_allocated) |
404 | 1.91k | xfree(iri->query); |
405 | 120k | if (iri->fragment_allocated) |
406 | 752 | xfree(iri->fragment); |
407 | 120k | xfree(iri->connection_part); |
408 | 120k | } |
409 | 120k | } |
410 | | |
411 | | /** |
412 | | * \param[in] iri A pointer to a pointer to an IRI (a \ref wget_iri_st "wget_iri") |
413 | | * |
414 | | * Destroy a \ref wget_iri_st "wget_iri" structure. |
415 | | * |
416 | | * The provided pointer is set to NULL. |
417 | | */ |
418 | | void wget_iri_free(wget_iri **iri) |
419 | 134k | { |
420 | 134k | if (iri && *iri) { |
421 | 120k | wget_iri_free_content(*iri); |
422 | 120k | xfree(*iri); |
423 | 120k | } |
424 | 134k | } |
425 | | |
426 | | // URIs are assumed to be unescaped at this point |
427 | | |
428 | | /** |
429 | | * \param[in] url A URL/IRI |
430 | | * \param[in] encoding Original encoding of \p url |
431 | | * \return A libwget IRI (`wget_iri`) |
432 | | * |
433 | | * The host, path, query and fragment parts will be converted to UTF-8 from |
434 | | * the encoding given in the parameter \p encoding. GNU libiconv is used |
435 | | * to perform the conversion, so this value should be the name of a valid character set |
436 | | * supported by that library, such as "utf-8" or "iso-8859-1". |
437 | | */ |
438 | | wget_iri *wget_iri_parse(const char *url, const char *encoding) |
439 | 88.7k | { |
440 | 88.7k | return wget_iri_parse_ex(url, encoding, 0); |
441 | 88.7k | } |
442 | | |
443 | | /** |
444 | | * \param[in] url A URL/IRI |
445 | | * \param[in] encoding Original encoding of \p url |
446 | | * \return A libwget IRI (`wget_iri`) |
447 | | * |
448 | | * The host, path, query and fragment parts will be converted to UTF-8 from |
449 | | * the encoding given in the parameter \p encoding. GNU libiconv is used |
450 | | * to perform the conversion, so this value should be the name of a valid character set |
451 | | * supported by that library, such as "utf-8" or "iso-8859-1". |
452 | | */ |
453 | | wget_iri *wget_iri_parse_ex(const char *url, const char *encoding, int flags) |
454 | 106k | { |
455 | 106k | wget_iri *iri; |
456 | 106k | char *p, *s, *authority, c; |
457 | 106k | size_t slen, extra; |
458 | 106k | int have_scheme; |
459 | | |
460 | 106k | if (!url) |
461 | 1.28k | return NULL; |
462 | | |
463 | | /* |
464 | | URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] |
465 | | hier-part = "//" authority path-abempty / path-absolute / path-rootless / path-empty |
466 | | scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) |
467 | | */ |
468 | 107k | while (c_isspace(*url)) url++; |
469 | 105k | if (!*url) return NULL; |
470 | | /* |
471 | | // first unescape, than convert to UTF-8 |
472 | | if (strchr(url, '%')) { |
473 | | char *unesc_url = wget_strdup(url); |
474 | | |
475 | | wget_percent_unescape(unesc_url); |
476 | | |
477 | | if (wget_str_needs_encoding(unesc_url)) { |
478 | | if ((url = wget_str_to_utf8(unesc_url, encoding))) |
479 | | xfree(unesc_url); |
480 | | else |
481 | | url = unesc_url; // on error, use what we have |
482 | | } else |
483 | | url = unesc_url; |
484 | | |
485 | | url_allocated = 1; |
486 | | } else { |
487 | | url_allocated = 0; |
488 | | |
489 | | if (wget_str_needs_encoding(url)) { |
490 | | if ((s = wget_str_to_utf8(url, encoding))) { |
491 | | url = s; |
492 | | url_allocated = 1; |
493 | | } |
494 | | } |
495 | | } |
496 | | */ |
497 | | |
498 | 105k | if (c_isalpha(*url)) { |
499 | 70.3k | const char *x; |
500 | 70.3k | have_scheme = 1; |
501 | | |
502 | 325k | for (x = url; *x && iri_isscheme(*x); x++) |
503 | 255k | ; |
504 | | |
505 | 70.3k | if (*x != ':' || c_isdigit(x[1])) |
506 | 50.3k | have_scheme = 0; // not a scheme |
507 | 70.3k | } else |
508 | 34.6k | have_scheme = 0; |
509 | | |
510 | | // just use one block of memory for all parsed URI parts |
511 | 105k | slen = strlen(url); |
512 | | |
513 | | // Calculate extra space for inserting a scheme. Take the longest supported scheme string. |
514 | 105k | extra = have_scheme ? 0 : sizeof("https://") - 1; |
515 | | |
516 | 105k | iri = wget_calloc(1, sizeof(wget_iri) + (slen + extra + 1) * 2); |
517 | 105k | if (!iri) |
518 | 0 | return NULL; |
519 | | |
520 | 105k | iri->keep_path_as_is = (flags&WGET_IRI_KEEP_AS_IS) != 0; |
521 | 105k | iri->msize = slen + extra + 1; |
522 | | |
523 | 105k | if (have_scheme) { |
524 | 19.9k | iri->uri = memcpy(iri + 1, url, slen + 1); |
525 | 19.9k | p = s = memcpy((char *)iri->uri + iri->msize, url, slen + 1); |
526 | 19.9k | s = strchr(s, ':'); // we know there is a : |
527 | 19.9k | *s++ = 0; |
528 | | |
529 | | // p points to scheme |
530 | 19.9k | if (!iri->keep_path_as_is) |
531 | 19.9k | wget_iri_unescape_inline(p); // percent unescape |
532 | | |
533 | 19.9k | wget_strtolower(p); // convert to lowercase |
534 | | |
535 | 19.9k | bool found = false; // assume the scheme is unsupported |
536 | | |
537 | | // find the scheme in our static list of supported schemes |
538 | | // for later comparisons we compare pointers (avoiding strcasecmp()) |
539 | 24.1k | for (unsigned it = 0; it < countof(schemes); it++) { |
540 | 22.0k | if (!strcmp(schemes[it].name, p)) { |
541 | 17.8k | iri->scheme = it; |
542 | 17.8k | iri->port = schemes[it].port; |
543 | 17.8k | found = true; |
544 | 17.8k | break; |
545 | 17.8k | } |
546 | 22.0k | } |
547 | | |
548 | 19.9k | if (!found) { |
549 | 2.10k | debug_printf("Unsupported scheme in '%s'\n", url); |
550 | 2.10k | wget_iri_free(&iri); |
551 | 2.10k | return NULL; |
552 | 2.10k | } |
553 | 85.0k | } else { |
554 | | // add http:// scheme to url |
555 | 510k | #define HTTP_SCHEME_STRING "http://" |
556 | 425k | #define HTTP_SCHEME_LEN (sizeof(HTTP_SCHEME_STRING) - 1) |
557 | 85.0k | iri->uri = memcpy(iri + 1, HTTP_SCHEME_STRING, HTTP_SCHEME_LEN); |
558 | 85.0k | memcpy((char *)iri->uri + HTTP_SCHEME_LEN, url, slen + 1); |
559 | 85.0k | s = memcpy((char *)iri->uri + iri->msize, iri->uri, HTTP_SCHEME_LEN + slen + 1); |
560 | 85.0k | s[HTTP_SCHEME_LEN - 3] = 0; |
561 | 85.0k | s += HTTP_SCHEME_LEN; |
562 | | |
563 | 85.0k | iri->scheme = WGET_IRI_SCHEME_HTTP; |
564 | 85.0k | iri->port = schemes[WGET_IRI_SCHEME_HTTP].port; |
565 | 85.0k | } |
566 | | |
567 | | // if (url_allocated) |
568 | | // xfree(url); |
569 | | |
570 | | // this is true for http, https, ftp, file (accept any number of /, like most browsers) |
571 | 141k | while (*s == '/') |
572 | 38.5k | s++; |
573 | | |
574 | | // authority |
575 | 102k | authority = s; |
576 | 1.79M | while (*s && *s != '/' && *s != '?' && *s != '#') |
577 | 1.69M | s++; |
578 | 102k | c = *s; |
579 | 102k | if (c) *s++ = 0; |
580 | 102k | wget_iri_unescape_inline(authority); |
581 | | |
582 | | // left over: [path][?query][#fragment] |
583 | 102k | if (c == '/') { |
584 | 14.5k | iri->path = s; |
585 | 998k | while (*s && *s != '?' && *s != '#') |
586 | 984k | s++; |
587 | 14.5k | c = *s; |
588 | 14.5k | if (c) *s++ = 0; |
589 | 14.5k | if (!iri->keep_path_as_is) |
590 | 14.5k | wget_iri_unescape_inline((char *)iri->path); |
591 | 14.5k | normalize_path((char *)iri->path); |
592 | 14.5k | } |
593 | | |
594 | 102k | if (c == '?') { |
595 | 4.24k | iri->query = s; |
596 | 41.7k | while (*s && *s != '#') { |
597 | 37.4k | if (*s == '+') |
598 | 1.21k | *s = ' '; |
599 | 37.4k | s++; |
600 | 37.4k | } |
601 | 4.24k | c = *s; |
602 | 4.24k | if (c) *s++ = 0; |
603 | | /* do not unescape query else we get ambiguity for chars like &, =, +, ... */ |
604 | 4.24k | } |
605 | | |
606 | 102k | if (c == '#') { |
607 | 4.58k | iri->fragment = s; |
608 | 4.58k | s += strlen(s); |
609 | 4.58k | wget_iri_unescape_inline((char *)iri->fragment); |
610 | 4.58k | } |
611 | | |
612 | 102k | if (*s) { |
613 | 0 | debug_printf("unparsed rest '%s'\n", s); |
614 | 0 | } |
615 | | |
616 | 102k | if (*authority) { |
617 | 99.4k | s = authority; |
618 | 99.4k | p = strchr(authority, '@'); |
619 | 99.4k | if (p) { |
620 | 6.38k | iri->userinfo = s; |
621 | 6.38k | *p = 0; |
622 | 6.38k | if ((s = strchr(s, ':'))) { |
623 | 459 | *s = 0; |
624 | 459 | iri->password = s + 1; |
625 | 459 | } |
626 | 6.38k | s = p + 1; |
627 | 6.38k | } |
628 | 99.4k | if (*s == '[') { |
629 | 1.49k | p = strrchr(s, ']'); |
630 | 1.49k | if (p) { |
631 | 447 | iri->host = s + 1; |
632 | 447 | *p = 0; |
633 | 447 | s = p + 1; |
634 | 1.04k | } else { |
635 | | // something is broken |
636 | 1.04k | iri->host = s + 1; |
637 | 1.04k | s += strlen(s); |
638 | 1.04k | } |
639 | 97.9k | } else { |
640 | 97.9k | iri->host = s; |
641 | 1.67M | while (*s && *s != ':') |
642 | 1.57M | s++; |
643 | 97.9k | } |
644 | 99.4k | if (*s == ':') { |
645 | 8.70k | if (c_isdigit(s[1])) { |
646 | 3.54k | unsigned long port = strtoul(s + 1, NULL, 10); |
647 | 3.54k | if (port == 0 || port > 65535) { |
648 | 2.44k | error_printf(_("Port number must be in the range 1..65535\n")); |
649 | 2.44k | wget_iri_free(&iri); |
650 | 2.44k | return NULL; |
651 | 2.44k | } |
652 | 1.09k | iri->port = (uint16_t) port; |
653 | 1.09k | iri->port_given = true; |
654 | 1.09k | } |
655 | 8.70k | } |
656 | 97.0k | *s = 0; |
657 | 97.0k | } |
658 | | |
659 | | // now unescape all components (not interested in display, userinfo, password right now) |
660 | | |
661 | 100k | if (iri->host) { |
662 | 97.0k | wget_strtolower((char *)iri->host); |
663 | 97.0k | if (wget_str_needs_encoding(iri->host)) { |
664 | 29.7k | if ((s = wget_str_to_utf8(iri->host, encoding))) { |
665 | 26.0k | iri->host = s; |
666 | 26.0k | iri->host_allocated = true; |
667 | 26.0k | } |
668 | 29.7k | } |
669 | 97.0k | if ((p = (char *)wget_str_to_ascii(iri->host)) != iri->host) { |
670 | 13.8k | if (iri->host_allocated) |
671 | 12.2k | xfree(iri->host); |
672 | 13.8k | iri->host = p; |
673 | 13.8k | iri->host_allocated = true; |
674 | 13.8k | } |
675 | | |
676 | | // Finally, if the host is a literal IPv4 or IPv6 address, mark it as so |
677 | 97.0k | if (wget_ip_is_family(iri->host, WGET_NET_FAMILY_IPV4) || wget_ip_is_family(iri->host, WGET_NET_FAMILY_IPV6)) |
678 | 798 | iri->is_ip_address = true; |
679 | 97.0k | } |
680 | | |
681 | 100k | if (!iri->host) { |
682 | 3.43k | error_printf(_("Missing host/domain in URI '%s'\n"), iri->uri); |
683 | 3.43k | wget_iri_free(&iri); |
684 | 3.43k | return NULL; |
685 | 3.43k | } |
686 | | |
687 | 97.0k | if (iri->path && wget_str_needs_encoding(iri->path)) { |
688 | 7.10k | if ((s = wget_str_to_utf8(iri->path, encoding))) { |
689 | 6.37k | iri->path = s; |
690 | 6.37k | iri->path_allocated = true; |
691 | 6.37k | } |
692 | 7.10k | } |
693 | | |
694 | 97.0k | if (iri->query && wget_str_needs_encoding(iri->query)) { |
695 | 1.42k | if ((s = wget_str_to_utf8(iri->query, encoding))) { |
696 | 1.23k | iri->query = s; |
697 | 1.23k | iri->query_allocated = true; |
698 | 1.23k | } |
699 | 1.42k | } |
700 | | |
701 | 97.0k | if (iri->fragment && wget_str_needs_encoding(iri->fragment)) { |
702 | 849 | if ((s = wget_str_to_utf8(iri->fragment, encoding))) { |
703 | 628 | iri->fragment = s; |
704 | 628 | iri->fragment_allocated = true; |
705 | 628 | } |
706 | 849 | } |
707 | | |
708 | 97.0k | if (iri->userinfo) { |
709 | 5.98k | iri->safe_uri = create_safe_uri(iri); |
710 | 91.0k | } else { |
711 | 91.0k | iri->safe_uri = iri->uri; |
712 | 91.0k | } |
713 | | |
714 | | /* |
715 | | debug_printf("scheme=%s\n",iri->scheme); |
716 | | debug_printf("host=%s\n",iri->host); |
717 | | debug_printf("path=%s\n",iri->path); |
718 | | debug_printf("query=%s\n",iri->query); |
719 | | debug_printf("fragment=%s\n",iri->fragment); |
720 | | */ |
721 | 97.0k | return iri; |
722 | 100k | } |
723 | | |
724 | | /** |
725 | | * \param[in] iri An IRI |
726 | | * \return A new IRI, with the exact same contents as the provided one. |
727 | | * |
728 | | * Clone the provided IRI. |
729 | | */ |
730 | | wget_iri *wget_iri_clone(const wget_iri *iri) |
731 | 16.5k | { |
732 | 16.5k | if (!iri || !iri->uri) |
733 | 1.43k | return NULL; |
734 | | |
735 | 15.1k | wget_iri *clone = wget_memdup(iri, sizeof(wget_iri) + iri->msize * 2); |
736 | | |
737 | 15.1k | if (!clone) |
738 | 0 | return NULL; |
739 | | |
740 | 15.1k | clone->uri = (char *) (clone + 1); |
741 | | |
742 | 15.1k | if (iri->userinfo) |
743 | 244 | clone->safe_uri = wget_strdup(iri->safe_uri); |
744 | 14.9k | else |
745 | 14.9k | clone->safe_uri = clone->uri; |
746 | | |
747 | 15.1k | clone->connection_part = wget_strdup(iri->connection_part); |
748 | | |
749 | | // adjust pointers |
750 | 15.1k | if (iri->host_allocated) |
751 | 12.5k | clone->host = wget_strdup(iri->host); |
752 | 2.61k | else |
753 | 2.61k | clone->host = iri->host ? (char *)clone + (size_t) (iri->host - (const char *)iri) : NULL; |
754 | | |
755 | 15.1k | clone->display = iri->display ? (char *)clone + (size_t) (iri->display - (const char *)iri): NULL; |
756 | | // not adjust scheme, it is a pointer to a static string |
757 | 15.1k | clone->userinfo = iri->userinfo ? (char *)clone + (size_t) (iri->userinfo - (const char *)iri): NULL; |
758 | 15.1k | clone->password = iri->password ? (char *)clone + (size_t) (iri->password - (const char *)iri): NULL; |
759 | | |
760 | 15.1k | if (iri->path_allocated) |
761 | 1.07k | clone->path = wget_strdup(iri->path); |
762 | 14.0k | else |
763 | 14.0k | clone->path = iri->path ? (char *)clone + (size_t) (iri->path - (const char *)iri): NULL; |
764 | | |
765 | 15.1k | if (iri->query_allocated) |
766 | 684 | clone->query = wget_strdup(iri->query); |
767 | 14.4k | else |
768 | 14.4k | clone->query = iri->query ? (char *)clone + (size_t) (iri->query - (const char *)iri): NULL; |
769 | | |
770 | 15.1k | if (iri->fragment_allocated) |
771 | 124 | clone->fragment = wget_strdup(iri->fragment); |
772 | 15.0k | else |
773 | 15.0k | clone->fragment = iri->fragment ? (char *)clone + (size_t) (iri->fragment - (const char *)iri): NULL; |
774 | | |
775 | 15.1k | return clone; |
776 | 15.1k | } |
777 | | |
778 | | /** |
779 | | * \param[in] iri An IRI |
780 | | * \param[in] buf A buffer, where the resulting string will be put |
781 | | * \return The contents of the buffer \p buf |
782 | | * |
783 | | * Append the connection part of the IRI \p iri to \p buf. |
784 | | * |
785 | | * The connection part is formed by the scheme, the hostname, and optionally the port. For example: |
786 | | * |
787 | | * https://localhost:8080 |
788 | | * https://www.example.com |
789 | | * |
790 | | * It may be of the form `https://example.com:8080` if the port was provided when creating the IRI |
791 | | * or of the form `https://example.com` otherwise. |
792 | | */ |
793 | | const char *wget_iri_get_connection_part(const wget_iri *iri, wget_buffer *buf) |
794 | 36.3k | { |
795 | 36.3k | if (iri) { |
796 | 36.3k | if (wget_ip_is_family(iri->host, WGET_NET_FAMILY_IPV6)) |
797 | 4 | wget_buffer_printf_append(buf, "%s://[%s]", schemes[iri->scheme].name, iri->host); |
798 | 36.3k | else |
799 | 36.3k | wget_buffer_printf_append(buf, "%s://%s", schemes[iri->scheme].name, iri->host); |
800 | | |
801 | 36.3k | if (iri->port_given) |
802 | 162 | wget_buffer_printf_append(buf, ":%hu", iri->port); |
803 | 36.3k | } |
804 | | |
805 | 36.3k | return buf->data; |
806 | 36.3k | } |
807 | | |
808 | | // normalize /../ and remove /./ |
809 | | |
810 | | static size_t WGET_GCC_NONNULL_ALL normalize_path(char *path) |
811 | 44.8k | { |
812 | 44.8k | char *p1 = path, *p2 = path; |
813 | | |
814 | 44.8k | debug_printf("path %s ->\n", path); |
815 | | |
816 | | // skip ./ and ../ at the beginning of the path |
817 | 53.9k | for (;;) { |
818 | 53.9k | if (*p2 == '/') |
819 | 4.78k | p2++; |
820 | 49.1k | else if (*p2 == '.') { |
821 | 6.20k | if (p2[1] == '/') |
822 | 1.34k | p2 += 2; |
823 | 4.86k | else if (p2[1] == '.') { |
824 | 3.08k | if (p2[2] == '/') |
825 | 1.14k | p2 += 3; |
826 | 1.93k | else if (!p2[2]) |
827 | 1.12k | p2 += 2; |
828 | 807 | else |
829 | 807 | break; |
830 | 3.08k | } |
831 | 1.78k | else if (!p2[1]) |
832 | 728 | p2++; |
833 | 1.05k | else |
834 | 1.05k | break; |
835 | 6.20k | } else |
836 | 42.9k | break; |
837 | 53.9k | } |
838 | | |
839 | | // normalize path but stop at query or fragment |
840 | 2.23M | while (*p2 && *p2 != '?' && *p2 != '#') { |
841 | 2.18M | if (*p2 == '/') { |
842 | 42.0k | if (p2[1] == '.') { |
843 | 22.4k | if (!strncmp(p2, "/../", 4)) { |
844 | | // go one level up |
845 | 8.39k | p2 += 3; |
846 | 117k | while (p1 > path && *--p1 != '/'); |
847 | 14.0k | } else if (!strcmp(p2, "/..")) { |
848 | 1.86k | p2 += 3; |
849 | 16.0k | while (p1 > path && *--p1 != '/'); |
850 | 1.86k | if (p1 > path) *p1++='/'; |
851 | 12.1k | } else if (!strncmp(p2, "/./", 3)) { |
852 | 5.55k | p2 += 2; |
853 | 6.64k | } else if (!strcmp(p2, "/.")) { |
854 | 1.81k | p2 += 2; |
855 | 1.81k | if (p1 > path) *p1++='/'; |
856 | 1.81k | } else |
857 | 4.83k | *p1++ = *p2++; |
858 | 22.4k | } else if (p1 == path) |
859 | 2.82k | p2++; // avoid leading slash |
860 | 16.7k | else if (p2[1] == '/') |
861 | 4.01k | p2++; // double slash to single slash |
862 | 12.7k | else |
863 | 12.7k | *p1++ = *p2++; |
864 | 42.0k | } else |
865 | 2.14M | *p1++ = *p2++; |
866 | 2.18M | } |
867 | | |
868 | 44.8k | if (p1 != p2) { |
869 | 203k | while (*p2) |
870 | 192k | *p1++ = *p2++; |
871 | | |
872 | 10.6k | *p1 = 0; |
873 | 34.1k | } else { |
874 | 34.1k | p1 += strlen(p1); |
875 | 34.1k | } |
876 | | |
877 | 44.8k | debug_printf(" %s\n", path); |
878 | | |
879 | 44.8k | return p1 - path; |
880 | 44.8k | } |
881 | | |
882 | | // create an absolute URI from a base + relative URI |
883 | | |
884 | | //char *iri_relative_to_absolute(IRI *iri, const char *tag, const char *val, size_t len, char *dst, size_t dst_size) |
885 | | /** |
886 | | * \param[in] base A base IRI |
887 | | * \param[in] val A path, or another URI |
888 | | * \param[in] len Length of the string \p val or -1 |
889 | | * \param[in] buf Destination buffer, where the result will be copied. |
890 | | * \return A new URI (string) which is based on the base IRI \p base provided, or NULL in case of error. |
891 | | * |
892 | | * Calculates a new URI which is based on the provided IRI \p base. |
893 | | * |
894 | | * Taking the IRI \p base as a starting point, a new URI is created with the path \p val, which may be |
895 | | * a relative or absolute path, or even a whole URI. The result is returned as a string, and if the buffer |
896 | | * \p buf is provided, it is also placed there. |
897 | | * |
898 | | * If \p val is an absolute path (it begins with a `/`), it is normalized first. Then the provided IRI's |
899 | | * path is replaced by that new path. If it's a relative path, the file name of the \p base IRI's path |
900 | | * is replaced by that path. Finally, if \p val begins with a scheme (such as `https://`) that string is returned |
901 | | * untouched, and placed in the buffer if provided. |
902 | | * |
903 | | * If \p base is NULL, then \p val must itself be an absolute URI. Likewise, if \p buf is NULL, |
904 | | * then \p val must also be an absolute URI. |
905 | | * |
906 | | * if \p len is `-1`, the length of \p val will be the result from `strlen(val)`. |
907 | | */ |
908 | | const char *wget_iri_relative_to_abs(const wget_iri *base, const char *val, size_t len, wget_buffer *buf) |
909 | 45.1k | { |
910 | 45.1k | if (len == (size_t) -1) |
911 | 18.0k | len = strlen(val); |
912 | | |
913 | 45.1k | if (*val == '/') { |
914 | 6.09k | if (base) { |
915 | 4.80k | char tmp[4096], *path = tmp; |
916 | | |
917 | 4.80k | if (len >= sizeof(tmp)) { |
918 | 51 | path = wget_malloc(len + 1); |
919 | 51 | if (!path) |
920 | 0 | return NULL; |
921 | 51 | } |
922 | | |
923 | | // strlcpy or snprintf are ineffective here since they do strlen(val), which might be large |
924 | 4.80k | wget_strscpy(path, val, len + 1); |
925 | | |
926 | 4.80k | if (len >= 2 && val[1] == '/') { |
927 | 2.05k | char *p; |
928 | | |
929 | | // absolute URI without scheme: //authority/path... |
930 | 2.05k | if ((p = strchr(path + 2, '/'))) |
931 | 1.44k | normalize_path(p + 1); |
932 | | |
933 | 2.05k | wget_buffer_strcpy(buf, schemes[base->scheme].name); |
934 | 2.05k | wget_buffer_strcat(buf, ":"); |
935 | 2.05k | wget_buffer_strcat(buf, path); |
936 | 2.75k | } else { |
937 | | // absolute path |
938 | 2.75k | normalize_path(path); |
939 | | |
940 | 2.75k | wget_buffer_reset(buf); |
941 | 2.75k | wget_iri_get_connection_part(base, buf); |
942 | 2.75k | wget_buffer_strcat(buf, "/"); |
943 | 2.75k | wget_buffer_strcat(buf, path); |
944 | 2.75k | } |
945 | | |
946 | 4.80k | if (path != tmp) |
947 | 51 | xfree(path); |
948 | 4.80k | } else { |
949 | 1.28k | return NULL; |
950 | 1.28k | } |
951 | 39.0k | } else { |
952 | | // see if URI begins with a scheme: |
953 | 39.0k | if (memchr(val, ':', len)) { |
954 | | // absolute URI |
955 | 7.75k | if (buf) { |
956 | 5.28k | wget_buffer_memcpy(buf, val, len); |
957 | 5.28k | } else { |
958 | 2.47k | return val; |
959 | 2.47k | } |
960 | 31.3k | } else if (base) { |
961 | | // relative path |
962 | 26.0k | const char *lastsep = base->path ? strrchr(base->path, '/') : NULL; |
963 | 26.0k | wget_buffer_reset(buf); |
964 | 26.0k | wget_iri_get_connection_part(base, buf); |
965 | 26.0k | wget_buffer_strcat(buf, "/"); |
966 | | |
967 | 26.0k | size_t tmp_len = buf->length; |
968 | | |
969 | 26.0k | if (lastsep) |
970 | 0 | wget_buffer_memcat(buf, base->path, lastsep - base->path + 1); |
971 | | |
972 | 26.0k | if (len) |
973 | 13.7k | wget_buffer_memcat(buf, val, len); |
974 | | |
975 | 26.0k | buf->length = normalize_path(buf->data + tmp_len) + tmp_len; |
976 | 26.0k | } else if (val[len] == 0) { |
977 | 5.25k | return val; |
978 | 5.25k | } else { |
979 | 0 | return NULL; |
980 | 0 | } |
981 | 39.0k | } |
982 | | |
983 | 36.1k | return likely(buf) ? buf->data : NULL; |
984 | 45.1k | } |
985 | | |
986 | | /** |
987 | | * \param[in] base The base IRI |
988 | | * \param[in] url A relative/absolute path (or a URI) to be appended to \p base |
989 | | * \param[in] encoding The encoding of \p url (e.g. "utf-8" or "iso-8859-1") |
990 | | * \return A new IRI |
991 | | * |
992 | | * Generate a new IRI by using the provided IRI \p base as a base and the path \p url. |
993 | | * |
994 | | * This is equivalent to: |
995 | | * |
996 | | * wget_iri *iri = wget_iri_parse(wget_iri_relative_to_abs(base, url, strlen(url), NULL), encoding); |
997 | | * return iri; |
998 | | * |
999 | | * As such, \p url can be a relative or absolute path, or another URI. |
1000 | | * |
1001 | | * If \p base is NULL, then the parameter \p url must itself be an absolute URI. |
1002 | | */ |
1003 | | wget_iri *wget_iri_parse_base(const wget_iri *base, const char *url, const char *encoding, int flags) |
1004 | 18.0k | { |
1005 | 18.0k | wget_iri *iri; |
1006 | | |
1007 | 18.0k | if (base) { |
1008 | 9.01k | wget_buffer buf; |
1009 | 9.01k | char sbuf[256]; |
1010 | | |
1011 | 9.01k | wget_buffer_init(&buf, sbuf, sizeof(sbuf)); |
1012 | 9.01k | iri = wget_iri_parse_ex(wget_iri_relative_to_abs(base, url, (size_t) -1, &buf), encoding, flags); |
1013 | 9.01k | wget_buffer_deinit(&buf); |
1014 | 9.01k | } else { |
1015 | | // no base: just check URL for being an absolute URI |
1016 | 9.01k | iri = wget_iri_parse_ex(wget_iri_relative_to_abs(NULL, url, (size_t) -1, NULL), encoding, flags); |
1017 | 9.01k | } |
1018 | | |
1019 | 18.0k | return iri; |
1020 | 18.0k | } |
1021 | | |
1022 | | // RFC conform comparison as described in https://tools.ietf.org/html/rfc2616#section-3.2.3 |
1023 | | /** |
1024 | | * \param[in] iri1 An IRI |
1025 | | * \param[in] iri2 Another IRI |
1026 | | * \return 0 if both IRIs are equal according to RFC 2616 or a non-zero value otherwise |
1027 | | * |
1028 | | * Compare two IRIs. |
1029 | | * |
1030 | | * Comparison is performed according to [RFC 2616, sect. 3.2.3](https://tools.ietf.org/html/rfc2616#section-3.2.3). |
1031 | | * |
1032 | | * This function uses wget_strcasecmp() to compare the various parts of the IRIs so a non-zero negative return value |
1033 | | * indicates that \p iri1 is less than \p iri2, whereas a positive value indicates \p iri1 is greater than \p iri2. |
1034 | | */ |
1035 | | int wget_iri_compare(const wget_iri *iri1, const wget_iri *iri2) |
1036 | 9.01k | { |
1037 | 9.01k | int n; |
1038 | | |
1039 | 9.01k | if (!iri1) { |
1040 | 1.43k | if (!iri2) |
1041 | 850 | return 0; |
1042 | 584 | else |
1043 | 584 | return -1; |
1044 | 7.58k | } else if (!iri2) |
1045 | 12 | return 1; |
1046 | | |
1047 | | // info_printf("iri %p %p %s:%s %s:%s\n",iri1,iri2,iri1->scheme,iri1->port,iri2->scheme,iri2->port); |
1048 | | |
1049 | | /* |
1050 | | if (!iri1->path) { |
1051 | | // if (iri2->path && strcmp(iri2->path, "/")) |
1052 | | if (iri2->path) |
1053 | | return -1; |
1054 | | } |
1055 | | else if (!iri2->path) { |
1056 | | // if (iri1->path && strcmp(iri1->path, "/")) |
1057 | | if (iri1->path) |
1058 | | return 1; |
1059 | | } |
1060 | | */ |
1061 | 7.57k | if ((n = wget_strcasecmp(iri1->path, iri2->path))) |
1062 | 5.13k | return n; |
1063 | | |
1064 | 2.43k | if ((n = wget_strcasecmp(iri1->query, iri2->query))) |
1065 | 278 | return n; |
1066 | | |
1067 | 2.15k | if (iri1->scheme != iri2->scheme) |
1068 | 0 | return iri1->scheme < iri2->scheme ? -1 : 1; |
1069 | | |
1070 | 2.15k | if ((n = iri1->port - iri2->port)) |
1071 | 84 | return n; |
1072 | | |
1073 | | // host is already lowercase, no need to call strcasecmp() |
1074 | 2.07k | if ((n = strcmp(iri1->host, iri2->host))) |
1075 | 426 | return n; |
1076 | | |
1077 | | // if ((n = wget_strcasecmp(iri1->fragment, iri2->fragment))) |
1078 | | // return n; |
1079 | | |
1080 | 1.64k | return 0; |
1081 | 2.07k | } |
1082 | | |
1083 | | /** |
1084 | | * \param[in] src A string, whose reserved characters are to be percent-encoded |
1085 | | * \param[in] buf A buffer where the result will be copied. |
1086 | | * \return The contents of the buffer \p buf after \p src has been encoded. |
1087 | | * |
1088 | | * Escapes (using percent-encoding) all the reserved characters in the string \p src. |
1089 | | * |
1090 | | * If \p src is NULL, the contents of the buffer \p buf are returned. \p buf cannot be NULL. |
1091 | | */ |
1092 | | const char *wget_iri_escape(const char *src, wget_buffer *buf) |
1093 | 20.3k | { |
1094 | 20.3k | const char *begin; |
1095 | | |
1096 | 20.3k | if (!src) |
1097 | 0 | return buf->data; |
1098 | | |
1099 | 1.70M | for (begin = src; *src; src++) { |
1100 | 1.68M | if (!iri_isunreserved(*src)) { |
1101 | 1.36M | if (begin != src) |
1102 | 54.5k | wget_buffer_memcat(buf, begin, src - begin); |
1103 | 1.36M | begin = src + 1; |
1104 | 1.36M | wget_buffer_printf_append(buf, "%%%02X", (unsigned char)*src); |
1105 | 1.36M | } |
1106 | 1.68M | } |
1107 | | |
1108 | 20.3k | if (begin != src) |
1109 | 10.6k | wget_buffer_memcat(buf, begin, src - begin); |
1110 | | |
1111 | 20.3k | return buf->data; |
1112 | 20.3k | } |
1113 | | |
1114 | | /** |
1115 | | * \param[in] src A string, whose reserved characters are to be percent-encoded |
1116 | | * \param[in] buf A buffer where the result will be copied. |
1117 | | * \return The contents of the buffer \p buf after \p src has been encoded |
1118 | | * as described in https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1. |
1119 | | * |
1120 | | * Escapes the path part of the URI suitable for GET/POST requests (origin-form). |
1121 | | * origin-form = absolute-path [ "?" query ] |
1122 | | * path-absolute = "/" [ segment-nz *( "/" segment ) ] |
1123 | | * segment-nz = 1*pchar |
1124 | | * segment = *pchar |
1125 | | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
1126 | | */ |
1127 | | const char *wget_iri_escape_path(const char *src, wget_buffer *buf) |
1128 | 10.2k | { |
1129 | 10.2k | const char *begin; |
1130 | | |
1131 | 1.20M | for (begin = src; *src; src++) { |
1132 | 1.19M | if (!(iri_isunreserved(*src) || iri_issubdelim(*src) || *src == '/' || *src == ':' || *src == '@')) { |
1133 | 902k | if (begin != src) |
1134 | 46.7k | wget_buffer_memcat(buf, begin, src - begin); |
1135 | 902k | begin = src + 1; |
1136 | 902k | wget_buffer_printf_append(buf, "%%%02X", (unsigned char)*src); |
1137 | 902k | } |
1138 | 1.19M | } |
1139 | | |
1140 | 10.2k | if (begin != src) |
1141 | 5.40k | wget_buffer_memcat(buf, begin, src - begin); |
1142 | | |
1143 | 10.2k | return buf->data; |
1144 | 10.2k | } |
1145 | | |
1146 | | /** |
1147 | | * \param[in] src A string, whose reserved characters are to be percent-encoded |
1148 | | * \param[in] buf A buffer where the result will be copied. |
1149 | | * \return The contents of the buffer \p buf after \p src has been encoded. |
1150 | | * |
1151 | | * Escapes (using percent-encoding) all the reserved characters in the string \p src |
1152 | | * (just like wget_iri_escape()), but **excluding the equal sign `=` and the ampersand `&`**. |
1153 | | * This function is thus ideally suited for query parts of URIs. |
1154 | | */ |
1155 | | const char *wget_iri_escape_query(const char *src, wget_buffer *buf) |
1156 | 9.01k | { |
1157 | 9.01k | const char *begin; |
1158 | | |
1159 | 931k | for (begin = src; *src; src++) { |
1160 | 922k | if (!iri_isunreserved(*src) && *src != '=' && *src != '&') { |
1161 | 724k | if (begin != src) |
1162 | 45.3k | wget_buffer_memcat(buf, begin, src - begin); |
1163 | 724k | begin = src + 1; |
1164 | 724k | if (*src == ' ') |
1165 | 432 | wget_buffer_memcat(buf, "+", 1); |
1166 | 724k | else |
1167 | 724k | wget_buffer_printf_append(buf, "%%%02X", (unsigned char)*src); |
1168 | 724k | } |
1169 | 922k | } |
1170 | | |
1171 | 9.01k | if (begin != src) |
1172 | 2.47k | wget_buffer_memcat(buf, begin, src - begin); |
1173 | | |
1174 | 9.01k | return buf->data; |
1175 | 9.01k | } |
1176 | | |
1177 | | /** |
1178 | | * \param[in] iri An IRI |
1179 | | * \param[in] buf A buffer, where the resulting string will be put |
1180 | | * \return The contents of the buffer \p buf |
1181 | | * |
1182 | | * Return the host part of the provided IRI. It is placed in the buffer \p buf |
1183 | | * and also returned as a `const char *`. |
1184 | | * |
1185 | | * The host is escaped using wget_iri_escape(). |
1186 | | */ |
1187 | | const char *wget_iri_get_escaped_host(const wget_iri *iri, wget_buffer *buf) |
1188 | 11.3k | { |
1189 | 11.3k | return wget_iri_escape(iri->host, buf); |
1190 | 11.3k | } |
1191 | | |
1192 | | /** |
1193 | | * \param[in] iri An IRI |
1194 | | * \param[in] buf A buffer, where the resulting string will be put |
1195 | | * \return The contents of the buffer \p buf |
1196 | | * |
1197 | | * Return the resource string, suitable for use in HTTP requests. |
1198 | | * Details: |
1199 | | * https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1 |
1200 | | * https://datatracker.ietf.org/doc/html/rfc7230#section-2.7 |
1201 | | * https://datatracker.ietf.org/doc/html/rfc3986#section-3.3 |
1202 | | * |
1203 | | * The resource string is comprised of the path, plus the query part, if present. Example: |
1204 | | * |
1205 | | * /foo/bar/?param_1=one¶m_2=two |
1206 | | * |
1207 | | * Both the path and the query are escaped using wget_iri_escape_path() and |
1208 | | * wget_iri_escape_query(), respectively. |
1209 | | * |
1210 | | * The resulting string is placed in the buffer \p buf and also returned as a `const char *`. |
1211 | | */ |
1212 | | const char *wget_iri_get_escaped_resource(const wget_iri *iri, wget_buffer *buf) |
1213 | 11.3k | { |
1214 | 11.3k | if (iri->path) { |
1215 | 1.19k | if (!iri->keep_path_as_is) |
1216 | 1.19k | wget_iri_escape_path(iri->path, buf); |
1217 | 0 | else |
1218 | 0 | wget_buffer_strcat(buf, iri->path); |
1219 | 1.19k | } |
1220 | | |
1221 | | // Do not actually escape the query field. This part of the URL *MAY* |
1222 | | // contain reserved characters which should be passed on as-is and without |
1223 | | // escaping them. This is according to the rules laid out in RFC 2616 and |
1224 | | // RFC 7230. But we have to replace spaces in any case. |
1225 | 11.3k | if (iri->query) { |
1226 | 698 | wget_buffer_memcat(buf, "?", 1); |
1227 | 16.3k | for (const char *p = iri->query; *p; p++) |
1228 | 15.6k | wget_buffer_memcat(buf, *p == ' ' ? "+" : p, 1); |
1229 | 698 | } |
1230 | | |
1231 | 11.3k | return buf->data; |
1232 | 11.3k | } |
1233 | | |
1234 | | /** |
1235 | | * \param[in] iri An IRI |
1236 | | * \param[in] buf A buffer, where the resulting string will be put |
1237 | | * \param[in] encoding Character set the string should be converted to |
1238 | | * \return The contents of the buffer \p buf |
1239 | | * |
1240 | | * Get the path part of the provided IRI. |
1241 | | * |
1242 | | * The path is appended to \p buf. If \p buf is non-empty and does not end with |
1243 | | * a path separator (`/`), then one is added before the path is appended to \p |
1244 | | * buf. |
1245 | | * |
1246 | | * If \p encoding is provided, this function will try to convert the path (which is originally |
1247 | | * in UTF-8) to that encoding. |
1248 | | */ |
1249 | | |
1250 | | char *wget_iri_get_path(const wget_iri *iri, wget_buffer *buf, const char *encoding) |
1251 | 7.58k | { |
1252 | 7.58k | if (buf->length != 0 && buf->data[buf->length - 1] != '/') |
1253 | 7.44k | wget_buffer_memcat(buf, "/", 1); |
1254 | | |
1255 | 7.58k | if (iri->path) { |
1256 | 1.19k | if (wget_strcasecmp_ascii(encoding, "utf-8")) { |
1257 | 597 | char *fname; |
1258 | | |
1259 | 597 | if ((fname = wget_utf8_to_str(iri->path, encoding))) { |
1260 | 597 | wget_buffer_strcat(buf, fname); |
1261 | 597 | xfree(fname); |
1262 | 597 | } else { |
1263 | | // conversion failed, keep original string |
1264 | 0 | wget_buffer_strcat(buf, iri->path); |
1265 | 0 | } |
1266 | 597 | } else { |
1267 | 597 | wget_buffer_strcat(buf, iri->path); |
1268 | 597 | } |
1269 | 1.19k | } |
1270 | | |
1271 | 7.58k | if ((buf->length == 0 || buf->data[buf->length - 1] == '/') && default_page) |
1272 | 7.01k | wget_buffer_memcat(buf, default_page, default_page_length); |
1273 | | |
1274 | 7.58k | return buf->data; |
1275 | 7.58k | } |
1276 | | |
1277 | | /** |
1278 | | * \param[in] iri An IRI |
1279 | | * \param[in] buf A buffer, where the resulting string will be put |
1280 | | * \param[in] encoding Character set the string should be converted to |
1281 | | * \return The contents of the buffer \p buf |
1282 | | * |
1283 | | * Take the query part, and escape the path separators (`/`), so that it can be used as part |
1284 | | * of a filename. |
1285 | | * |
1286 | | * The resulting string will be placed in the buffer \p buf and also returned as a `const char *`. |
1287 | | * If the provided IRI has no query part, then the original contents of \p buf are returned and \p buf |
1288 | | * is kept untouched. |
1289 | | * |
1290 | | * If \p encoding is provided, this function will try to convert the query (which is originally |
1291 | | * in UTF-8) to that encoding. |
1292 | | */ |
1293 | | char *wget_iri_get_query_as_filename(const wget_iri *iri, wget_buffer *buf, const char *encoding) |
1294 | 15.1k | { |
1295 | 15.1k | if (iri->query) { |
1296 | 1.39k | const char *query; |
1297 | 1.39k | int allocated = 0; |
1298 | | |
1299 | 1.39k | wget_buffer_memcat(buf, "?", 1); |
1300 | | |
1301 | 1.39k | if (wget_strcasecmp_ascii(encoding, "utf-8")) { |
1302 | 698 | if ((query = wget_utf8_to_str(iri->query, encoding))) |
1303 | 698 | allocated = 1; |
1304 | 0 | else |
1305 | 0 | query = iri->query; |
1306 | 698 | } else { |
1307 | 698 | query = iri->query; |
1308 | 698 | } |
1309 | | |
1310 | 1.39k | int slashes = 0; |
1311 | 1.39k | const char *src = query; |
1312 | | |
1313 | | // count slashes in query string |
1314 | 4.42k | while ((src = strchr(src, '/'))) { |
1315 | 3.02k | slashes++; |
1316 | 3.02k | src++; |
1317 | 3.02k | } |
1318 | | |
1319 | 1.39k | if (slashes) { |
1320 | | // escape slashes to use query as part of a filename |
1321 | 720 | const char *begin; |
1322 | | |
1323 | 17.1k | for (src = begin = query; *src; src++) { |
1324 | 16.4k | if (*src == '/') { |
1325 | 3.02k | if (begin != src) |
1326 | 1.23k | wget_buffer_memcat(buf, begin, src - begin); |
1327 | 3.02k | begin = src + 1; |
1328 | 3.02k | wget_buffer_memcat(buf, "%2F", 3); |
1329 | 3.02k | } |
1330 | 16.4k | } |
1331 | | |
1332 | 720 | if (begin != src) |
1333 | 572 | wget_buffer_memcat(buf, begin, src - begin); |
1334 | 720 | } else { |
1335 | 676 | wget_buffer_strcat(buf, query); |
1336 | 676 | } |
1337 | | |
1338 | 1.39k | if (allocated) |
1339 | 698 | xfree(query); |
1340 | 1.39k | } |
1341 | | |
1342 | 15.1k | return buf->data; |
1343 | 15.1k | } |
1344 | | |
1345 | | /** |
1346 | | * \param[in] iri An IRI |
1347 | | * \param[in] buf A buffer, where the resulting string will be put |
1348 | | * \param[in] encoding Character set the string should be converted to |
1349 | | * \return The contents of the buffer \p buf |
1350 | | * |
1351 | | * Get the filename of the path of the provided IRI. |
1352 | | * |
1353 | | * This is similar to wget_iri_get_path(), but instead of returning the whole path |
1354 | | * it only returns the substring after the last occurrence of `/`. In other words, the |
1355 | | * filename of the path. |
1356 | | * |
1357 | | * This is also known as the "basename" in the UNIX world, and the output of this function |
1358 | | * would be equivalent to the output of the `basename(1)` tool. |
1359 | | * |
1360 | | * The path is copied into \p buf if it's empty. If the buffer \p buf is not empty, |
1361 | | * it is appended to it after a path separator (`/`). |
1362 | | * |
1363 | | * If \p encoding is provided, this function will try to convert the path (which is originally |
1364 | | * in UTF-8) to that encoding. |
1365 | | */ |
1366 | | char *wget_iri_get_basename(const wget_iri *iri, wget_buffer *buf, const char *encoding, int flags) |
1367 | 7.58k | { |
1368 | 7.58k | if (iri->path) { |
1369 | 1.19k | const char *fname; |
1370 | | |
1371 | 1.19k | if (wget_strcasecmp_ascii(encoding, "utf-8")) { |
1372 | 597 | const char *p; |
1373 | | |
1374 | 597 | if ((p = strrchr(iri->path, '/'))) { |
1375 | 194 | if (!(fname = wget_utf8_to_str(p + 1, encoding))) |
1376 | 0 | wget_buffer_strcat(buf, p + 1); // conversion failed, keep original string |
1377 | 403 | } else { |
1378 | 403 | if (!(fname = wget_utf8_to_str(iri->path, encoding))) |
1379 | 0 | wget_buffer_strcat(buf, iri->path); // conversion failed, keep original string |
1380 | 403 | } |
1381 | | |
1382 | 597 | if (fname) { |
1383 | | // conversion succeeded |
1384 | 597 | wget_buffer_strcat(buf, fname); |
1385 | 597 | xfree(fname); |
1386 | 597 | } |
1387 | 597 | } else { |
1388 | 597 | if ((fname = strrchr(iri->path, '/'))) |
1389 | 194 | wget_buffer_strcat(buf, fname + 1); |
1390 | 403 | else |
1391 | 403 | wget_buffer_strcat(buf, iri->path); |
1392 | 597 | } |
1393 | 1.19k | } |
1394 | | |
1395 | 7.58k | if ((buf->length == 0 || buf->data[buf->length - 1] == '/') && default_page) |
1396 | 0 | wget_buffer_memcat(buf, default_page, default_page_length); |
1397 | | |
1398 | 7.58k | if (flags & WGET_IRI_WITH_QUERY) |
1399 | 7.58k | return wget_iri_get_query_as_filename(iri, buf, encoding); |
1400 | | |
1401 | 0 | return buf->data; |
1402 | 7.58k | } |
1403 | | |
1404 | | // escaping: see https://tools.ietf.org/html/rfc2396#2 following (especially 2.4.2) |
1405 | | /*const char *iri_escape(const char *uri) |
1406 | | { |
1407 | | int esc = 0; |
1408 | | const char *p; |
1409 | | |
1410 | | for (p = uri; *p; p++) { |
1411 | | if (*p == '%') { |
1412 | | if ((isxdigit(p[1]) && isxdigit(p[2])) || p[1] == '%') |
1413 | | return uri; // assume that URI is already escaped |
1414 | | esc++; |
1415 | | } else if () |
1416 | | } |
1417 | | } |
1418 | | */ |
1419 | | |
1420 | | void wget_iri_set_defaultpage(const char *page) |
1421 | 14.1k | { |
1422 | 14.1k | default_page = page; |
1423 | 14.1k | default_page_length = default_page ? strlen(default_page) : 0; |
1424 | 14.1k | } |
1425 | | |
1426 | | /** |
1427 | | * \param scheme The scheme for the new default port |
1428 | | * \param port The new default port value for the given scheme |
1429 | | * \return 0: success -1: Unknown scheme |
1430 | | * |
1431 | | * Set the default \p port for the given \p scheme. |
1432 | | */ |
1433 | | int wget_iri_set_defaultport(wget_iri_scheme scheme, uint16_t port) |
1434 | 19.5k | { |
1435 | 19.5k | if ((unsigned) scheme < countof(schemes)) { |
1436 | 19.5k | schemes[scheme].port = port; |
1437 | 19.5k | return 0; |
1438 | 19.5k | } |
1439 | | |
1440 | 0 | return -1; |
1441 | 19.5k | } |
1442 | | |
1443 | | /** |
1444 | | * \param[in] iri An IRI |
1445 | | * \param[in] scheme A scheme, such as `http` or `https`. |
1446 | | * \return The original scheme of IRI (ie. before the replacement) |
1447 | | * |
1448 | | * Set the scheme of the provided IRI. The IRI's original scheme |
1449 | | * is replaced by the new one. |
1450 | | * |
1451 | | * If the IRI was using a default port (such as 80 for HTTP or 443 for HTTPS) |
1452 | | * that port is modified as well to match the default port of the new scheme. |
1453 | | * Otherwise the port is left untouched. |
1454 | | */ |
1455 | | wget_iri_scheme wget_iri_set_scheme(wget_iri *iri, wget_iri_scheme scheme) |
1456 | 7.58k | { |
1457 | 7.58k | wget_iri_scheme old_scheme = iri->scheme; |
1458 | | |
1459 | 7.58k | if ((unsigned) scheme < countof(schemes) && iri->scheme != scheme) { |
1460 | 7.58k | iri->scheme = scheme; |
1461 | | |
1462 | | // If the IRI is using the default port, also change it |
1463 | 7.58k | if (iri->port == schemes[old_scheme].port) |
1464 | 7.42k | iri->port = schemes[scheme].port; |
1465 | | |
1466 | 7.58k | size_t old_scheme_len = strlen(schemes[old_scheme].name); |
1467 | 7.58k | size_t scheme_len = strlen(schemes[scheme].name); |
1468 | | |
1469 | 7.58k | if (strncmp(iri->uri, schemes[old_scheme].name, old_scheme_len) == 0 && iri->uri[old_scheme_len] == ':') { |
1470 | 7.57k | memmove((char *)iri->uri + scheme_len, iri->uri + old_scheme_len, iri->msize - old_scheme_len); |
1471 | 7.57k | memcpy((char *)iri->uri, schemes[scheme].name, scheme_len); |
1472 | 7.57k | } |
1473 | | |
1474 | 7.58k | if (iri->userinfo) { |
1475 | 122 | xfree(iri->safe_uri); |
1476 | 122 | iri->safe_uri = create_safe_uri(iri); |
1477 | 7.45k | } else { |
1478 | 7.45k | iri->safe_uri = iri->uri; |
1479 | 7.45k | } |
1480 | 7.58k | } |
1481 | | |
1482 | 7.58k | return old_scheme; |
1483 | 7.58k | } |
1484 | | |
1485 | | static char *create_safe_uri(wget_iri *iri) |
1486 | 6.11k | { |
1487 | 6.11k | if (!iri || !iri->uri) |
1488 | 0 | return NULL; |
1489 | | |
1490 | 6.11k | wget_buffer *buf = wget_buffer_alloc(strlen(iri->uri)); |
1491 | 6.11k | if (!buf) |
1492 | 0 | return NULL; |
1493 | | |
1494 | 6.11k | wget_buffer_printf(buf, "%s://%s", schemes[iri->scheme].name, iri->host); |
1495 | | |
1496 | 6.11k | if (iri->path) { |
1497 | 839 | wget_buffer_strcat(buf, "/"); |
1498 | 839 | wget_buffer_strcat(buf, iri->path); |
1499 | 839 | } |
1500 | 6.11k | if (iri->query) { |
1501 | 717 | wget_buffer_strcat(buf, "?"); |
1502 | 717 | wget_buffer_strcat(buf, iri->query); |
1503 | 717 | } |
1504 | 6.11k | if (iri->fragment) { |
1505 | 601 | wget_buffer_strcat(buf, "#"); |
1506 | 601 | wget_buffer_strcat(buf, iri->fragment); |
1507 | 601 | } |
1508 | | |
1509 | 6.11k | char *safe_uri = buf->data; |
1510 | 6.11k | buf->data = NULL; |
1511 | 6.11k | wget_buffer_free(&buf); |
1512 | | |
1513 | 6.11k | return safe_uri; |
1514 | 6.11k | } |
1515 | | |
1516 | | /** @} */ |