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