diff options
Diffstat (limited to 'roms/ipxe/src/core/uri.c')
-rw-r--r-- | roms/ipxe/src/core/uri.c | 218 |
1 files changed, 160 insertions, 58 deletions
diff --git a/roms/ipxe/src/core/uri.c b/roms/ipxe/src/core/uri.c index 3b5f270fe..73ad2b227 100644 --- a/roms/ipxe/src/core/uri.c +++ b/roms/ipxe/src/core/uri.c @@ -36,18 +36,23 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ctype.h> #include <ipxe/vsprintf.h> #include <ipxe/params.h> +#include <ipxe/tcpip.h> #include <ipxe/uri.h> /** - * Decode URI field (in place) + * Decode URI field * - * @v string String + * @v encoded Encoded field + * @v buf Data buffer + * @v len Length + * @ret len Length of data * * URI decoding can never increase the length of a string; we can * therefore safely decode in place. */ -static void uri_decode ( char *string ) { - char *dest = string; +size_t uri_decode ( const char *encoded, void *buf, size_t len ) { + uint8_t *out = buf; + unsigned int count = 0; char hexbuf[3]; char *hexbuf_end; char c; @@ -55,18 +60,42 @@ static void uri_decode ( char *string ) { unsigned int skip; /* Copy string, decoding escaped characters as necessary */ - do { - c = *(string++); + while ( ( c = *(encoded++) ) ) { if ( c == '%' ) { - snprintf ( hexbuf, sizeof ( hexbuf ), "%s", string ); + snprintf ( hexbuf, sizeof ( hexbuf ), "%s", encoded ); decoded = strtoul ( hexbuf, &hexbuf_end, 16 ); skip = ( hexbuf_end - hexbuf ); - string += skip; + encoded += skip; if ( skip ) c = decoded; } - *(dest++) = c; - } while ( c ); + if ( count < len ) + out[count] = c; + count++; + } + return count; +} + +/** + * Decode URI field in-place + * + * @v uri URI + * @v field URI field index + */ +static void uri_decode_inplace ( struct uri *uri, unsigned int field ) { + const char *encoded = uri_field ( uri, field ); + char *decoded = ( ( char * ) encoded ); + size_t len; + + /* Do nothing if field is not present */ + if ( ! encoded ) + return; + + /* Decode field in place */ + len = uri_decode ( encoded, decoded, strlen ( encoded ) ); + + /* Terminate decoded string */ + decoded[len] = '\0'; } /** @@ -115,15 +144,20 @@ static int uri_character_escaped ( char c, unsigned int field ) { * '%', the full set of characters with significance to the * URL parser is "/#:@?". We choose for each URI field which * of these require escaping in our use cases. + * + * For the scheme field (equivalently, if field is zero), we + * escape anything that has significance not just for our URI + * parser but for any other URI parsers (e.g. HTTP query + * string parsers, which care about '=' and '&'). */ static const char *escaped[URI_FIELDS] = { - /* Scheme: escape everything */ - [URI_SCHEME] = "/#:@?", + /* Scheme or default: escape everything */ + [URI_SCHEME] = "/#:@?=&", /* Opaque part: escape characters which would affect * the reparsing of the URI, allowing everything else * (e.g. ':', which will appear in iSCSI URIs). */ - [URI_OPAQUE] = "/#", + [URI_OPAQUE] = "#", /* User name: escape everything */ [URI_USER] = "/#:@?", /* Password: escape everything */ @@ -157,14 +191,16 @@ static int uri_character_escaped ( char c, unsigned int field ) { /** * Encode URI field * - * @v uri URI * @v field URI field index - * @v buf Buffer to contain encoded string + * @v raw Raw data + * @v raw_len Length of raw data + * @v buf Buffer * @v len Length of buffer * @ret len Length of encoded string (excluding NUL) */ -size_t uri_encode ( const char *string, unsigned int field, +size_t uri_encode ( unsigned int field, const void *raw, size_t raw_len, char *buf, ssize_t len ) { + const uint8_t *raw_bytes = ( ( const uint8_t * ) raw ); ssize_t remaining = len; size_t used; char c; @@ -174,7 +210,8 @@ size_t uri_encode ( const char *string, unsigned int field, buf[0] = '\0'; /* Copy string, escaping as necessary */ - while ( ( c = *(string++) ) ) { + while ( raw_len-- ) { + c = *(raw_bytes++); if ( uri_character_escaped ( c, field ) ) { used = ssnprintf ( buf, remaining, "%%%02X", c ); } else { @@ -188,6 +225,21 @@ size_t uri_encode ( const char *string, unsigned int field, } /** + * Encode URI field string + * + * @v field URI field index + * @v string String + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of encoded string (excluding NUL) + */ +size_t uri_encode_string ( unsigned int field, const char *string, + char *buf, ssize_t len ) { + + return uri_encode ( field, string, strlen ( string ), buf, len ); +} + +/** * Dump URI for debugging * * @v uri URI @@ -316,7 +368,7 @@ struct uri * parse_uri ( const char *uri_string ) { goto done; /* Identify net/absolute/relative path */ - if ( strncmp ( path, "//", 2 ) == 0 ) { + if ( uri->scheme && ( strncmp ( path, "//", 2 ) == 0 ) ) { /* Net path. If this is terminated by the first '/' * of an absolute path, then we have no space for a * terminator after the authority field, so shuffle @@ -367,13 +419,11 @@ struct uri * parse_uri ( const char *uri_string ) { uri->port = tmp; } + done: /* Decode fields in-place */ - for ( field = 0 ; field < URI_FIELDS ; field++ ) { - if ( uri_field ( uri, field ) ) - uri_decode ( ( char * ) uri_field ( uri, field ) ); - } + for ( field = 0 ; field < URI_FIELDS ; field++ ) + uri_decode_inplace ( uri, field ); - done: DBGC ( uri, "URI parsed \"%s\" to", uri_string ); uri_dump ( uri ); DBGC ( uri, "\n" ); @@ -406,10 +456,8 @@ unsigned int uri_port ( const struct uri *uri, unsigned int default_port ) { */ size_t format_uri ( const struct uri *uri, char *buf, size_t len ) { static const char prefixes[URI_FIELDS] = { - [URI_OPAQUE] = ':', [URI_PASSWORD] = ':', [URI_PORT] = ':', - [URI_PATH] = '/', [URI_QUERY] = '?', [URI_FRAGMENT] = '#', }; @@ -436,21 +484,19 @@ size_t format_uri ( const struct uri *uri, char *buf, size_t len ) { prefix = prefixes[field]; if ( ( field == URI_HOST ) && ( uri->user != NULL ) ) prefix = '@'; - if ( ( field == URI_PATH ) && ( uri->path[0] == '/' ) ) - prefix = '\0'; if ( prefix ) { used += ssnprintf ( ( buf + used ), ( len - used ), "%c", prefix ); } /* Encode this field */ - used += uri_encode ( uri_field ( uri, field ), field, - ( buf + used ), ( len - used ) ); + used += uri_encode_string ( field, uri_field ( uri, field ), + ( buf + used ), ( len - used ) ); /* Suffix this field, if applicable */ - if ( ( field == URI_SCHEME ) && ( ! uri->opaque ) ) { + if ( field == URI_SCHEME ) { used += ssnprintf ( ( buf + used ), ( len - used ), - "://" ); + ":%s", ( uri->host ? "//" : "" ) ); } } @@ -556,7 +602,7 @@ struct uri * uri_dup ( const struct uri *uri ) { * * @v base_uri Base path * @v relative_uri Relative path - * @ret resolved_uri Resolved path + * @ret resolved_uri Resolved path, or NULL on failure * * Takes a base path (e.g. "/var/lib/tftpboot/vmlinuz" and a relative * path (e.g. "initrd.gz") and produces a new path @@ -567,9 +613,8 @@ struct uri * uri_dup ( const struct uri *uri ) { */ char * resolve_path ( const char *base_path, const char *relative_path ) { - size_t base_len = ( strlen ( base_path ) + 1 ); - char base_path_copy[base_len]; - char *base_tmp = base_path_copy; + char *base_copy; + char *base_tmp; char *resolved; /* If relative path is absolute, just re-use it */ @@ -577,8 +622,12 @@ char * resolve_path ( const char *base_path, return strdup ( relative_path ); /* Create modifiable copy of path for dirname() */ - memcpy ( base_tmp, base_path, base_len ); - base_tmp = dirname ( base_tmp ); + base_copy = strdup ( base_path ); + if ( ! base_copy ) + return NULL; + + /* Strip filename portion of base path */ + base_tmp = dirname ( base_copy ); /* Process "./" and "../" elements */ while ( *relative_path == '.' ) { @@ -608,8 +657,8 @@ char * resolve_path ( const char *base_path, if ( asprintf ( &resolved, "%s%s%s", base_tmp, ( ( base_tmp[ strlen ( base_tmp ) - 1 ] == '/' ) ? "" : "/" ), relative_path ) < 0 ) - return NULL; - + resolved = NULL; + free ( base_copy ); return resolved; } @@ -618,7 +667,7 @@ char * resolve_path ( const char *base_path, * * @v base_uri Base URI, or NULL * @v relative_uri Relative URI - * @ret resolved_uri Resolved URI + * @ret resolved_uri Resolved URI, or NULL on failure * * Takes a base URI (e.g. "http://ipxe.org/kernels/vmlinuz" and a * relative URI (e.g. "../initrds/initrd.gz") and produces a new URI @@ -662,30 +711,83 @@ struct uri * resolve_uri ( const struct uri *base_uri, } /** - * Construct TFTP URI from next-server and filename + * Construct TFTP URI from server address and filename + * + * @v sa_server Server address + * @v filename Filename + * @ret uri URI, or NULL on failure + */ +static struct uri * tftp_uri ( struct sockaddr *sa_server, + const char *filename ) { + struct sockaddr_tcpip *st_server = + ( ( struct sockaddr_tcpip * ) sa_server ); + char buf[ 6 /* "65535" + NUL */ ]; + char *path; + struct uri tmp; + struct uri *uri = NULL; + + /* Initialise TFTP URI */ + memset ( &tmp, 0, sizeof ( tmp ) ); + tmp.scheme = "tftp"; + + /* Construct TFTP server address */ + tmp.host = sock_ntoa ( sa_server ); + if ( ! tmp.host ) + goto err_host; + + /* Construct TFTP server port, if applicable */ + if ( st_server->st_port ) { + snprintf ( buf, sizeof ( buf ), "%d", + ntohs ( st_server->st_port ) ); + tmp.port = buf; + } + + /* Construct TFTP path */ + if ( asprintf ( &path, "/%s", filename ) < 0 ) + goto err_path; + tmp.path = path; + + /* Demangle URI */ + uri = uri_dup ( &tmp ); + if ( ! uri ) + goto err_uri; + + err_uri: + free ( path ); + err_path: + err_host: + return uri; +} + +/** + * Construct URI from server address and filename * - * @v next_server Next-server address - * @v port Port number, or zero to use the default port + * @v sa_server Server address * @v filename Filename * @ret uri URI, or NULL on failure * - * TFTP filenames specified via the DHCP next-server field often + * PXE TFTP filenames specified via the DHCP next-server field often * contain characters such as ':' or '#' which would confuse the * generic URI parser. We provide a mechanism for directly * constructing a TFTP URI from the next-server and filename. */ -struct uri * tftp_uri ( struct in_addr next_server, unsigned int port, - const char *filename ) { - char buf[ 6 /* "65535" + NUL */ ]; - struct uri uri; - - memset ( &uri, 0, sizeof ( uri ) ); - uri.scheme = "tftp"; - uri.host = inet_ntoa ( next_server ); - if ( port ) { - snprintf ( buf, sizeof ( buf ), "%d", port ); - uri.port = buf; - } - uri.path = filename; - return uri_dup ( &uri ); +struct uri * pxe_uri ( struct sockaddr *sa_server, const char *filename ) { + struct uri *uri; + + /* Fail if filename is empty */ + if ( ! ( filename && filename[0] ) ) + return NULL; + + /* If filename is a hierarchical absolute URI, then use that + * URI. (We accept only hierarchical absolute URIs, since PXE + * filenames sometimes start with DOS drive letters such as + * "C:\", which get misinterpreted as opaque absolute URIs.) + */ + uri = parse_uri ( filename ); + if ( uri && uri_is_absolute ( uri ) && ( ! uri->opaque ) ) + return uri; + uri_put ( uri ); + + /* Otherwise, construct a TFTP URI directly */ + return tftp_uri ( sa_server, filename ); } |