]> git.sesse.net Git - vlc/blob - src/text/url.c
42473ff54b2e85eb77d4a7a9af27685a9c9450b5
[vlc] / 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 <stdlib.h>
27 #include <string.h>
28 #include <assert.h>
29 #ifdef WIN32
30 # include <io.h>
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_url.h>
35 #include <vlc_fs.h>
36 #include <ctype.h>
37
38 /**
39  * Decodes an encoded URI component. See also decode_URI().
40  * \return decoded string allocated on the heap, or NULL on error.
41  */
42 char *decode_URI_duplicate (const char *str)
43 {
44     char *buf = strdup (str);
45     if (decode_URI (buf) == NULL)
46     {
47         free (buf);
48         buf = NULL;
49     }
50     return buf;
51 }
52
53 /**
54  * Decodes an encoded URI component in place.
55  * <b>This function does NOT decode entire URIs.</b> Instead, it decodes one
56  * component at a time (e.g. host name, directory, file name).
57  * Decoded URIs do not exist in the real world (see RFC3986 §2.4).
58  * Complete URIs are always "encoded" (or they are syntaxically invalid).
59  *
60  * Note that URI encoding is different from Javascript escaping. Especially,
61  * white spaces and Unicode non-ASCII code points are encoded differently.
62  *
63  * \param str nul-terminated URI component to decode
64  * \return str on success, NULL if it was not properly encoded
65  */
66 char *decode_URI (char *str)
67 {
68     char *in = str, *out = str;
69     if (in == NULL)
70         return NULL;
71
72     signed char c;
73     while ((c = *(in++)) != '\0')
74     {
75         if (c == '%')
76         {
77             char hex[3];
78
79             if (!(hex[0] = *(in++)) || !(hex[1] = *(in++)))
80                 return NULL;
81             hex[2] = '\0';
82             *(out++) = strtoul (hex, NULL, 0x10);
83         }
84         else
85         if (c >= 32)
86             *(out++) = c;
87         else
88             /* Inserting non-ASCII or non-printable characters is unsafe,
89              * and no sane browser will send these unencoded */
90             *(out++) = '?';
91     }
92     *out = '\0';
93     return str;
94 }
95
96 static inline bool isurisafe (int c)
97 {
98     /* These are the _unreserved_ URI characters (RFC3986 §2.3) */
99     return ((unsigned char)(c - 'a') < 26)
100         || ((unsigned char)(c - 'A') < 26)
101         || ((unsigned char)(c - '0') < 10)
102         || (strchr ("-._~", c) != NULL);
103 }
104
105 static char *encode_URI_bytes (const char *str, size_t *restrict lenp)
106 {
107     char *buf = malloc (3 * *lenp + 1);
108     if (unlikely(buf == NULL))
109         return NULL;
110
111     char *out = buf;
112     for (size_t i = 0; i < *lenp; i++)
113     {
114         static const char hex[16] = "0123456789ABCDEF";
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++) = hex[c >> 4];
125             *(out++) = hex[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 /**
135  * Encodes a URI component (RFC3986 §2).
136  *
137  * @param str nul-terminated UTF-8 representation of the component.
138  * @note Obviously, a URI containing nul bytes cannot be passed.
139  * @return encoded string (must be free()'d), or NULL for ENOMEM.
140  */
141 char *encode_URI_component (const char *str)
142 {
143     size_t len = strlen (str);
144     char *ret = encode_URI_bytes (str, &len);
145     if (likely(ret != NULL))
146         ret[len] = '\0';
147     return ret;
148 }
149
150 /**
151  * Builds a URL representation from a local file path.
152  * @param path path to convert (or URI to copy)
153  * @param scheme URI scheme to use (default is auto: "file", "fd" or "smb")
154  * @return a nul-terminated URI string (use free() to release it),
155  * or NULL in case of error
156  */
157 char *vlc_path2uri (const char *path, const char *scheme)
158 {
159     if (path == NULL)
160         return NULL;
161     if (scheme == NULL && !strcmp (path, "-"))
162         return strdup ("fd://0"); // standard input
163     /* Note: VLC cannot handle URI schemes without double slash after the
164      * scheme name (such as mailto: or news:). */
165
166     char *buf;
167
168 #ifdef __OS2__
169     char p[strlen (path) + 1];
170
171     for (buf = p; *path; buf++, path++)
172         *buf = (*path == '/') ? DIR_SEP_CHAR : *path;
173     *buf = '\0';
174
175     path = p;
176 #endif
177
178 #if defined( WIN32 ) || defined( __OS2__ )
179     /* Drive letter */
180     if (isalpha ((unsigned char)path[0]) && (path[1] == ':'))
181     {
182         if (asprintf (&buf, "%s:///%c:", scheme ? scheme : "file",
183                       path[0]) == -1)
184             buf = NULL;
185         path += 2;
186 # warning Drive letter-relative path not implemented!
187         if (path[0] != DIR_SEP_CHAR)
188             return NULL;
189     }
190     else
191 #endif
192     if (!strncmp (path, "\\\\", 2))
193     {   /* Windows UNC paths */
194 #if !defined( WIN32 ) && !defined( __OS2__ )
195         if (scheme != NULL)
196             return NULL; /* remote files not supported */
197
198         /* \\host\share\path -> smb://host/share/path */
199         if (strchr (path + 2, '\\') != NULL)
200         {   /* Convert backslashes to slashes */
201             char *dup = strdup (path);
202             if (dup == NULL)
203                 return NULL;
204             for (size_t i = 2; dup[i]; i++)
205                 if (dup[i] == '\\')
206                     dup[i] = DIR_SEP_CHAR;
207
208             char *ret = vlc_path2uri (dup, scheme);
209             free (dup);
210             return ret;
211         }
212 # define SMB_SCHEME "smb"
213 #else
214         /* \\host\share\path -> file://host/share/path */
215 # define SMB_SCHEME "file"
216 #endif
217         size_t hostlen = strcspn (path + 2, DIR_SEP);
218
219         buf = malloc (sizeof (SMB_SCHEME) + 3 + hostlen);
220         if (buf != NULL)
221             snprintf (buf, sizeof (SMB_SCHEME) + 3 + hostlen,
222                       SMB_SCHEME"://%s", path + 2);
223         path += 2 + hostlen;
224
225         if (path[0] == '\0')
226             return buf; /* Hostname without path */
227     }
228     else
229     if (path[0] != DIR_SEP_CHAR)
230     {   /* Relative path: prepend the current working directory */
231         char *cwd, *ret;
232
233         if ((cwd = vlc_getcwd ()) == NULL)
234             return NULL;
235         if (asprintf (&buf, "%s"DIR_SEP"%s", cwd, path) == -1)
236             buf = NULL;
237
238         free (cwd);
239         ret = (buf != NULL) ? vlc_path2uri (buf, scheme) : NULL;
240         free (buf);
241         return ret;
242     }
243     else
244     if (asprintf (&buf, "%s://", scheme ? scheme : "file") == -1)
245         buf = NULL;
246     if (buf == NULL)
247         return NULL;
248
249     /* Absolute file path */
250     assert (path[0] == DIR_SEP_CHAR);
251     do
252     {
253         size_t len = strcspn (++path, DIR_SEP);
254         path += len;
255
256         char *component = encode_URI_bytes (path - len, &len);
257         if (unlikely(component == NULL))
258         {
259             free (buf);
260             return NULL;
261         }
262         component[len] = '\0';
263
264         char *uri;
265         int val = asprintf (&uri, "%s/%s", buf, component);
266         free (component);
267         free (buf);
268         if (unlikely(val == -1))
269             return NULL;
270         buf = uri;
271     }
272     while (*path);
273
274     return buf;
275 }
276
277 /**
278  * Tries to convert a URI to a local (UTF-8-encoded) file path.
279  * @param url URI to convert
280  * @return NULL on error, a nul-terminated string otherwise
281  * (use free() to release it)
282  */
283 char *make_path (const char *url)
284 {
285     char *ret = NULL;
286     char *end;
287
288     char *path = strstr (url, "://");
289     if (path == NULL)
290         return NULL; /* unsupported scheme or invalid syntax */
291
292     end = memchr (url, '/', path - url);
293     size_t schemelen = ((end != NULL) ? end : path) - url;
294     path += 3; /* skip "://" */
295
296     /* Remove HTML anchor if present */
297     end = strchr (path, '#');
298     if (end)
299         path = strndup (path, end - path);
300     else
301         path = strdup (path);
302     if (unlikely(path == NULL))
303         return NULL; /* boom! */
304
305     /* Decode path */
306     decode_URI (path);
307
308     if (schemelen == 4 && !strncasecmp (url, "file", 4))
309     {
310 #if (!defined (WIN32) && !defined (__OS2__)) || defined (UNDER_CE)
311         /* Leading slash => local path */
312         if (*path == '/')
313             return path;
314         /* Local path disguised as a remote one */
315         if (!strncasecmp (path, "localhost/", 10))
316             return memmove (path, path + 9, strlen (path + 9) + 1);
317 #else
318         /* cannot start with a space */
319         if (*path == ' ')
320             goto out;
321         for (char *p = strchr (path, '/'); p; p = strchr (p + 1, '/'))
322             *p = '\\';
323
324         /* Leading backslash => local path */
325         if (*path == '\\')
326             return memmove (path, path + 1, strlen (path + 1) + 1);
327         /* Local path disguised as a remote one */
328         if (!strncasecmp (path, "localhost\\", 10))
329             return memmove (path, path + 10, strlen (path + 10) + 1);
330         /* UNC path */
331         if (*path && asprintf (&ret, "\\\\%s", path) == -1)
332             ret = NULL;
333 #endif
334         /* non-local path :-( */
335     }
336     else
337     if (schemelen == 2 && !strncasecmp (url, "fd", 2))
338     {
339         int fd = strtol (path, &end, 0);
340
341         if (*end)
342             goto out;
343
344 #if !defined( WIN32 ) && !defined( __OS2__ )
345         switch (fd)
346         {
347             case 0:
348                 ret = strdup ("/dev/stdin");
349                 break;
350             case 1:
351                 ret = strdup ("/dev/stdout");
352                 break;
353             case 2:
354                 ret = strdup ("/dev/stderr");
355                 break;
356             default:
357                 if (asprintf (&ret, "/dev/fd/%d", fd) == -1)
358                     ret = NULL;
359         }
360 #else
361         /* XXX: Does this work on WinCE? */
362         if (fd < 2)
363             ret = strdup ("CON");
364         else
365             ret = NULL;
366 #endif
367     }
368
369 out:
370     free (path);
371     return ret; /* unknown scheme */
372 }
373
374 static char *vlc_idna_to_ascii (const char *);
375
376 /**
377  * Splits an URL into parts.
378  * \param url structure of URL parts [OUT]
379  * \param str nul-terminated URL string to split
380  * \param opt if non-zero, character separating paths from options,
381  *            normally the question mark
382  * \note Use vlc_UrlClean() to free associated resources
383  * \bug Errors cannot be detected.
384  * \return nothing
385  */
386 void vlc_UrlParse (vlc_url_t *restrict url, const char *str, unsigned char opt)
387 {
388     url->psz_protocol = NULL;
389     url->psz_username = NULL;
390     url->psz_password = NULL;
391     url->psz_host = NULL;
392     url->i_port = 0;
393     url->psz_path = NULL;
394     url->psz_option = NULL;
395     url->psz_buffer = NULL;
396
397     if (str == NULL)
398         return;
399
400     char *buf = strdup (str);
401     if (unlikely(buf == NULL))
402         abort ();
403     url->psz_buffer = buf;
404
405     char *cur = buf, *next;
406
407     /* URL scheme */
408     next = strchr (cur, ':');
409     /* This is not strictly correct. In principles, the scheme is always
410      * present in an absolute URL and followed by a colon. Depending on the
411      * URL scheme, the two subsequent slashes are not required.
412      * VLC uses a different scheme for historical compatibility reasons - the
413      * scheme is often implicit. */
414     if (next != NULL && !strncmp (next + 1, "//", 2))
415     {
416         *next = '\0';
417         next += 3;
418         url->psz_protocol = cur;
419         cur = next;
420     }
421
422     /* Path */
423     next = strchr (cur, '/');
424     if (next != NULL)
425     {
426         *next = '\0'; /* temporary nul, reset to slash later */
427         url->psz_path = next;
428         if (opt && (next = strchr (next, opt)) != NULL)
429         {
430             *(next++) = '\0';
431             url->psz_option = next;
432         }
433     }
434     /*else
435         url->psz_path = "/";*/
436
437     /* User name */
438     next = strchr (cur, '@');
439     if (next != NULL)
440     {
441         *(next++) = '\0';
442         url->psz_username = cur;
443         cur = next;
444
445         /* Password (obsolete) */
446         next = strchr (url->psz_username, ':');
447         if (next != NULL)
448         {
449             *(next++) = '\0';
450             url->psz_password = next;
451             decode_URI (url->psz_password);
452         }
453         decode_URI (url->psz_username);
454     }
455
456     /* Host name */
457     if (*cur == '[' && (next = strrchr (cur, ']')) != NULL)
458     {   /* Try IPv6 numeral within brackets */
459         *(next++) = '\0';
460         url->psz_host = strdup (cur + 1);
461
462         if (*next == ':')
463             next++;
464         else
465             next = NULL;
466     }
467     else
468     {
469         next = strchr (cur, ':');
470         if (next != NULL)
471             *(next++) = '\0';
472
473         url->psz_host = vlc_idna_to_ascii (cur);
474     }
475
476     /* Port number */
477     if (next != NULL)
478         url->i_port = atoi (next);
479
480     if (url->psz_path != NULL)
481         *url->psz_path = '/'; /* restore leading slash */
482 }
483
484 /**
485  * Releases resources allocated by vlc_UrlParse().
486  */
487 void vlc_UrlClean (vlc_url_t *restrict url)
488 {
489     free (url->psz_host);
490     free (url->psz_buffer);
491 }
492
493 #if defined (HAVE_IDN)
494 # include <idna.h>
495 #elif defined (WIN32)
496 # include <windows.h>
497 # include <vlc_charset.h>
498 #endif
499
500 /**
501  * Converts a UTF-8 nul-terminated IDN to nul-terminated ASCII domain name.
502  * \param idn UTF-8 Internationalized Domain Name to convert
503  * \return a heap-allocated string or NULL on error.
504  */
505 static char *vlc_idna_to_ascii (const char *idn)
506 {
507 #if defined (HAVE_IDN)
508     char *adn;
509
510     if (idna_to_ascii_8z (idn, &adn, IDNA_ALLOW_UNASSIGNED) != IDNA_SUCCESS)
511         return NULL;
512     return adn;
513
514 #elif defined (WIN32) && (_WIN32_WINNT >= 0x0601)
515     char *ret = NULL;
516
517     wchar_t *wide = ToWide (idn);
518     if (wide == NULL)
519         return NULL;
520
521     int len = IdnToAscii (IDN_ALLOW_UNASSIGNED, wide, -1, NULL, 0);
522     if (len == 0)
523         goto error;
524
525     wchar_t *buf = malloc (sizeof (*buf) * len);
526     if (unlikely(buf == NULL))
527         goto error;
528     if (!IdnToAscii (IDN_ALLOW_UNASSIGNED, wide, -1, buf, len))
529     {
530         free (buf);
531         goto error;
532     }
533     free (wide);
534
535     ret = FromWide (buf);
536     free (buf);
537 error:
538     free (wide);
539     return ret;
540
541 #else
542     /* No IDN support, filter out non-ASCII domain names */
543     for (const char *p = idn; *p; p++)
544         if (((unsigned char)*p) >= 0x80)
545             return NULL;
546
547     return strdup (idn);
548
549 #endif
550 }