/*****************************************************************************
* url.c: URL related functions
*****************************************************************************
* Copyright (C) 2006 VLC authors and VideoLAN
* Copyright (C) 2008-2012 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef _WIN32
# include <windows.h>
# include <io.h>
#ifndef IDN_ALLOW_UNASSIGNED // GAMES
#define IDN_ALLOW_UNASSIGNED 0x01 // Allow unassigned "query" behavior per RFC 3454
#endif
#endif
#include <vlc_common.h>
#include <vlc_memstream.h>
#include <vlc_url.h>
#include <vlc_fs.h>
#include <ctype.h>
char *vlc_uri_decode_duplicate (const char *str)
{
char *buf = strdup (str);
if (vlc_uri_decode (buf) == NULL)
{
free (buf);
buf = NULL;
}
return buf;
}
static int_fast8_t hex_to_char(unsigned char c)
{
uint_fast8_t v = c - '0';
if ( v < 10 )
return v;
v = c - 'a';
if ( v <= 5 )
return v + 10;
v = c - 'A';
if ( v <= 5 )
return v + 10;
return -1;
}
char *vlc_uri_decode (char *str)
{
char *in = str, *out = str;
if (in == NULL)
return NULL;
char c;
while ((c = *(in++)) != '\0')
{
if (c == '%')
{
int_fast8_t a, b;
if ((a = hex_to_char(*(in++))) < 0 ||
(b = hex_to_char(*(in++))) < 0)
{
errno = EINVAL;
return NULL;
}
*(out++) = a << 4 | b;
}
else
*(out++) = c;
}
*out = '\0';
return str;
}
static bool isurialnum(int c)
{
return ((unsigned char)(c - 'a') < 26)
|| ((unsigned char)(c - 'A') < 26)
|| ((unsigned char)(c - '0') < 10);
}
static bool isurisafe(int c)
{
/* These are the _unreserved_ URI characters (RFC3986 §2.3) */
return isurialnum(c) || (strchr ("-._~", c) != NULL);
}
static bool isurisubdelim(int c)
{
return strchr("!$&'()*+,;=", c) != NULL;
}
static bool isurihex(int c)
{ /* Same as isxdigit() but does not depend on locale and unsignedness */
return ((unsigned char)(c - '0') < 10)
|| ((unsigned char)(c - 'A') < 6)
|| ((unsigned char)(c - 'a') < 6);
}
static const char urihex[] = "0123456789ABCDEF";
static char *encode_URI_bytes (const char *str, size_t *restrict lenp)
{
char *buf = malloc (3 * *lenp + 1);
if (unlikely(buf == NULL))
return NULL;
char *out = buf;
for (size_t i = 0; i < *lenp; i++)
{
unsigned char c = str[i];
if (isurisafe (c))
*(out++) = c;
/* This is URI encoding, not HTTP forms:
* Space is encoded as '%20', not '+'. */
else
{
*(out++) = '%';
*(out++) = urihex[c >> 4];
*(out++) = urihex[c & 0xf];
}
}
*lenp = out - buf;
out = realloc (buf, *lenp + 1);
return likely(out != NULL) ? out : buf;
}
char *vlc_uri_encode (const char *str)
{
size_t len = strlen (str);
char *ret = encode_URI_bytes (str, &len);
if (likely(ret != NULL))
ret[len] = '\0';
return ret;
}
char *vlc_path2uri (const char *path, const char *scheme)
{
if (path == NULL)
{
errno = EINVAL;
return NULL;
}
if (scheme == NULL && !strcmp (path, "-"))
return strdup ("fd://0"); // standard input
/* Note: VLC cannot handle URI schemes without double slash after the
* scheme name (such as mailto: or news:). */
char *buf;
#ifdef __OS2__
char p[strlen (path) + 1];
for (buf = p; *path; buf++, path++)
*buf = (*path == '/') ? DIR_SEP_CHAR : *path;
*buf = '\0';
path = p;
#endif
#if defined (_WIN32) || defined (__OS2__)
/* Drive letter */
if (isalpha ((unsigned char)path[0]) && (path[1] == ':'))
{
if (asprintf (&buf, "%s:///%c:", scheme ? scheme : "file",
path[0]) == -1)
buf = NULL;
path += 2;
if (path[0] != DIR_SEP_CHAR)
{
// Warning: Drive letter-relative path not implemented!
errno = ENOTSUP;
return NULL;
}
}
else
if (!strncmp (path, "\\\\", 2))
{ /* Windows UNC paths */
/* \\host\share\path -> file://host/share/path */
int hostlen = strcspn (path + 2, DIR_SEP);
if (asprintf (&buf, "file://%.*s", hostlen, path + 2) == -1)
buf = NULL;
path += 2 + hostlen;
if (path[0] == '\0')
return buf; /* Hostname without path */
}
else
#endif
if (path[0] != DIR_SEP_CHAR)
{ /* Relative path: prepend the current working directory */
char *cwd, *ret;
if (path[0] == '\0')
return NULL;
if ((cwd = vlc_getcwd ()) == NULL)
return NULL;
if (asprintf (&buf, "%s"DIR_SEP"%s", cwd, path) == -1)
buf = NULL;
free (cwd);
ret = (buf != NULL) ? vlc_path2uri (buf, scheme) : NULL;
free (buf);
return ret;
}
else
if (asprintf (&buf, "%s://", scheme ? scheme : "file") == -1)
buf = NULL;
if (buf == NULL)
return NULL;
/* Absolute file path */
assert (path[0] == DIR_SEP_CHAR);
do
{
size_t len = strcspn (++path, DIR_SEP);
path += len;
char *component = encode_URI_bytes (path - len, &len);
if (unlikely(component == NULL))
{
free (buf);
return NULL;
}
component[len] = '\0';
char *uri;
int val = asprintf (&uri, "%s/%s", buf, component);
free (component);
free (buf);
if (unlikely(val == -1))
return NULL;
buf = uri;
}
while (*path);
return buf;
}
char *vlc_uri2path (const char *url)
{
char *ret = NULL;
char *path = NULL;
size_t schemelen = 0;
{
const char *start = strstr (url, "://");
if (start == NULL)
return NULL; /* unsupported scheme or invalid syntax */
const char *end = memchr (url, '/', start - url);
schemelen = ((end != NULL) ? end : start) - url;
start += 3; /* skip "://" */
/* Remove request parameters and/or HTML anchor if present */
end = start + strcspn (start, "?#");
path = strndup (start, end - start);
if (unlikely(path == NULL))
return NULL; /* boom! */
}
/* Decode path */
vlc_uri_decode (path);
if (schemelen == 4 && !strncasecmp (url, "file", 4))
{
#if !defined (_WIN32) && !defined (__OS2__)
/* Leading slash => local path */
if (*path == '/')
return path;
/* Local path disguised as a remote one */
if (!strncasecmp (path, "localhost/", 10))
return memmove (path, path + 9, strlen (path + 9) + 1);
#else
/* cannot start with a space */
if (*path == ' ')
goto out;
for (char *p = strchr (path, '/'); p; p = strchr (p + 1, '/'))
*p = '\\';
/* Leading backslash => local path */
if (*path == '\\')
return memmove (path, path + 1, strlen (path + 1) + 1);
/* Local path disguised as a remote one */
if (!strncasecmp (path, "localhost\\", 10))
return memmove (path, path + 10, strlen (path + 10) + 1);
/* UNC path */
if (*path && asprintf (&ret, "\\\\%s", path) == -1)
ret = NULL;
#endif
/* non-local path :-( */
}
else
if (schemelen == 2 && !strncasecmp (url, "fd", 2))
{
char *leftover;
long fd = strtol(path, &leftover, 0);
if (*leftover != '\0' || ((unsigned long)fd) > INT_MAX)
goto out;
#if !defined( _WIN32 ) && !defined( __OS2__ )
switch (fd)
{
case 0:
ret = strdup ("/dev/stdin");
break;
case 1:
ret = strdup ("/dev/stdout");
break;
case 2:
ret = strdup ("/dev/stderr");
break;
default:
if (asprintf (&ret, "/dev/fd/%ld", fd) == -1)
ret = NULL;
}
#else
/* XXX: Does this work on WinCE? */
if (fd < 2)
ret = strdup ("CON");
#endif
}
out:
free (path);
return ret; /* unknown scheme */
}
static char *vlc_idna_to_ascii (const char *);
/* RFC3987 §3.1 */
static char *vlc_iri2uri(const char *iri)
{
size_t a = 0, u = 0;
for (size_t i = 0; iri[i] != '\0'; i++)
{
unsigned char c = iri[i];
if (c < 128)
a++;
else
u++;
}
if (unlikely((a + u) > (SIZE_MAX / 4)))
{
errno = ENOMEM;
return NULL;
}
char *uri = malloc(a + 3 * u + 1), *p;
if (unlikely(uri == NULL))
return NULL;
for (p = uri; *iri != '\0'; iri++)
{
unsigned char c = *iri;
if (c < 128)
*(p++) = c;
else
{
*(p++) = '%';
*(p++) = urihex[c >> 4];
*(p++) = urihex[c & 0xf];
}
}
*p = '\0';
return uri;
}
static bool vlc_uri_component_validate(const char *str, const char *extras)
{
assert(str != NULL);
for (size_t i = 0; str[i] != '\0'; i++)
{
int c = str[i];
if (isurisafe(c) || isurisubdelim(c))
continue;
if (strchr(extras, c) != NULL)
continue;
if (c == '%' && isurihex(str[i + 1]) && isurihex(str[i + 2]))
{
i += 2;
continue;
}
return false;
}
return true;
}
static bool vlc_uri_host_validate(const char *str)
{
return vlc_uri_component_validate(str, ":");
}
static bool vlc_uri_path_validate(const char *str)
{
return vlc_uri_component_validate(str, "/@:[]");
}
static int vlc_UrlParseInner(vlc_url_t *restrict url, const char *str)
{
url->psz_protocol = NULL;
url->psz_username = NULL;
url->psz_password = NULL;
url->psz_host = NULL;
url->i_port = 0;
url->psz_path = NULL;
url->psz_option = NULL;
url->psz_fragment = NULL;
url->psz_buffer = NULL;
url->psz_pathbuffer = NULL;
if (str == NULL)
{
errno = EINVAL;
return -1;
}
char *buf = vlc_iri2uri(str);
if (unlikely(buf == NULL))
return -1;
url->psz_buffer = buf;
char *cur = buf, *next;
int ret = 0;
/* URI scheme */
next = buf;
while (isurialnum(*next) || memchr ("+-.", *next, 3) != NULL)
next++;
if (*next == ':')
{
*(next++) = '\0';
url->psz_protocol = cur;
cur = next;
}
/* Fragment */
next = strchr(cur, '#');
if (next != NULL)
{
*(next++) = '\0';
if (vlc_uri_component_validate(next, "/?"))
url->psz_fragment = next;
}
/* Query parameters */
next = strchr(cur, '?');
if (next != NULL)
{
*(next++) = '\0';
url->psz_option = next;
}
/* Authority */
if (strncmp(cur, "//", 2) == 0)
{
cur += 2;
/* Path */
next = strchr(cur, '/');
if (next != NULL)
{
*next = '\0'; /* temporary nul, reset to slash later */
url->psz_path = next;
}
/*else
url->psz_path = "/";*/
/* User name */
next = strrchr(cur, '@');
if (next != NULL)
{
*(next++) = '\0';
url->psz_username = cur;
cur = next;
/* Password (obsolete) */
next = strchr(url->psz_username, ':');
if (next != NULL)
{
*(next++) = '\0';
url->psz_password = next;
vlc_uri_decode(url->psz_password);
}
vlc_uri_decode(url->psz_username);
}
/* Host name */
if (*cur == '[' && (next = strrchr(cur, ']')) != NULL)
{ /* Try IPv6 numeral within brackets */
*(next++) = '\0';
url->psz_host = strdup(cur + 1);
if (*next == ':')
next++;
else
next = NULL;
}
else
{
next = strchr(cur, ':');
if (next != NULL)
*(next++) = '\0';
const char *host = vlc_uri_decode(cur);
url->psz_host = (host != NULL) ? vlc_idna_to_ascii(host) : NULL;
}
if (url->psz_host == NULL)
ret = -1;
else
if (!vlc_uri_host_validate(url->psz_host))
{
free(url->psz_host);
url->psz_host = NULL;
errno = EINVAL;
ret = -1;
}
/* Port number */
if (next != NULL && *next)
{
char* end;
unsigned long port = strtoul(next, &end, 10);
if (strchr("0123456789", *next) == NULL || *end || port > UINT_MAX)
{
errno = EINVAL;
ret = -1;
}
url->i_port = port;
}
if (url->psz_path != NULL)
*url->psz_path = '/'; /* restore leading slash */
}
else
{
url->psz_path = cur;
}
return ret;
}
int vlc_UrlParse(vlc_url_t *url, const char *str)
{
int ret = vlc_UrlParseInner(url, str);
if (url->psz_path != NULL && !vlc_uri_path_validate(url->psz_path))
{
url->psz_path = NULL;
errno = EINVAL;
ret = -1;
}
return ret;
}
static char *vlc_uri_fixup_inner(const char *str, const char *extras);
int vlc_UrlParseFixup(vlc_url_t *url, const char *str)
{
int ret = vlc_UrlParseInner(url, str);
static const char pathextras[] = "/@:";
if (url->psz_path != NULL
&& !vlc_uri_component_validate(url->psz_path, pathextras))
{
url->psz_pathbuffer = vlc_uri_fixup_inner(url->psz_path, pathextras);
if (url->psz_pathbuffer == NULL)
{
url->psz_path = NULL;
errno = ENOMEM;
ret = -1;
}
else
{
url->psz_path = url->psz_pathbuffer;
assert(vlc_uri_path_validate(url->psz_path));
}
}
return ret;
}
void vlc_UrlClean (vlc_url_t *restrict url)
{
free (url->psz_host);
free (url->psz_buffer);
free (url->psz_pathbuffer);
}
/**
* Merge paths
*
* See IETF RFC3986 section 5.2.3 for details.
*/
static char *vlc_uri_merge_paths(const char *base, const char *ref)
{
char *str;
int len;
if (base == NULL)
len = asprintf(&str, "/%s", ref);
else
{
const char *end = strrchr(base, '/');
if (end != NULL)
end++;
else
end = base;
len = asprintf(&str, "%.*s%s", (int)(end - base), base, ref);
}
if (unlikely(len == -1))
str = NULL;
return str;
}
/**
* Remove dot segments
*
* See IETF RFC3986 section 5.2.4 for details.
*/
static char *vlc_uri_remove_dot_segments(char *str)
{
char *input = str, *output = str;
while (input[0] != '\0')
{
assert(output <= input);
if (strncmp(input, "../", 3) == 0)
{
input += 3;
continue;
}
if (strncmp(input, "./", 2) == 0)
{
input += 2;
continue;
}
if (strncmp(input, "/./", 3) == 0)
{
input += 2;
continue;
}
if (strcmp(input, "/.") == 0)
{
input[1] = '\0';
continue;
}
if (strncmp(input, "/../", 4) == 0)
{
input += 3;
output = memrchr(str, '/', output - str);
if (output == NULL)
output = str;
continue;
}
if (strcmp(input, "/..") == 0)
{
input[1] = '\0';
output = memrchr(str, '/', output - str);
if (output == NULL)
output = str;
continue;
}
if (strcmp(input, ".") == 0)
{
input++;
continue;
}
if (strcmp(input, "..") == 0)
{
input += 2;
continue;
}
if (input[0] == '/')
*(output++) = *(input++);
size_t len = strcspn(input, "/");
if (input != output)
memmove(output, input, len);
input += len;
output += len;
}
output[0] = '\0';
return str;
}
char *vlc_uri_compose(const vlc_url_t *uri)
{
struct vlc_memstream stream;
char *enc;
vlc_memstream_open(&stream);
if (uri->psz_protocol != NULL)
vlc_memstream_printf(&stream, "%s:", uri->psz_protocol);
if (uri->psz_host != NULL)
{
vlc_memstream_write(&stream, "//", 2);
if (uri->psz_username != NULL)
{
enc = vlc_uri_encode(uri->psz_username);
if (enc == NULL)
goto error;
vlc_memstream_puts(&stream, enc);
free(enc);
if (uri->psz_password != NULL)
{
enc = vlc_uri_encode(uri->psz_password);
if (unlikely(enc == NULL))
goto error;
vlc_memstream_printf(&stream, ":%s", enc);
free(enc);
}
vlc_memstream_putc(&stream, '@');
}
const char *fmt;
if (strchr(uri->psz_host, ':') != NULL)
fmt = (uri->i_port != 0) ? "[%s]:%d" : "[%s]";
else
fmt = (uri->i_port != 0) ? "%s:%d" : "%s";
/* No IDNA decoding here. Seems unnecessary, dangerous even. */
vlc_memstream_printf(&stream, fmt, uri->psz_host, uri->i_port);
}
if (uri->psz_path != NULL)
vlc_memstream_puts(&stream, uri->psz_path);
if (uri->psz_option != NULL)
vlc_memstream_printf(&stream, "?%s", uri->psz_option);
if (uri->psz_fragment != NULL)
vlc_memstream_printf(&stream, "#%s", uri->psz_fragment);
if (vlc_memstream_close(&stream))
return NULL;
return stream.ptr;
error:
if (vlc_memstream_close(&stream) == 0)
free(stream.ptr);
return NULL;
}
char *vlc_uri_resolve(const char *base, const char *ref)
{
vlc_url_t base_uri, rel_uri;
vlc_url_t tgt_uri;
char *pathbuf = NULL, *ret = NULL;
if (vlc_UrlParse(&rel_uri, ref))
{
vlc_UrlClean(&rel_uri);
return NULL;
}
if (rel_uri.psz_protocol != NULL)
{ /* Short circuit in case of absolute URI */
vlc_UrlClean(&rel_uri);
return strdup(ref);
}
vlc_UrlParse(&base_uri, base);
/* RFC3986 section 5.2.2 */
do
{
tgt_uri = rel_uri;
tgt_uri.psz_protocol = base_uri.psz_protocol;
if (rel_uri.psz_host != NULL)
break;
tgt_uri.psz_username = base_uri.psz_username;
tgt_uri.psz_password = base_uri.psz_password;
tgt_uri.psz_host = base_uri.psz_host;
tgt_uri.i_port = base_uri.i_port;
if (rel_uri.psz_path == NULL || rel_uri.psz_path[0] == '\0')
{
tgt_uri.psz_path = base_uri.psz_path;
if (rel_uri.psz_option == NULL)
tgt_uri.psz_option = base_uri.psz_option;
break;
}
if (rel_uri.psz_path[0] == '/')
break;
pathbuf = vlc_uri_merge_paths(base_uri.psz_path, rel_uri.psz_path);
if (unlikely(pathbuf == NULL))
goto error;
tgt_uri.psz_path = pathbuf;
}
while (0);
if (tgt_uri.psz_path != NULL)
vlc_uri_remove_dot_segments(tgt_uri.psz_path);
ret = vlc_uri_compose(&tgt_uri);
error:
free(pathbuf);
vlc_UrlClean(&base_uri);
vlc_UrlClean(&rel_uri);
return ret;
}
VLC_MALLOC static char *vlc_uri_fixup_inner(const char *str, const char *extras)
{
assert(str && extras);
bool encode_percent = false;
for (size_t i = 0; str[i] != '\0'; i++)
if (str[i] == '%' && !(isurihex(str[i+1]) && isurihex(str[i+2])))
{
encode_percent = true;
break;
}
struct vlc_memstream stream;
vlc_memstream_open(&stream);
for (size_t i = 0; str[i] != '\0'; i++)
{
unsigned char c = str[i];
if (isurisafe(c) || isurisubdelim(c) || (strchr(extras, c) != NULL)
|| (c == '%' && !encode_percent))
vlc_memstream_putc(&stream, c);
else
vlc_memstream_printf(&stream, "%%%02hhX", c);
}
if (vlc_memstream_close(&stream))
return NULL;
return stream.ptr;
}
static void vlc_uri_putc(struct vlc_memstream *s, int c, const char *extras)
{
if (isurisafe(c) || isurisubdelim(c) || (strchr(extras, c) != NULL))
vlc_memstream_putc(s, c);
else
vlc_memstream_printf(s, "%%%02hhX", (unsigned char)c);
}
char *vlc_uri_fixup(const char *str)
{
assert(str != NULL);
/* If percent sign is consistently followed by two hexadecimal digits,
* then URL encoding must be assumed.
* Otherwise, the percent sign itself must be URL-encoded.
*/
bool encode_percent = false;
for (const char *p = str; *p != '\0'; p++)
if (p[0] == '%' && !(isurihex(p[1]) && isurihex(p[2])))
{
encode_percent = true;
break;
}
struct vlc_memstream stream;
vlc_memstream_open(&stream);
/* Handle URI scheme */
const char *p = str;
bool absolute = false;
bool encode_brackets = true;
while (isurialnum(*p) || memchr("+-.", *p, 3) != NULL)
vlc_memstream_putc(&stream, *(p++));
if (p > str && *p == ':')
{ /* There is an URI scheme, assume an absolute URI. */
vlc_memstream_putc(&stream, *(p++));
absolute = true;
encode_brackets = false;
}
/* Handle URI authority */
if ((absolute || p == str) && strncmp(p, "//", 2) == 0)
{
vlc_memstream_write(&stream, p, 2);
p += 2;
encode_brackets = true;
while (memchr("/?#", *p, 4) == NULL)
vlc_uri_putc(&stream, *(p++), &"%:[]@"[encode_percent]);
}
/* Handle URI path and what follows */
const char *extras = encode_brackets ? "%/?#@" : "%:/?#[]@";
while (*p != '\0')
vlc_uri_putc(&stream, *(p++), extras + encode_percent);
return vlc_memstream_close(&stream) ? NULL : stream.ptr;
}
#if defined (HAVE_IDN)
# include <idna.h>
#elif defined (_WIN32)
# include <windows.h>
# include <vlc_charset.h>
#endif
/**
* Converts a UTF-8 nul-terminated IDN to nul-terminated ASCII domain name.
* \param idn UTF-8 Internationalized Domain Name to convert
* \return a heap-allocated string or NULL on error.
*/
static char *vlc_idna_to_ascii (const char *idn)
{
#if defined (HAVE_IDN)
char *adn;
switch (idna_to_ascii_8z(idn, &adn, IDNA_ALLOW_UNASSIGNED))
{
case IDNA_SUCCESS:
return adn;
case IDNA_MALLOC_ERROR:
errno = ENOMEM;
return NULL;
case IDNA_DLOPEN_ERROR:
errno = ENOSYS;
return NULL;
default:
errno = EINVAL;
return NULL;
}
#elif defined (_WIN32)
char *ret = NULL;
if (idn[0] == '\0')
return strdup("");
wchar_t *wide = ToWide (idn);
if (wide == NULL)
return NULL;
int len = IdnToAscii (IDN_ALLOW_UNASSIGNED, wide, -1, NULL, 0);
if (len == 0)
{
errno = EINVAL;
goto error;
}
wchar_t *buf = vlc_alloc (len, sizeof (*buf));
if (unlikely(buf == NULL))
goto error;
if (!IdnToAscii (IDN_ALLOW_UNASSIGNED, wide, -1, buf, len))
{
free (buf);
errno = EINVAL;
goto error;
}
ret = FromWide (buf);
free (buf);
error:
free (wide);
return ret;
#else
/* No IDN support, filter out non-ASCII domain names */
for (const char *p = idn; *p; p++)
if (((unsigned char)*p) >= 0x80)
{
errno = ENOSYS;
return NULL;
}
return strdup (idn);
#endif
}
¤ Dauer der Verarbeitung: 0.6 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|