forked from Qortal/Brooklyn
1121 lines
31 KiB
C
1121 lines
31 KiB
C
/*
|
|
Copyright (c) 2012, Broadcom Europe Ltd
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the copyright holder nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "containers/core/containers_uri.h"
|
|
|
|
/*****************************************************************************/
|
|
/* Internal types and definitions */
|
|
/*****************************************************************************/
|
|
|
|
typedef struct VC_URI_QUERY_T
|
|
{
|
|
char *name;
|
|
char *value;
|
|
} VC_URI_QUERY_T;
|
|
|
|
struct VC_URI_PARTS_T
|
|
{
|
|
char *scheme; /**< Unescaped scheme */
|
|
char *userinfo; /**< Unescaped userinfo */
|
|
char *host; /**< Unescaped host name/IP address */
|
|
char *port; /**< Unescaped port */
|
|
char *path; /**< Unescaped path */
|
|
char *path_extension; /**< Unescaped path extension */
|
|
char *fragment; /**< Unescaped fragment */
|
|
VC_URI_QUERY_T *queries; /**< Array of queries */
|
|
uint32_t num_queries; /**< Number of queries in array */
|
|
};
|
|
|
|
typedef const uint32_t *RESERVED_CHARS_TABLE_T;
|
|
|
|
/** Reserved character table for scheme component
|
|
* Controls, space, !"#$%&'()*,/:;<=>?@[\]^`{|} and 0x7F and above reserved. */
|
|
static uint32_t scheme_reserved_chars[8] = {
|
|
0xFFFFFFFF, 0xFC0097FF, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
|
|
};
|
|
|
|
/** Reserved character table for userinfo component
|
|
* Controls, space, "#%/<>?@[\]^`{|} and 0x7F and above reserved. */
|
|
static uint32_t userinfo_reserved_chars[8] = {
|
|
0xFFFFFFFF, 0xD000802D, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
|
|
};
|
|
|
|
/** Reserved character table for host component
|
|
* Controls, space, "#%/<>?@\^`{|} and 0x7F and above reserved. */
|
|
static uint32_t host_reserved_chars[8] = {
|
|
0xFFFFFFFF, 0xD000802D, 0x50000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
|
|
};
|
|
|
|
/** Reserved character table for port component
|
|
* Controls, space, !"#$%&'()*+,/:;<=>?@[\]^`{|} and 0x7F and above reserved. */
|
|
static uint32_t port_reserved_chars[8] = {
|
|
0xFFFFFFFF, 0xFC009FFF, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
|
|
};
|
|
|
|
/** Reserved character table for path component
|
|
* Controls, space, "#%<>?[\]^`{|} and 0x7F and above reserved. */
|
|
static uint32_t path_reserved_chars[8] = {
|
|
0xFFFFFFFF, 0xD000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
|
|
};
|
|
|
|
/** Reserved character table for query component
|
|
* Controls, space, "#%<>[\]^`{|} and 0x7F and above reserved. */
|
|
static uint32_t query_reserved_chars[8] = {
|
|
0xFFFFFFFF, 0x5000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
|
|
};
|
|
|
|
/** Reserved character table for fragment component
|
|
* Controls, space, "#%<>[\]^`{|} and 0x7F and above reserved. */
|
|
static uint32_t fragment_reserved_chars[8] = {
|
|
0xFFFFFFFF, 0x5000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
|
|
};
|
|
|
|
#define URI_RESERVED(C, TABLE) (!!((TABLE)[(unsigned char)(C) >> 5] & (1 << ((C) & 0x1F))))
|
|
|
|
#define SCHEME_DELIMITERS ":/?#"
|
|
#define NETWORK_DELIMITERS "@/?#"
|
|
#define HOST_PORT_DELIMITERS "/?#"
|
|
#define PATH_DELIMITERS "?#"
|
|
#define QUERY_DELIMITERS "#"
|
|
|
|
/*****************************************************************************/
|
|
/* Internal functions */
|
|
/*****************************************************************************/
|
|
|
|
static char to_hex(int v)
|
|
{
|
|
if (v > 9)
|
|
return 'A' + v - 10;
|
|
return '0' + v;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static uint32_t from_hex(const char *str, uint32_t str_len)
|
|
{
|
|
uint32_t val = 0;
|
|
|
|
while (str_len--)
|
|
{
|
|
char c = *str++;
|
|
if (c >= '0' && c <= '9')
|
|
c -= '0';
|
|
else if (c >= 'A' && c <= 'F')
|
|
c -= 'A' - 10;
|
|
else if (c >= 'a' && c <= 'f')
|
|
c -= 'a' - 10;
|
|
else
|
|
c = 0; /* Illegal character (not hex) */
|
|
val = (val << 4) + c;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static uint32_t escaped_length( const char *str, RESERVED_CHARS_TABLE_T reserved )
|
|
{
|
|
uint32_t ii;
|
|
uint32_t esclen = 0;
|
|
char c;
|
|
|
|
for (ii = strlen(str); ii > 0; ii--)
|
|
{
|
|
c = *str++;
|
|
if (URI_RESERVED(c, reserved))
|
|
{
|
|
/* Reserved character needs escaping as %xx */
|
|
esclen += 3;
|
|
} else {
|
|
esclen++;
|
|
}
|
|
}
|
|
|
|
return esclen;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static uint32_t escape_string( const char *str, char *escaped,
|
|
RESERVED_CHARS_TABLE_T reserved )
|
|
{
|
|
uint32_t ii;
|
|
uint32_t esclen = 0;
|
|
|
|
if (!str)
|
|
return 0;
|
|
|
|
for (ii = strlen(str); ii > 0; ii--)
|
|
{
|
|
char c = *str++;
|
|
|
|
if (URI_RESERVED(c, reserved))
|
|
{
|
|
escaped[esclen++] = '%';
|
|
escaped[esclen++] = to_hex((c >> 4) & 0xF);
|
|
escaped[esclen++] = to_hex(c & 0xF);
|
|
} else {
|
|
escaped[esclen++] = c;
|
|
}
|
|
}
|
|
|
|
return esclen;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static uint32_t unescaped_length( const char *str, uint32_t str_len )
|
|
{
|
|
uint32_t ii;
|
|
uint32_t unesclen = 0;
|
|
|
|
for (ii = 0; ii < str_len; ii++)
|
|
{
|
|
if (*str++ == '%' && (ii + 2) < str_len)
|
|
{
|
|
str += 2; /* Should be two hex values next */
|
|
ii += 2;
|
|
}
|
|
unesclen++;
|
|
}
|
|
|
|
return unesclen;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void unescape_string( const char *str, uint32_t str_len, char *unescaped )
|
|
{
|
|
uint32_t ii;
|
|
|
|
for (ii = 0; ii < str_len; ii++)
|
|
{
|
|
char c = *str++;
|
|
|
|
if (c == '%' && (ii + 2) < str_len )
|
|
{
|
|
c = (char)(from_hex(str, 2) & 0xFF);
|
|
str += 2;
|
|
ii += 2;
|
|
}
|
|
*unescaped++ = c;
|
|
}
|
|
|
|
*unescaped = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static char *create_unescaped_string( const char *escstr, uint32_t esclen )
|
|
{
|
|
char *unescstr;
|
|
|
|
unescstr = (char *)malloc(unescaped_length(escstr, esclen) + 1); /* Allow for NUL */
|
|
if (unescstr)
|
|
unescape_string(escstr, esclen, unescstr);
|
|
|
|
return unescstr;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static bool duplicate_string( const char *src, char **p_dst )
|
|
{
|
|
if (*p_dst)
|
|
free(*p_dst);
|
|
|
|
if (src)
|
|
{
|
|
size_t str_size = strlen(src) + 1;
|
|
|
|
*p_dst = (char *)malloc(str_size);
|
|
if (!*p_dst)
|
|
return false;
|
|
|
|
memcpy(*p_dst, src, str_size);
|
|
} else
|
|
*p_dst = NULL;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void release_string( char **str )
|
|
{
|
|
if (*str)
|
|
{
|
|
free(*str);
|
|
*str = NULL;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void to_lower_string( char *str )
|
|
{
|
|
char c;
|
|
|
|
while ((c = *str) != '\0')
|
|
{
|
|
if (c >= 'A' && c <= 'Z')
|
|
*str = c - 'A' + 'a';
|
|
str++;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static const char *vc_uri_find_delimiter(const char *str, const char *delimiters)
|
|
{
|
|
const char *ptr = str;
|
|
char c;
|
|
|
|
while ((c = *ptr) != 0)
|
|
{
|
|
if (strchr(delimiters, c) != 0)
|
|
break;
|
|
ptr++;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void vc_uri_set_path_extension(VC_URI_PARTS_T *p_uri)
|
|
{
|
|
char *end;
|
|
|
|
if (!p_uri)
|
|
return;
|
|
|
|
p_uri->path_extension = NULL;
|
|
|
|
if (!p_uri->path)
|
|
return;
|
|
|
|
/* Look for the magic dot */
|
|
for (end = p_uri->path + strlen(p_uri->path); *end != '.'; end--)
|
|
if (end == p_uri->path || *end == '/' || *end == '\\')
|
|
return;
|
|
|
|
p_uri->path_extension = end + 1;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static bool parse_authority( VC_URI_PARTS_T *p_uri, const char *str,
|
|
uint32_t str_len, const char *userinfo_end )
|
|
{
|
|
const char *marker = userinfo_end;
|
|
const char *str_end = str + str_len;
|
|
char c;
|
|
|
|
if (marker)
|
|
{
|
|
p_uri->userinfo = create_unescaped_string(str, marker - str);
|
|
if (!p_uri->userinfo)
|
|
return false;
|
|
str = marker + 1; /* Past '@' character */
|
|
}
|
|
|
|
if (*str == '[') /* IPvFuture / IPv6 address */
|
|
{
|
|
/* Find end of address marker */
|
|
for (marker = str; marker < str_end; marker++)
|
|
{
|
|
c = *marker;
|
|
if (c == ']')
|
|
break;
|
|
}
|
|
|
|
if (marker < str_end)
|
|
marker++; /* Found marker, move to next character */
|
|
} else {
|
|
/* Find port value marker*/
|
|
for (marker = str; marker < str_end; marker++)
|
|
{
|
|
c = *marker;
|
|
if (c == ':')
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Always store the host, even if empty, to trigger the "://" form of URI */
|
|
p_uri->host = create_unescaped_string(str, marker - str);
|
|
if (!p_uri->host)
|
|
return false;
|
|
to_lower_string(p_uri->host); /* Host names are case-insensitive */
|
|
|
|
if (*marker == ':')
|
|
{
|
|
str = marker + 1;
|
|
p_uri->port = create_unescaped_string(str, str_end - str);
|
|
if (!p_uri->port)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static bool store_query( VC_URI_PARTS_T *p_uri, const char *name_start,
|
|
const char *equals_ptr, const char *query_end)
|
|
{
|
|
uint32_t name_len, value_len;
|
|
|
|
if (equals_ptr)
|
|
{
|
|
name_len = equals_ptr - name_start;
|
|
value_len = query_end - equals_ptr - 1; /* Don't include '=' itself */
|
|
} else {
|
|
name_len = query_end - name_start;
|
|
value_len = 0;
|
|
}
|
|
|
|
/* Only store something if there is a name */
|
|
if (name_len)
|
|
{
|
|
char *name, *value = NULL;
|
|
VC_URI_QUERY_T *p_query;
|
|
|
|
if (equals_ptr)
|
|
{
|
|
value = create_unescaped_string(equals_ptr + 1, value_len);
|
|
if (!value)
|
|
return false;
|
|
equals_ptr = query_end;
|
|
}
|
|
|
|
name = create_unescaped_string(name_start, name_len);
|
|
if (!name)
|
|
{
|
|
if (value)
|
|
free(value);
|
|
return false;
|
|
}
|
|
|
|
/* Store query data in URI structure */
|
|
p_query = &p_uri->queries[ p_uri->num_queries++ ];
|
|
p_query->name = name;
|
|
p_query->value = value;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static bool parse_query( VC_URI_PARTS_T *p_uri, const char *str, uint32_t str_len )
|
|
{
|
|
uint32_t ii;
|
|
uint32_t query_count;
|
|
VC_URI_QUERY_T *queries;
|
|
const char *name_start = str;
|
|
const char *equals_ptr = NULL;
|
|
char c;
|
|
|
|
if (!str_len)
|
|
return true;
|
|
|
|
/* Scan for the number of query items, so array can be allocated the right size */
|
|
query_count = 1; /* At least */
|
|
for (ii = 0; ii < str_len; ii++)
|
|
{
|
|
c = str[ii];
|
|
|
|
if (c == '&' || c ==';')
|
|
query_count++;
|
|
}
|
|
|
|
queries = (VC_URI_QUERY_T *)malloc(query_count * sizeof(VC_URI_QUERY_T));
|
|
if (!queries)
|
|
return false;
|
|
|
|
p_uri->queries = queries;
|
|
|
|
/* Go back and parse the string for each query item and store in array */
|
|
for (ii = 0; ii < str_len; ii++)
|
|
{
|
|
c = *str;
|
|
|
|
/* Take first '=' as break between name and value */
|
|
if (c == '=' && !equals_ptr)
|
|
equals_ptr = str;
|
|
|
|
/* If at the end of the name or name/value pair */
|
|
if (c == '&' || c ==';')
|
|
{
|
|
if (!store_query(p_uri, name_start, equals_ptr, str))
|
|
return false;
|
|
|
|
equals_ptr = NULL;
|
|
name_start = str + 1;
|
|
}
|
|
|
|
str++;
|
|
}
|
|
|
|
return store_query(p_uri, name_start, equals_ptr, str);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static uint32_t calculate_uri_length(const VC_URI_PARTS_T *p_uri)
|
|
{
|
|
uint32_t length = 0;
|
|
uint32_t count;
|
|
|
|
/* With no scheme, assume this is a plain path (without escaping) */
|
|
if (!p_uri->scheme)
|
|
return p_uri->path ? strlen(p_uri->path) : 0;
|
|
|
|
length += escaped_length(p_uri->scheme, scheme_reserved_chars);
|
|
length++; /* for the colon */
|
|
|
|
if (p_uri->host)
|
|
{
|
|
length += escaped_length(p_uri->host, host_reserved_chars) + 2; /* for the double slash */
|
|
if (p_uri->userinfo)
|
|
length += escaped_length(p_uri->userinfo, userinfo_reserved_chars) + 1; /* for the '@' */
|
|
if (p_uri->port)
|
|
length += escaped_length(p_uri->port, port_reserved_chars) + 1; /* for the ':' */
|
|
}
|
|
|
|
if (p_uri->path)
|
|
length += escaped_length(p_uri->path, path_reserved_chars);
|
|
|
|
count = p_uri->num_queries;
|
|
if (count)
|
|
{
|
|
VC_URI_QUERY_T * queries = p_uri->queries;
|
|
|
|
while (count--)
|
|
{
|
|
/* The name is preceded by either the '?' or the '&' */
|
|
length += escaped_length(queries->name, query_reserved_chars) + 1;
|
|
|
|
/* The value is optional, but if present will require an '=' */
|
|
if (queries->value)
|
|
length += escaped_length(queries->value, query_reserved_chars) + 1;
|
|
queries++;
|
|
}
|
|
}
|
|
|
|
if (p_uri->fragment)
|
|
length += escaped_length(p_uri->fragment, fragment_reserved_chars) + 1; /* for the '#' */
|
|
|
|
return length;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void build_uri(const VC_URI_PARTS_T *p_uri, char *buffer, size_t buffer_size)
|
|
{
|
|
uint32_t count;
|
|
|
|
/* With no scheme, assume this is a plain path (without escaping) */
|
|
if (!p_uri->scheme)
|
|
{
|
|
if (p_uri->path)
|
|
strncpy(buffer, p_uri->path, buffer_size);
|
|
else
|
|
buffer[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
buffer += escape_string(p_uri->scheme, buffer, scheme_reserved_chars);
|
|
*buffer++ = ':';
|
|
|
|
if (p_uri->host)
|
|
{
|
|
*buffer++ = '/';
|
|
*buffer++ = '/';
|
|
if (p_uri->userinfo)
|
|
{
|
|
buffer += escape_string(p_uri->userinfo, buffer, userinfo_reserved_chars);
|
|
*buffer++ = '@';
|
|
}
|
|
buffer += escape_string(p_uri->host, buffer, host_reserved_chars);
|
|
if (p_uri->port)
|
|
{
|
|
*buffer++ = ':';
|
|
buffer += escape_string(p_uri->port, buffer, port_reserved_chars);
|
|
}
|
|
}
|
|
|
|
if (p_uri->path)
|
|
buffer += escape_string(p_uri->path, buffer, path_reserved_chars);
|
|
|
|
count = p_uri->num_queries;
|
|
if (count)
|
|
{
|
|
VC_URI_QUERY_T * queries = p_uri->queries;
|
|
|
|
*buffer++ = '?';
|
|
while (count--)
|
|
{
|
|
buffer += escape_string(queries->name, buffer, query_reserved_chars);
|
|
|
|
if (queries->value)
|
|
{
|
|
*buffer++ = '=';
|
|
buffer += escape_string(queries->value, buffer, query_reserved_chars);
|
|
}
|
|
|
|
/* Add separator if there is another item to add */
|
|
if (count)
|
|
*buffer++ = '&';
|
|
|
|
queries++;
|
|
}
|
|
}
|
|
|
|
if (p_uri->fragment)
|
|
{
|
|
*buffer++ = '#';
|
|
buffer += escape_string(p_uri->fragment, buffer, fragment_reserved_chars);
|
|
}
|
|
|
|
*buffer = '\0';
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static bool vc_uri_copy_base_path( const VC_URI_PARTS_T *base_uri,
|
|
VC_URI_PARTS_T *relative_uri )
|
|
{
|
|
const char *base_path = vc_uri_path(base_uri);
|
|
|
|
/* No path set (or empty), copy from base */
|
|
if (!vc_uri_set_path(relative_uri, base_path))
|
|
return false;
|
|
|
|
/* If relative path has no queries, copy base queries across */
|
|
if (!vc_uri_num_queries(relative_uri))
|
|
{
|
|
uint32_t base_queries = vc_uri_num_queries(base_uri);
|
|
const char *name, *value;
|
|
uint32_t ii;
|
|
|
|
for (ii = 0; ii < base_queries; ii++)
|
|
{
|
|
vc_uri_query(base_uri, ii, &name, &value);
|
|
if (!vc_uri_add_query(relative_uri, name, value))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void vc_uri_remove_single_dot_segments( char *path_str )
|
|
{
|
|
char *slash = path_str - 1;
|
|
|
|
while (slash++)
|
|
{
|
|
if (*slash == '.')
|
|
{
|
|
switch (slash[1])
|
|
{
|
|
case '/': /* Single dot segment, remove it */
|
|
memmove(slash, slash + 2, strlen(slash + 2) + 1);
|
|
break;
|
|
case '\0': /* Trailing single dot, remove it */
|
|
*slash = '\0';
|
|
break;
|
|
default: /* Something else (e.g. ".." or ".foo") */
|
|
; /* Do nothing */
|
|
}
|
|
}
|
|
slash = strchr(slash, '/');
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
static void vc_uri_remove_double_dot_segments( char *path_str )
|
|
{
|
|
char *previous_segment = path_str;
|
|
char *slash;
|
|
|
|
if (previous_segment[0] == '/')
|
|
previous_segment++;
|
|
|
|
/* Remove strings of the form "<segment>/../" (or "<segment>/.." at the end of the path)
|
|
* as long as <segment> is not itself ".." */
|
|
slash = strchr(previous_segment, '/');
|
|
while (slash)
|
|
{
|
|
if (previous_segment[0] != '.' || previous_segment[1] != '.' || previous_segment[2] != '/')
|
|
{
|
|
if (slash[1] == '.' && slash[2] == '.')
|
|
{
|
|
bool previous_segment_removed = true;
|
|
|
|
switch (slash[3])
|
|
{
|
|
case '/': /* "/../" inside path, snip it and last segment out */
|
|
memmove(previous_segment, slash + 4, strlen(slash + 4) + 1);
|
|
break;
|
|
case '\0': /* Trailing "/.." on path, just terminate path at last segment */
|
|
*previous_segment = '\0';
|
|
break;
|
|
default: /* Not a simple ".." segment, so skip over it */
|
|
previous_segment_removed = false;
|
|
}
|
|
|
|
if (previous_segment_removed)
|
|
{
|
|
/* The segment just removed was the first one in the path (optionally
|
|
* prefixed by a slash), so no more can be removed: stop. */
|
|
if (previous_segment < path_str + 2)
|
|
break;
|
|
|
|
/* Move back to slash before previous segment, or the start of the path */
|
|
slash = previous_segment - 1;
|
|
while (--slash >= path_str && *slash != '/')
|
|
; /* Everything done in the while */
|
|
}
|
|
}
|
|
}
|
|
previous_segment = slash + 1;
|
|
slash = strchr(previous_segment, '/');
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* API functions */
|
|
/*****************************************************************************/
|
|
|
|
VC_URI_PARTS_T *vc_uri_create( void )
|
|
{
|
|
VC_URI_PARTS_T *p_uri;
|
|
|
|
p_uri = (VC_URI_PARTS_T *)malloc(sizeof(VC_URI_PARTS_T));
|
|
if (p_uri)
|
|
{
|
|
memset(p_uri, 0, sizeof(VC_URI_PARTS_T));
|
|
}
|
|
|
|
return p_uri;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
void vc_uri_clear( VC_URI_PARTS_T *p_uri )
|
|
{
|
|
if (!p_uri)
|
|
return;
|
|
|
|
release_string(&p_uri->scheme);
|
|
release_string(&p_uri->userinfo);
|
|
release_string(&p_uri->host);
|
|
release_string(&p_uri->port);
|
|
release_string(&p_uri->path);
|
|
release_string(&p_uri->fragment);
|
|
|
|
if (p_uri->queries)
|
|
{
|
|
VC_URI_QUERY_T *queries = p_uri->queries;
|
|
uint32_t count = p_uri->num_queries;
|
|
|
|
while (count--)
|
|
{
|
|
release_string(&queries[count].name);
|
|
release_string(&queries[count].value);
|
|
}
|
|
|
|
free(queries);
|
|
p_uri->queries = NULL;
|
|
p_uri->num_queries = 0;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
void vc_uri_release( VC_URI_PARTS_T *p_uri )
|
|
{
|
|
if (!p_uri)
|
|
return;
|
|
|
|
vc_uri_clear(p_uri);
|
|
|
|
free(p_uri);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_parse( VC_URI_PARTS_T *p_uri, const char *uri )
|
|
{
|
|
const char *marker;
|
|
uint32_t len;
|
|
|
|
if (!p_uri || !uri)
|
|
return false;
|
|
|
|
vc_uri_clear(p_uri);
|
|
|
|
/* URI = scheme ":" hier_part [ "?" query ] [ "#" fragment ] */
|
|
|
|
/* Find end of scheme, or another separator */
|
|
marker = vc_uri_find_delimiter(uri, SCHEME_DELIMITERS);
|
|
|
|
if (*marker == ':')
|
|
{
|
|
len = (marker - uri);
|
|
if (isalpha((int)*uri) && len == 1 && marker[1] == '\\')
|
|
{
|
|
/* Looks like a bare, absolute DOS/Windows filename with a drive letter */
|
|
/* coverity[double_free] Pointer freed and set to NULL */
|
|
bool ret = duplicate_string(uri, &p_uri->path);
|
|
vc_uri_set_path_extension(p_uri);
|
|
return ret;
|
|
}
|
|
|
|
p_uri->scheme = create_unescaped_string(uri, len);
|
|
if (!p_uri->scheme)
|
|
goto error;
|
|
|
|
to_lower_string(p_uri->scheme); /* Schemes should be handled case-insensitively */
|
|
uri = marker + 1;
|
|
}
|
|
|
|
if (uri[0] == '/' && uri[1] == '/') /* hier-part includes authority */
|
|
{
|
|
const char *userinfo_end = NULL;
|
|
|
|
/* authority = [ userinfo "@" ] host [ ":" port ] */
|
|
uri += 2;
|
|
|
|
marker = vc_uri_find_delimiter(uri, NETWORK_DELIMITERS);
|
|
if (*marker == '@')
|
|
{
|
|
userinfo_end = marker;
|
|
marker = vc_uri_find_delimiter(marker + 1, HOST_PORT_DELIMITERS);
|
|
}
|
|
|
|
if (!parse_authority(p_uri, uri, marker - uri, userinfo_end))
|
|
goto error;
|
|
uri = marker;
|
|
}
|
|
|
|
/* path */
|
|
marker = vc_uri_find_delimiter(uri, PATH_DELIMITERS);
|
|
len = marker - uri;
|
|
if (len)
|
|
{
|
|
p_uri->path = create_unescaped_string(uri, len);
|
|
vc_uri_set_path_extension(p_uri);
|
|
if (!p_uri->path)
|
|
goto error;
|
|
}
|
|
|
|
/* query */
|
|
if (*marker == '?')
|
|
{
|
|
uri = marker + 1;
|
|
marker = vc_uri_find_delimiter(uri, QUERY_DELIMITERS);
|
|
if (!parse_query(p_uri, uri, marker - uri))
|
|
goto error;
|
|
}
|
|
|
|
/* fragment */
|
|
if (*marker == '#')
|
|
{
|
|
uri = marker + 1;
|
|
p_uri->fragment = create_unescaped_string(uri, strlen(uri));
|
|
if (!p_uri->fragment)
|
|
goto error;
|
|
}
|
|
|
|
return true;
|
|
|
|
error:
|
|
vc_uri_clear(p_uri);
|
|
return false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
uint32_t vc_uri_build( const VC_URI_PARTS_T *p_uri, char *buffer, size_t buffer_size )
|
|
{
|
|
uint32_t required_length;
|
|
|
|
if (!p_uri)
|
|
return 0;
|
|
|
|
required_length = calculate_uri_length(p_uri);
|
|
if (buffer && required_length < buffer_size) /* Allow for NUL */
|
|
build_uri(p_uri, buffer, buffer_size);
|
|
|
|
return required_length;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
const char *vc_uri_scheme( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->scheme : NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
const char *vc_uri_userinfo( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->userinfo : NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
const char *vc_uri_host( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->host : NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
const char *vc_uri_port( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->port : NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
const char *vc_uri_path( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->path : NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
const char *vc_uri_path_extension( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->path_extension : NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
const char *vc_uri_fragment( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->fragment : NULL;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
uint32_t vc_uri_num_queries( const VC_URI_PARTS_T *p_uri )
|
|
{
|
|
return p_uri ? p_uri->num_queries : 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
void vc_uri_query( const VC_URI_PARTS_T *p_uri, uint32_t index, const char **p_name, const char **p_value )
|
|
{
|
|
const char *name = NULL;
|
|
const char *value = NULL;
|
|
|
|
if (p_uri)
|
|
{
|
|
if (index < p_uri->num_queries)
|
|
{
|
|
name = p_uri->queries[index].name;
|
|
value = p_uri->queries[index].value;
|
|
}
|
|
}
|
|
|
|
if (p_name)
|
|
*p_name = name;
|
|
if (p_value)
|
|
*p_value = value;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_find_query( VC_URI_PARTS_T *p_uri, uint32_t *p_index, const char *name, const char **p_value )
|
|
{
|
|
unsigned int i = p_index ? *p_index : 0;
|
|
|
|
if (!p_uri)
|
|
return false;
|
|
|
|
for (; name && i < p_uri->num_queries; i++)
|
|
{
|
|
if (!strcmp(name, p_uri->queries[i].name))
|
|
{
|
|
if (p_value)
|
|
*p_value = p_uri->queries[i].value;
|
|
if (p_index)
|
|
*p_index = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_set_scheme( VC_URI_PARTS_T *p_uri, const char *scheme )
|
|
{
|
|
return p_uri ? duplicate_string(scheme, &p_uri->scheme) : false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_set_userinfo( VC_URI_PARTS_T *p_uri, const char *userinfo )
|
|
{
|
|
return p_uri ? duplicate_string(userinfo, &p_uri->userinfo) : false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_set_host( VC_URI_PARTS_T *p_uri, const char *host )
|
|
{
|
|
return p_uri ? duplicate_string(host, &p_uri->host) : false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_set_port( VC_URI_PARTS_T *p_uri, const char *port )
|
|
{
|
|
return p_uri ? duplicate_string(port, &p_uri->port) : false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_set_path( VC_URI_PARTS_T *p_uri, const char *path )
|
|
{
|
|
bool ret = p_uri ? duplicate_string(path, &p_uri->path) : false;
|
|
vc_uri_set_path_extension(p_uri);
|
|
return ret;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_set_fragment( VC_URI_PARTS_T *p_uri, const char *fragment )
|
|
{
|
|
return p_uri ? duplicate_string(fragment, &p_uri->fragment) : false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_add_query( VC_URI_PARTS_T *p_uri, const char *name, const char *value )
|
|
{
|
|
VC_URI_QUERY_T *queries;
|
|
uint32_t count;
|
|
|
|
if (!p_uri || !name)
|
|
return false;
|
|
|
|
count = p_uri->num_queries;
|
|
if (p_uri->queries)
|
|
queries = (VC_URI_QUERY_T *)realloc(p_uri->queries, (count + 1) * sizeof(VC_URI_QUERY_T));
|
|
else
|
|
queries = (VC_URI_QUERY_T *)malloc(sizeof(VC_URI_QUERY_T));
|
|
|
|
if (!queries)
|
|
return false;
|
|
|
|
/* Always store the pointer, in case it has changed, and even if we fail to copy name/value */
|
|
p_uri->queries = queries;
|
|
queries[count].name = NULL;
|
|
queries[count].value = NULL;
|
|
|
|
if (duplicate_string(name, &queries[count].name))
|
|
{
|
|
if (duplicate_string(value, &queries[count].value))
|
|
{
|
|
/* Successful exit path */
|
|
p_uri->num_queries++;
|
|
return true;
|
|
}
|
|
|
|
release_string(&queries[count].name);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
bool vc_uri_merge( const VC_URI_PARTS_T *base_uri, VC_URI_PARTS_T *relative_uri )
|
|
{
|
|
bool success = true;
|
|
const char *relative_path;
|
|
|
|
/* If scheme is already set, the URI is already absolute */
|
|
if (relative_uri->scheme)
|
|
return true;
|
|
|
|
/* Otherwise, copy the base scheme */
|
|
if (!duplicate_string(base_uri->scheme, &relative_uri->scheme))
|
|
return false;
|
|
|
|
/* If any of the network info is set, use the rest of the relative URI as-is */
|
|
if (relative_uri->host || relative_uri->port || relative_uri->userinfo)
|
|
return true;
|
|
|
|
/* Otherwise, copy the base network info */
|
|
if (!duplicate_string(base_uri->host, &relative_uri->host) ||
|
|
!duplicate_string(base_uri->port, &relative_uri->port) ||
|
|
!duplicate_string(base_uri->userinfo, &relative_uri->userinfo))
|
|
return false;
|
|
|
|
relative_path = relative_uri->path;
|
|
|
|
if (!relative_path || !*relative_path)
|
|
{
|
|
/* No relative path (could be queries and/or fragment), so take base path */
|
|
success = vc_uri_copy_base_path(base_uri, relative_uri);
|
|
}
|
|
else if (*relative_path != '/')
|
|
{
|
|
const char *base_path = base_uri->path;
|
|
char *merged_path;
|
|
char *slash;
|
|
size_t len;
|
|
|
|
/* Path is relative, merge in with base path */
|
|
if (!base_path || !*base_path)
|
|
{
|
|
if (relative_uri->host || relative_uri->port || relative_uri->userinfo)
|
|
base_path = "/"; /* Need a separator to split network info from path */
|
|
else
|
|
base_path = "";
|
|
}
|
|
|
|
len = strlen(base_path) + strlen(relative_path) + 1;
|
|
|
|
/* Allocate space for largest possible combined path */
|
|
merged_path = (char *)malloc(len);
|
|
if (!merged_path)
|
|
return false;
|
|
|
|
strncpy(merged_path, base_path, len);
|
|
|
|
slash = strrchr(merged_path, '/'); /* Note: reverse search */
|
|
if (*relative_path == ';')
|
|
{
|
|
char *semi;
|
|
|
|
/* Relative path is just parameters, so remove any base parameters in final segment */
|
|
if (!slash)
|
|
slash = merged_path;
|
|
semi = strchr(slash, ';');
|
|
if (semi)
|
|
semi[0] = '\0';
|
|
} else {
|
|
/* Remove final segment */
|
|
if (slash)
|
|
slash[1] = '\0';
|
|
else
|
|
merged_path[0] = '\0';
|
|
}
|
|
strncat(merged_path, relative_path, len - strlen(merged_path) - 1);
|
|
|
|
vc_uri_remove_single_dot_segments(merged_path);
|
|
vc_uri_remove_double_dot_segments(merged_path);
|
|
|
|
success = duplicate_string(merged_path, &relative_uri->path);
|
|
|
|
free(merged_path);
|
|
}
|
|
/* Otherwise path is absolute, which can be left as-is */
|
|
|
|
return success;
|
|
}
|