qt: playlist: use item title if available
[vlc.git] / src / text / url.c
1 /*****************************************************************************
2  * url.c: URL related functions
3  *****************************************************************************
4  * Copyright (C) 2006 VLC authors and VideoLAN
5  * Copyright (C) 2008-2012 Rémi Denis-Courmont
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or
10  * (at your option) any later version.
11  *
12  * This program 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 this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20  *****************************************************************************/
21
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #include <errno.h>
27 #include <limits.h>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <assert.h>
33 #ifdef _WIN32
34 # include <io.h>
35 #endif
36
37 #include <vlc_common.h>
38 #include <vlc_memstream.h>
39 #include <vlc_url.h>
40 #include <vlc_fs.h>
41 #include <ctype.h>
42
43 char *vlc_uri_decode_duplicate (const char *str)
44 {
45     char *buf = strdup (str);
46     if (vlc_uri_decode (buf) == NULL)
47     {
48         free (buf);
49         buf = NULL;
50     }
51     return buf;
52 }
53
54 char *vlc_uri_decode (char *str)
55 {
56     char *in = str, *out = str;
57     if (in == NULL)
58         return NULL;
59
60     char c;
61     while ((c = *(in++)) != '\0')
62     {
63         if (c == '%')
64         {
65             char hex[3];
66
67             if (!(hex[0] = *(in++)) || !(hex[1] = *(in++)))
68                 return NULL;
69             hex[2] = '\0';
70             *(out++) = strtoul (hex, NULL, 0x10);
71         }
72         else
73             *(out++) = c;
74     }
75     *out = '\0';
76     return str;
77 }
78
79 static bool isurialnum(int c)
80 {
81     return ((unsigned char)(c - 'a') < 26)
82         || ((unsigned char)(c - 'A') < 26)
83         || ((unsigned char)(c - '0') < 10);
84 }
85
86 static bool isurisafe(int c)
87 {
88     /* These are the _unreserved_ URI characters (RFC3986 §2.3) */
89     return isurialnum(c) || (strchr ("-._~", c) != NULL);
90 }
91
92 static bool isurisubdelim(int c)
93 {
94     return strchr("!$&'()*+,;=", c) != NULL;
95 }
96
97 static bool isurihex(int c)
98 {   /* Same as isxdigit() but does not depend on locale and unsignedness */
99     return ((unsigned char)(c - '0') < 10)
100         || ((unsigned char)(c - 'A') < 6)
101         || ((unsigned char)(c - 'a') < 6);
102 }
103
104 static const char urihex[] = "0123456789ABCDEF";
105
106 static char *encode_URI_bytes (const char *str, size_t *restrict lenp)
107 {
108     char *buf = malloc (3 * *lenp + 1);
109     if (unlikely(buf == NULL))
110         return NULL;
111
112     char *out = buf;
113     for (size_t i = 0; i < *lenp; i++)
114     {
115         unsigned char c = str[i];
116
117         if (isurisafe (c))
118             *(out++) = c;
119         /* This is URI encoding, not HTTP forms:
120          * Space is encoded as '%20', not '+'. */
121         else
122         {
123             *(out++) = '%';
124             *(out++) = urihex[c >> 4];
125             *(out++) = urihex[c & 0xf];
126         }
127     }
128
129     *lenp = out - buf;
130     out = realloc (buf, *lenp + 1);
131     return likely(out != NULL) ? out : buf;
132 }
133
134 char *vlc_uri_encode (const char *str)
135 {
136     size_t len = strlen (str);
137     char *ret = encode_URI_bytes (str, &len);
138     if (likely(ret != NULL))
139         ret[len] = '\0';
140     return ret;
141 }
142
143 char *vlc_path2uri (const char *path, const char *scheme)
144 {
145     if (path == NULL)
146     {
147         errno = EINVAL;
148         return NULL;
149     }
150     if (scheme == NULL && !strcmp (path, "-"))
151         return strdup ("fd://0"); // standard input
152     /* Note: VLC cannot handle URI schemes without double slash after the
153      * scheme name (such as mailto: or news:). */
154
155     char *buf;
156
157 #ifdef __OS2__
158     char p[strlen (path) + 1];
159
160     for (buf = p; *path; buf++, path++)
161         *buf = (*path == '/') ? DIR_SEP_CHAR : *path;
162     *buf = '\0';
163
164     path = p;
165 #endif
166
167 #if defined (_WIN32) || defined (__OS2__)
168     /* Drive letter */
169     if (isalpha ((unsigned char)path[0]) && (path[1] == ':'))
170     {
171         if (asprintf (&buf, "%s:///%c:", scheme ? scheme : "file",
172                       path[0]) == -1)
173             buf = NULL;
174         path += 2;
175 # warning Drive letter-relative path not implemented!
176         if (path[0] != DIR_SEP_CHAR)
177         {
178             errno = ENOTSUP;
179             return NULL;
180         }
181     }
182     else
183     if (!strncmp (path, "\\\\", 2))
184     {   /* Windows UNC paths */
185         /* \\host\share\path -> file://host/share/path */
186         int hostlen = strcspn (path + 2, DIR_SEP);
187
188         if (asprintf (&buf, "file://%.*s", hostlen, path + 2) == -1)
189             buf = NULL;
190         path += 2 + hostlen;
191
192         if (path[0] == '\0')
193             return buf; /* Hostname without path */
194     }
195     else
196 #endif
197     if (path[0] != DIR_SEP_CHAR)
198     {   /* Relative path: prepend the current working directory */
199         char *cwd, *ret;
200
201         if (path[0] == '\0')
202             return NULL;
203         if ((cwd = vlc_getcwd ()) == NULL)
204             return NULL;
205         if (asprintf (&buf, "%s"DIR_SEP"%s", cwd, path) == -1)
206             buf = NULL;
207
208         free (cwd);
209         ret = (buf != NULL) ? vlc_path2uri (buf, scheme) : NULL;
210         free (buf);
211         return ret;
212     }
213     else
214     if (asprintf (&buf, "%s://", scheme ? scheme : "file") == -1)
215         buf = NULL;
216     if (buf == NULL)
217         return NULL;
218
219     /* Absolute file path */
220     assert (path[0] == DIR_SEP_CHAR);
221     do
222     {
223         size_t len = strcspn (++path, DIR_SEP);
224         path += len;
225
226         char *component = encode_URI_bytes (path - len, &len);
227         if (unlikely(component == NULL))
228         {
229             free (buf);
230             return NULL;
231         }
232         component[len] = '\0';
233
234         char *uri;
235         int val = asprintf (&uri, "%s/%s", buf, component);
236         free (component);
237         free (buf);
238         if (unlikely(val == -1))
239             return NULL;
240         buf = uri;
241     }
242     while (*path);
243
244     return buf;
245 }
246
247 char *vlc_uri2path (const char *url)
248 {
249     char *ret = NULL;
250     char *end;
251
252     char *path = strstr (url, "://");
253     if (path == NULL)
254         return NULL; /* unsupported scheme or invalid syntax */
255
256     end = memchr (url, '/', path - url);
257     size_t schemelen = ((end != NULL) ? end : path) - url;
258     path += 3; /* skip "://" */
259
260     /* Remove request parameters and/or HTML anchor if present */
261     end = path + strcspn (path, "?#");
262     path = strndup (path, end - path);
263     if (unlikely(path == NULL))
264         return NULL; /* boom! */
265
266     /* Decode path */
267     vlc_uri_decode (path);
268
269     if (schemelen == 4 && !strncasecmp (url, "file", 4))
270     {
271 #if !defined (_WIN32) && !defined (__OS2__)
272         /* Leading slash => local path */
273         if (*path == '/')
274             return path;
275         /* Local path disguised as a remote one */
276         if (!strncasecmp (path, "localhost/", 10))
277             return memmove (path, path + 9, strlen (path + 9) + 1);
278 #else
279         /* cannot start with a space */
280         if (*path == ' ')
281             goto out;
282         for (char *p = strchr (path, '/'); p; p = strchr (p + 1, '/'))
283             *p = '\\';
284
285         /* Leading backslash => local path */
286         if (*path == '\\')
287             return memmove (path, path + 1, strlen (path + 1) + 1);
288         /* Local path disguised as a remote one */
289         if (!strncasecmp (path, "localhost\\", 10))
290             return memmove (path, path + 10, strlen (path + 10) + 1);
291         /* UNC path */
292         if (*path && asprintf (&ret, "\\\\%s", path) == -1)
293             ret = NULL;
294 #endif
295         /* non-local path :-( */
296     }
297     else
298     if (schemelen == 2 && !strncasecmp (url, "fd", 2))
299     {
300         int fd = strtol (path, &end, 0);
301
302         if (*end)
303             goto out;
304
305 #if !defined( _WIN32 ) && !defined( __OS2__ )
306         switch (fd)
307         {
308             case 0:
309                 ret = strdup ("/dev/stdin");
310                 break;
311             case 1:
312                 ret = strdup ("/dev/stdout");
313                 break;
314             case 2:
315                 ret = strdup ("/dev/stderr");
316                 break;
317             default:
318                 if (asprintf (&ret, "/dev/fd/%d", fd) == -1)
319                     ret = NULL;
320         }
321 #else
322         /* XXX: Does this work on WinCE? */
323         if (fd < 2)
324             ret = strdup ("CON");
325 #endif
326     }
327
328 out:
329     free (path);
330     return ret; /* unknown scheme */
331 }
332
333 static char *vlc_idna_to_ascii (const char *);
334
335 /* RFC3987 §3.1 */
336 static char *vlc_iri2uri(const char *iri)
337 {
338     size_t a = 0, u = 0;
339
340     for (size_t i = 0; iri[i] != '\0'; i++)
341     {
342         unsigned char c = iri[i];
343
344         if (c < 128)
345             a++;
346         else
347             u++;
348     }
349
350     if (unlikely((a + u) > (SIZE_MAX / 4)))
351     {
352         errno = ENOMEM;
353         return NULL;
354     }
355
356     char *uri = malloc(a + 3 * u + 1), *p;
357     if (unlikely(uri == NULL))
358         return NULL;
359
360     for (p = uri; *iri != '\0'; iri++)
361     {
362         unsigned char c = *iri;
363
364         if (c < 128)
365             *(p++) = c;
366         else
367         {
368             *(p++) = '%';
369             *(p++) = urihex[c >> 4];
370             *(p++) = urihex[c & 0xf];
371         }
372     }
373
374     *p = '\0';
375     return uri;
376 }
377
378 static bool vlc_uri_component_validate(const char *str, const char *extras)
379 {
380     assert(str != NULL);
381
382     for (size_t i = 0; str[i] != '\0'; i++)
383     {
384         int c = str[i];
385
386         if (isurisafe(c) || isurisubdelim(c))
387             continue;
388         if (strchr(extras, c) != NULL)
389             continue;
390         if (c == '%' && isurihex(str[i + 1]) && isurihex(str[i + 2]))
391         {
392             i += 2;
393             continue;
394         }
395         return false;
396     }
397     return true;
398 }
399
400 static bool vlc_uri_host_validate(const char *str)
401 {
402     return vlc_uri_component_validate(str, ":");
403 }
404
405 static bool vlc_uri_path_validate(const char *str)
406 {
407     return vlc_uri_component_validate(str, "/@:");
408 }
409
410 static int vlc_UrlParseInner(vlc_url_t *restrict url, const char *str)
411 {
412     url->psz_protocol = NULL;
413     url->psz_username = NULL;
414     url->psz_password = NULL;
415     url->psz_host = NULL;
416     url->i_port = 0;
417     url->psz_path = NULL;
418     url->psz_option = NULL;
419     url->psz_fragment = NULL;
420     url->psz_buffer = NULL;
421     url->psz_pathbuffer = NULL;
422
423     if (str == NULL)
424     {
425         errno = EINVAL;
426         return -1;
427     }
428
429     char *buf = vlc_iri2uri(str);
430     if (unlikely(buf == NULL))
431         return -1;
432     url->psz_buffer = buf;
433
434     char *cur = buf, *next;
435     int ret = 0;
436
437     /* URI scheme */
438     next = buf;
439     while (isurialnum(*next) || memchr ("+-.", *next, 3) != NULL)
440         next++;
441
442     if (*next == ':')
443     {
444         *(next++) = '\0';
445         url->psz_protocol = cur;
446         cur = next;
447     }
448
449     /* Fragment */
450     next = strchr(cur, '#');
451     if (next != NULL)
452     {
453        *(next++) = '\0';
454        if (vlc_uri_component_validate(next, "/?"))
455            url->psz_fragment = next;
456     }
457
458     /* Query parameters */
459     next = strchr(cur, '?');
460     if (next != NULL)
461     {
462         *(next++) = '\0';
463         url->psz_option = next;
464     }
465
466     /* Authority */
467     if (strncmp(cur, "//", 2) == 0)
468     {
469         cur += 2;
470
471         /* Path */
472         next = strchr(cur, '/');
473         if (next != NULL)
474         {
475             *next = '\0'; /* temporary nul, reset to slash later */
476             url->psz_path = next;
477         }
478         /*else
479             url->psz_path = "/";*/
480
481         /* User name */
482         next = strrchr(cur, '@');
483         if (next != NULL)
484         {
485             *(next++) = '\0';
486             url->psz_username = cur;
487             cur = next;
488
489             /* Password (obsolete) */
490             next = strchr(url->psz_username, ':');
491             if (next != NULL)
492             {
493                 *(next++) = '\0';
494                 url->psz_password = next;
495                 vlc_uri_decode(url->psz_password);
496             }
497             vlc_uri_decode(url->psz_username);
498         }
499
500         /* Host name */
501         if (*cur == '[' && (next = strrchr(cur, ']')) != NULL)
502         {   /* Try IPv6 numeral within brackets */
503             *(next++) = '\0';
504             url->psz_host = strdup(cur + 1);
505
506             if (*next == ':')
507                 next++;
508             else
509                 next = NULL;
510         }
511         else
512         {
513             next = strchr(cur, ':');
514             if (next != NULL)
515                 *(next++) = '\0';
516
517             const char *host = vlc_uri_decode(cur);
518             url->psz_host = (host != NULL) ? vlc_idna_to_ascii(host) : NULL;
519         }
520
521         if (url->psz_host == NULL)
522             ret = -1;
523         else
524         if (!vlc_uri_host_validate(url->psz_host))
525         {
526             free(url->psz_host);
527             url->psz_host = NULL;
528             errno = EINVAL;
529             ret = -1;
530         }
531
532         /* Port number */
533         if (next != NULL && *next)
534         {
535             char* end;
536             unsigned long port = strtoul(next, &end, 10);
537
538             if (strchr("0123456789", *next) == NULL || *end || port > UINT_MAX)
539             {
540                 errno = EINVAL;
541                 ret = -1;
542             }
543
544             url->i_port = port;
545         }
546
547         if (url->psz_path != NULL)
548             *url->psz_path = '/'; /* restore leading slash */
549     }
550     else
551     {
552         url->psz_path = cur;
553     }
554
555     return ret;
556 }
557
558 int vlc_UrlParse(vlc_url_t *url, const char *str)
559 {
560     int ret = vlc_UrlParseInner(url, str);
561
562     if (url->psz_path != NULL && !vlc_uri_path_validate(url->psz_path))
563     {
564         url->psz_path = NULL;
565         errno = EINVAL;
566         ret = -1;
567     }
568     return ret;
569 }
570
571 static char *vlc_uri_fixup_inner(const char *str, const char *extras);
572
573 int vlc_UrlParseFixup(vlc_url_t *url, const char *str)
574 {
575     int ret = vlc_UrlParseInner(url, str);
576
577     static const char pathextras[] = "/@:";
578
579     if (url->psz_path != NULL
580      && !vlc_uri_component_validate(url->psz_path, pathextras))
581     {
582         url->psz_pathbuffer = vlc_uri_fixup_inner(url->psz_path, pathextras);
583         if (url->psz_pathbuffer == NULL)
584         {
585             url->psz_path = NULL;
586             errno = ENOMEM;
587             ret = -1;
588         }
589         else
590         {
591             url->psz_path = url->psz_pathbuffer;
592             assert(vlc_uri_path_validate(url->psz_path));
593         }
594     }
595     return ret;
596 }
597
598 void vlc_UrlClean (vlc_url_t *restrict url)
599 {
600     free (url->psz_host);
601     free (url->psz_buffer);
602     free (url->psz_pathbuffer);
603 }
604
605 /**
606  * Merge paths
607  *
608  * See IETF RFC3986 section 5.2.3 for details.
609  */
610 static char *vlc_uri_merge_paths(const char *base, const char *ref)
611 {
612     char *str;
613     int len;
614
615     if (base == NULL)
616         len = asprintf(&str, "/%s", ref);
617     else
618     {
619         const char *end = strrchr(base, '/');
620
621         if (end != NULL)
622             end++;
623         else
624             end = base;
625
626         len = asprintf(&str, "%.*s%s", (int)(end - base), base, ref);
627     }
628
629     if (unlikely(len == -1))
630         str = NULL;
631     return str;
632 }
633
634 /**
635  * Remove dot segments
636  *
637  * See IETF RFC3986 section 5.2.4 for details.
638  */
639 static char *vlc_uri_remove_dot_segments(char *str)
640 {
641     char *input = str, *output = str;
642
643     while (input[0] != '\0')
644     {
645         assert(output <= input);
646
647         if (strncmp(input, "../", 3) == 0)
648         {
649             input += 3;
650             continue;
651         }
652         if (strncmp(input, "./", 2) == 0)
653         {
654             input += 2;
655             continue;
656         }
657         if (strncmp(input, "/./", 3) == 0)
658         {
659             input += 2;
660             continue;
661         }
662         if (strcmp(input, "/.") == 0)
663         {
664             input[1] = '\0';
665             continue;
666         }
667         if (strncmp(input, "/../", 4) == 0)
668         {
669             input += 3;
670             output = memrchr(str, '/', output - str);
671             if (output == NULL)
672                 output = str;
673             continue;
674         }
675         if (strcmp(input, "/..") == 0)
676         {
677             input[1] = '\0';
678             output = memrchr(str, '/', output - str);
679             if (output == NULL)
680                 output = str;
681             continue;
682         }
683         if (strcmp(input, ".") == 0)
684         {
685             input++;
686             continue;
687         }
688         if (strcmp(input, "..") == 0)
689         {
690             input += 2;
691             continue;
692         }
693
694         if (input[0] == '/')
695             *(output++) = *(input++);
696
697         size_t len = strcspn(input, "/");
698
699         if (input != output)
700             memmove(output, input, len);
701
702         input += len;
703         output += len;
704     }
705
706     output[0] = '\0';
707     return str;
708 }
709
710 char *vlc_uri_compose(const vlc_url_t *uri)
711 {
712     struct vlc_memstream stream;
713     char *enc;
714
715     vlc_memstream_open(&stream);
716
717     if (uri->psz_protocol != NULL)
718         vlc_memstream_printf(&stream, "%s:", uri->psz_protocol);
719
720     if (uri->psz_host != NULL)
721     {
722         vlc_memstream_write(&stream, "//", 2);
723
724         if (uri->psz_username != NULL)
725         {
726             enc = vlc_uri_encode(uri->psz_username);
727             if (enc == NULL)
728                 goto error;
729
730             vlc_memstream_puts(&stream, enc);
731             free(enc);
732
733             if (uri->psz_password != NULL)
734             {
735                 enc = vlc_uri_encode(uri->psz_password);
736                 if (unlikely(enc == NULL))
737                     goto error;
738
739                 vlc_memstream_printf(&stream, ":%s", enc);
740                 free(enc);
741             }
742             vlc_memstream_putc(&stream, '@');
743         }
744
745         const char *fmt;
746
747         if (strchr(uri->psz_host, ':') != NULL)
748             fmt = (uri->i_port != 0) ? "[%s]:%d" : "[%s]";
749         else
750             fmt = (uri->i_port != 0) ? "%s:%d" : "%s";
751         /* No IDNA decoding here. Seems unnecessary, dangerous even. */
752         vlc_memstream_printf(&stream, fmt, uri->psz_host, uri->i_port);
753     }
754
755     if (uri->psz_path != NULL)
756         vlc_memstream_puts(&stream, uri->psz_path);
757     if (uri->psz_option != NULL)
758         vlc_memstream_printf(&stream, "?%s", uri->psz_option);
759     if (uri->psz_fragment != NULL)
760         vlc_memstream_printf(&stream, "#%s", uri->psz_fragment);
761
762     if (vlc_memstream_close(&stream))
763         return NULL;
764     return stream.ptr;
765
766 error:
767     if (vlc_memstream_close(&stream) == 0)
768         free(stream.ptr);
769     return NULL;
770 }
771
772 char *vlc_uri_resolve(const char *base, const char *ref)
773 {
774     vlc_url_t base_uri, rel_uri;
775     vlc_url_t tgt_uri;
776     char *pathbuf = NULL, *ret = NULL;
777
778     if (vlc_UrlParse(&rel_uri, ref))
779     {
780         vlc_UrlClean(&rel_uri);
781         return NULL;
782     }
783
784     if (rel_uri.psz_protocol != NULL)
785     {   /* Short circuit in case of absolute URI */
786         vlc_UrlClean(&rel_uri);
787         return strdup(ref);
788     }
789
790     vlc_UrlParse(&base_uri, base);
791
792     /* RFC3986 section 5.2.2 */
793     do
794     {
795         tgt_uri = rel_uri;
796         tgt_uri.psz_protocol = base_uri.psz_protocol;
797
798         if (rel_uri.psz_host != NULL)
799             break;
800
801         tgt_uri.psz_username = base_uri.psz_username;
802         tgt_uri.psz_password = base_uri.psz_password;
803         tgt_uri.psz_host = base_uri.psz_host;
804         tgt_uri.i_port = base_uri.i_port;
805
806         if (rel_uri.psz_path == NULL || rel_uri.psz_path[0] == '\0')
807         {
808             tgt_uri.psz_path = base_uri.psz_path;
809             if (rel_uri.psz_option == NULL)
810                 tgt_uri.psz_option = base_uri.psz_option;
811             break;
812         }
813
814         if (rel_uri.psz_path[0] == '/')
815             break;
816
817         pathbuf = vlc_uri_merge_paths(base_uri.psz_path, rel_uri.psz_path);
818         if (unlikely(pathbuf == NULL))
819             goto error;
820
821         tgt_uri.psz_path = pathbuf;
822     }
823     while (0);
824
825     if (tgt_uri.psz_path != NULL)
826         vlc_uri_remove_dot_segments(tgt_uri.psz_path);
827
828     ret = vlc_uri_compose(&tgt_uri);
829 error:
830     free(pathbuf);
831     vlc_UrlClean(&base_uri);
832     vlc_UrlClean(&rel_uri);
833     return ret;
834 }
835
836 static char *vlc_uri_fixup_inner(const char *str, const char *extras)
837 {
838     assert(str && extras);
839
840     bool encode_percent = false;
841     for (size_t i = 0; str[i] != '\0'; i++)
842         if (str[i] == '%' && !(isurihex(str[i+1]) && isurihex(str[i+2])))
843         {
844             encode_percent = true;
845             break;
846         }
847
848     struct vlc_memstream stream;
849
850     vlc_memstream_open(&stream);
851
852     for (size_t i = 0; str[i] != '\0'; i++)
853     {
854         unsigned char c = str[i];
855
856         if (isurisafe(c) || isurisubdelim(c) || (strchr(extras, c) != NULL)
857          || (c == '%' && !encode_percent))
858             vlc_memstream_putc(&stream, c);
859         else
860             vlc_memstream_printf(&stream, "%%%02hhX", c);
861     }
862
863     if (vlc_memstream_close(&stream))
864         return NULL;
865     return stream.ptr;
866 }
867
868 static void vlc_uri_putc(struct vlc_memstream *s, int c, const char *extras)
869 {
870     if (isurisafe(c) || isurisubdelim(c) || (strchr(extras, c) != NULL))
871         vlc_memstream_putc(s, c);
872     else
873         vlc_memstream_printf(s, "%%%02hhX", (unsigned char)c);
874 }
875
876 char *vlc_uri_fixup(const char *str)
877 {
878     assert(str != NULL);
879
880     /* If percent sign is consistently followed by two hexadecimal digits,
881      * then URL encoding must be assumed.
882      * Otherwise, the percent sign itself must be URL-encoded.
883      */
884     bool encode_percent = false;
885
886     for (const char *p = str; *p != '\0'; p++)
887         if (p[0] == '%' && !(isurihex(p[1]) && isurihex(p[2])))
888         {
889             encode_percent = true;
890             break;
891         }
892
893     struct vlc_memstream stream;
894     vlc_memstream_open(&stream);
895
896     /* Handle URI scheme */
897     const char *p = str;
898     bool absolute = false;
899     bool encode_brackets = true;
900
901     while (isurialnum(*p) || memchr("+-.", *p, 3) != NULL)
902         vlc_memstream_putc(&stream, *(p++));
903
904     if (p > str && *p == ':')
905     {   /* There is an URI scheme, assume an absolute URI. */
906         vlc_memstream_putc(&stream, *(p++));
907         absolute = true;
908         encode_brackets = false;
909     }
910
911     /* Handle URI authority */
912     if ((absolute || p == str) && strncmp(p, "//", 2) == 0)
913     {
914         vlc_memstream_write(&stream, p, 2);
915         p += 2;
916         encode_brackets = true;
917
918         while (memchr("/?#", *p, 4) == NULL)
919             vlc_uri_putc(&stream, *(p++), &"%:[]@"[encode_percent]);
920     }
921
922     /* Handle URI path and what follows */
923     const char *extras = encode_brackets ? "%/?#@" : "%:/?#[]@";
924
925     while (*p != '\0')
926         vlc_uri_putc(&stream, *(p++), extras + encode_percent);
927
928     return vlc_memstream_close(&stream) ? NULL : stream.ptr;
929 }
930
931 #if defined (HAVE_IDN)
932 # include <idna.h>
933 #elif defined (_WIN32)
934 # include <windows.h>
935 # include <vlc_charset.h>
936 #endif
937
938 /**
939  * Converts a UTF-8 nul-terminated IDN to nul-terminated ASCII domain name.
940  * \param idn UTF-8 Internationalized Domain Name to convert
941  * \return a heap-allocated string or NULL on error.
942  */
943 static char *vlc_idna_to_ascii (const char *idn)
944 {
945 #if defined (HAVE_IDN)
946     char *adn;
947
948     switch (idna_to_ascii_8z(idn, &adn, IDNA_ALLOW_UNASSIGNED))
949     {
950         case IDNA_SUCCESS:
951             return adn;
952         case IDNA_MALLOC_ERROR:
953             errno = ENOMEM;
954             return NULL;
955         case IDNA_DLOPEN_ERROR:
956             errno = ENOSYS;
957             return NULL;
958         default:
959             errno = EINVAL;
960             return NULL;
961     }
962
963 #elif defined (_WIN32)
964     char *ret = NULL;
965
966     if (idn[0] == '\0')
967         return strdup("");
968
969     wchar_t *wide = ToWide (idn);
970     if (wide == NULL)
971         return NULL;
972
973     int len = IdnToAscii (IDN_ALLOW_UNASSIGNED, wide, -1, NULL, 0);
974     if (len == 0)
975     {
976         errno = EINVAL;
977         goto error;
978     }
979
980     wchar_t *buf = vlc_alloc (len, sizeof (*buf));
981     if (unlikely(buf == NULL))
982         goto error;
983     if (!IdnToAscii (IDN_ALLOW_UNASSIGNED, wide, -1, buf, len))
984     {
985         free (buf);
986         errno = EINVAL;
987         goto error;
988     }
989     ret = FromWide (buf);
990     free (buf);
991 error:
992     free (wide);
993     return ret;
994
995 #else
996     /* No IDN support, filter out non-ASCII domain names */
997     for (const char *p = idn; *p; p++)
998         if (((unsigned char)*p) >= 0x80)
999         {
1000             errno = ENOSYS;
1001             return NULL;
1002         }
1003
1004     return strdup (idn);
1005
1006 #endif
1007 }