]> git.sesse.net Git - vlc/blob - src/text/url.c
vlc_path2uri(): rename from make_URI() and always convert path
[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
37 /**
38  * Decodes an encoded URI component. See also decode_URI().
39  * \return decoded string allocated on the heap, or NULL on error.
40  */
41 char *decode_URI_duplicate (const char *str)
42 {
43     char *buf = strdup (str);
44     if (decode_URI (buf) == NULL)
45     {
46         free (buf);
47         buf = NULL;
48     }
49     return buf;
50 }
51
52 /**
53  * Decodes an encoded URI component in place.
54  * <b>This function does NOT decode entire URIs.</b> Instead, it decodes one
55  * component at a time (e.g. host name, directory, file name).
56  * Decoded URIs do not exist in the real world (see RFC3986 §2.4).
57  * Complete URIs are always "encoded" (or they are syntaxically invalid).
58  *
59  * Note that URI encoding is different from Javascript escaping. Especially,
60  * white spaces and Unicode non-ASCII code points are encoded differently.
61  *
62  * \param str nul-terminated URI component to decode
63  * \return str on success, NULL if it was not properly encoded
64  */
65 char *decode_URI (char *str)
66 {
67     char *in = str, *out = str;
68     if (in == NULL)
69         return NULL;
70
71     signed char c;
72     while ((c = *(in++)) != '\0')
73     {
74         if (c == '%')
75         {
76             char hex[3];
77
78             if (!(hex[0] = *(in++)) || !(hex[1] = *(in++)))
79                 return NULL;
80             hex[2] = '\0';
81             *(out++) = strtoul (hex, NULL, 0x10);
82         }
83         else
84         if (c >= 32)
85             *(out++) = c;
86         else
87             /* Inserting non-ASCII or non-printable characters is unsafe,
88              * and no sane browser will send these unencoded */
89             *(out++) = '?';
90     }
91     *out = '\0';
92     return str;
93 }
94
95 static inline bool isurisafe (int c)
96 {
97     /* These are the _unreserved_ URI characters (RFC3986 §2.3) */
98     return ((unsigned char)(c - 'a') < 26)
99         || ((unsigned char)(c - 'A') < 26)
100         || ((unsigned char)(c - '0') < 10)
101         || (strchr ("-._~", c) != NULL);
102 }
103
104 static char *encode_URI_bytes (const char *str, size_t *restrict lenp)
105 {
106     char *buf = malloc (3 * *lenp + 1);
107     if (unlikely(buf == NULL))
108         return NULL;
109
110     char *out = buf;
111     for (size_t i = 0; i < *lenp; i++)
112     {
113         static const char hex[16] = "0123456789ABCDEF";
114         unsigned char c = str[i];
115
116         if (isurisafe (c))
117             *(out++) = c;
118         /* This is URI encoding, not HTTP forms:
119          * Space is encoded as '%20', not '+'. */
120         else
121         {
122             *(out++) = '%';
123             *(out++) = hex[c >> 4];
124             *(out++) = hex[c & 0xf];
125         }
126     }
127
128     *lenp = out - buf;
129     out = realloc (buf, *lenp + 1);
130     return likely(out != NULL) ? out : buf;
131 }
132
133 /**
134  * Encodes a URI component (RFC3986 §2).
135  *
136  * @param str nul-terminated UTF-8 representation of the component.
137  * @note Obviously, a URI containing nul bytes cannot be passed.
138  * @return encoded string (must be free()'d), or NULL for ENOMEM.
139  */
140 char *encode_URI_component (const char *str)
141 {
142     size_t len = strlen (str);
143     char *ret = encode_URI_bytes (str, &len);
144     if (likely(ret != NULL))
145         ret[len] = '\0';
146     return ret;
147 }
148
149 /**
150  * Builds a URL representation from a local file path.
151  * @param path path to convert (or URI to copy)
152  * @param scheme URI scheme to use (default is auto: "file", "fd" or "smb")
153  * @return a nul-terminated URI string (use free() to release it),
154  * or NULL in case of error
155  */
156 char *vlc_path2uri (const char *path, const char *scheme)
157 {
158     if (path == NULL)
159         return NULL;
160     if (scheme == NULL && !strcmp (path, "-"))
161         return strdup ("fd://0"); // standard input
162     /* Note: VLC cannot handle URI schemes without double slash after the
163      * scheme name (such as mailto: or news:). */
164
165     char *buf;
166
167 #ifdef __OS2__
168     char p[strlen (path) + 1];
169
170     for (buf = p; *path; buf++, path++)
171         *buf = (*path == '/') ? DIR_SEP_CHAR : *path;
172     *buf = '\0';
173
174     path = p;
175 #endif
176
177 #if defined( WIN32 ) || defined( __OS2__ )
178     /* Drive letter */
179     if (isalpha ((unsigned char)path[0]) && (path[1] == ':'))
180     {
181         if (asprintf (&buf, "%s:///%c:", scheme ? scheme : "file",
182                       path[0]) == -1)
183             buf = NULL;
184         path += 2;
185 # warning Drive letter-relative path not implemented!
186         if (path[0] != DIR_SEP_CHAR)
187             return NULL;
188     }
189     else
190 #endif
191     if (!strncmp (path, "\\\\", 2))
192     {   /* Windows UNC paths */
193 #if !defined( WIN32 ) && !defined( __OS2__ )
194         if (scheme != NULL)
195             return NULL; /* remote files not supported */
196
197         /* \\host\share\path -> smb://host/share/path */
198         if (strchr (path + 2, '\\') != NULL)
199         {   /* Convert backslashes to slashes */
200             char *dup = strdup (path);
201             if (dup == NULL)
202                 return NULL;
203             for (size_t i = 2; dup[i]; i++)
204                 if (dup[i] == '\\')
205                     dup[i] = DIR_SEP_CHAR;
206
207             char *ret = vlc_path2uri (dup, scheme);
208             free (dup);
209             return ret;
210         }
211 # define SMB_SCHEME "smb"
212 #else
213         /* \\host\share\path -> file://host/share/path */
214 # define SMB_SCHEME "file"
215 #endif
216         size_t hostlen = strcspn (path + 2, DIR_SEP);
217
218         buf = malloc (sizeof (SMB_SCHEME) + 3 + hostlen);
219         if (buf != NULL)
220             snprintf (buf, sizeof (SMB_SCHEME) + 3 + hostlen,
221                       SMB_SCHEME"://%s", path + 2);
222         path += 2 + hostlen;
223
224         if (path[0] == '\0')
225             return buf; /* Hostname without path */
226     }
227     else
228     if (path[0] != DIR_SEP_CHAR)
229     {   /* Relative path: prepend the current working directory */
230         char *cwd, *ret;
231
232         if ((cwd = vlc_getcwd ()) == NULL)
233             return NULL;
234         if (asprintf (&buf, "%s"DIR_SEP"%s", cwd, path) == -1)
235             buf = NULL;
236
237         free (cwd);
238         ret = (buf != NULL) ? vlc_path2uri (buf, scheme) : NULL;
239         free (buf);
240         return ret;
241     }
242     else
243     if (asprintf (&buf, "%s://", scheme ? scheme : "file") == -1)
244         buf = NULL;
245     if (buf == NULL)
246         return NULL;
247
248     /* Absolute file path */
249     assert (path[0] == DIR_SEP_CHAR);
250     do
251     {
252         size_t len = strcspn (++path, DIR_SEP);
253         path += len;
254
255         char *component = encode_URI_bytes (path - len, &len);
256         if (unlikely(component == NULL))
257         {
258             free (buf);
259             return NULL;
260         }
261         component[len] = '\0';
262
263         char *uri;
264         int val = asprintf (&uri, "%s/%s", buf, component);
265         free (component);
266         free (buf);
267         if (unlikely(val == -1))
268             return NULL;
269         buf = uri;
270     }
271     while (*path);
272
273     return buf;
274 }
275
276 /**
277  * Tries to convert a URI to a local (UTF-8-encoded) file path.
278  * @param url URI to convert
279  * @return NULL on error, a nul-terminated string otherwise
280  * (use free() to release it)
281  */
282 char *make_path (const char *url)
283 {
284     char *ret = NULL;
285     char *end;
286
287     char *path = strstr (url, "://");
288     if (path == NULL)
289         return NULL; /* unsupported scheme or invalid syntax */
290
291     end = memchr (url, '/', path - url);
292     size_t schemelen = ((end != NULL) ? end : path) - url;
293     path += 3; /* skip "://" */
294
295     /* Remove HTML anchor if present */
296     end = strchr (path, '#');
297     if (end)
298         path = strndup (path, end - path);
299     else
300         path = strdup (path);
301     if (unlikely(path == NULL))
302         return NULL; /* boom! */
303
304     /* Decode path */
305     decode_URI (path);
306
307     if (schemelen == 4 && !strncasecmp (url, "file", 4))
308     {
309 #if (!defined (WIN32) && !defined (__OS2__)) || defined (UNDER_CE)
310         /* Leading slash => local path */
311         if (*path == '/')
312             return path;
313         /* Local path disguised as a remote one */
314         if (!strncasecmp (path, "localhost/", 10))
315             return memmove (path, path + 9, strlen (path + 9) + 1);
316 #else
317         /* cannot start with a space */
318         if (*path == ' ')
319             goto out;
320         for (char *p = strchr (path, '/'); p; p = strchr (p + 1, '/'))
321             *p = '\\';
322
323         /* Leading backslash => local path */
324         if (*path == '\\')
325             return memmove (path, path + 1, strlen (path + 1) + 1);
326         /* Local path disguised as a remote one */
327         if (!strncasecmp (path, "localhost\\", 10))
328             return memmove (path, path + 10, strlen (path + 10) + 1);
329         /* UNC path */
330         if (*path && asprintf (&ret, "\\\\%s", path) == -1)
331             ret = NULL;
332 #endif
333         /* non-local path :-( */
334     }
335     else
336     if (schemelen == 2 && !strncasecmp (url, "fd", 2))
337     {
338         int fd = strtol (path, &end, 0);
339
340         if (*end)
341             goto out;
342
343 #if !defined( WIN32 ) && !defined( __OS2__ )
344         switch (fd)
345         {
346             case 0:
347                 ret = strdup ("/dev/stdin");
348                 break;
349             case 1:
350                 ret = strdup ("/dev/stdout");
351                 break;
352             case 2:
353                 ret = strdup ("/dev/stderr");
354                 break;
355             default:
356                 if (asprintf (&ret, "/dev/fd/%d", fd) == -1)
357                     ret = NULL;
358         }
359 #else
360         /* XXX: Does this work on WinCE? */
361         if (fd < 2)
362             ret = strdup ("CON");
363         else
364             ret = NULL;
365 #endif
366     }
367
368 out:
369     free (path);
370     return ret; /* unknown scheme */
371 }