mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1120 lines
31 KiB
1120 lines
31 KiB
/* |
|
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; |
|
}
|
|
|