mirror of
https://github.com/ipxe/ipxe
synced 2026-02-11 05:38:16 +03:00
RFC3986 allows for colons to appear within the path component of a relative URI, but iPXE will currently parse such URIs incorrectly by interpreting the text before the colon as the URI scheme. Fix by checking for valid characters when identifying the URI scheme. Deliberately deviate from the RFC3986 definition of valid characters by accepting "_" (which was incorrectly used in the iPXE-specific "ib_srp" URI scheme and so must be accepted for compatibility with existing deployments), and by omitting the code to check for characters that are not used in any URI scheme supported by iPXE. Reported-by: Ignat Korchagin <ignat@cloudflare.com> Signed-off-by: Michael Brown <mcb30@ipxe.org>
828 lines
21 KiB
C
828 lines
21 KiB
C
/*
|
|
* Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or 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
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU 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.
|
|
*
|
|
* You can also choose to distribute this program under the terms of
|
|
* the Unmodified Binary Distribution Licence (as given in the file
|
|
* COPYING.UBDL), provided that you have satisfied its requirements.
|
|
*/
|
|
|
|
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
|
|
|
/** @file
|
|
*
|
|
* Uniform Resource Identifiers
|
|
*
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <libgen.h>
|
|
#include <ctype.h>
|
|
#include <ipxe/vsprintf.h>
|
|
#include <ipxe/params.h>
|
|
#include <ipxe/tcpip.h>
|
|
#include <ipxe/uri.h>
|
|
|
|
/**
|
|
* Decode URI field
|
|
*
|
|
* @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.
|
|
*/
|
|
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;
|
|
char decoded;
|
|
unsigned int skip;
|
|
|
|
/* Copy string, decoding escaped characters as necessary */
|
|
while ( ( c = *(encoded++) ) ) {
|
|
if ( c == '%' ) {
|
|
snprintf ( hexbuf, sizeof ( hexbuf ), "%s", encoded );
|
|
decoded = strtoul ( hexbuf, &hexbuf_end, 16 );
|
|
skip = ( hexbuf_end - hexbuf );
|
|
encoded += skip;
|
|
if ( skip )
|
|
c = decoded;
|
|
}
|
|
if ( count < len )
|
|
out[count] = c;
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Decode URI field in-place
|
|
*
|
|
* @v encoded Encoded field, or NULL
|
|
*/
|
|
static void uri_decode_inplace ( char *encoded ) {
|
|
char *decoded = 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';
|
|
}
|
|
|
|
/**
|
|
* Check if character should be escaped within a URI field
|
|
*
|
|
* @v c Character
|
|
* @v field URI field index
|
|
* @ret escaped Character should be escaped
|
|
*/
|
|
static int uri_character_escaped ( char c, unsigned int field ) {
|
|
|
|
/* Non-printing characters and whitespace should always be
|
|
* escaped, since they cannot sensibly be displayed as part of
|
|
* a coherent URL string. (This test also catches control
|
|
* characters such as CR and LF, which could affect the
|
|
* operation of line-based protocols such as HTTP.)
|
|
*
|
|
* We should also escape characters which would alter the
|
|
* interpretation of the URL if not escaped, i.e. characters
|
|
* which have significance to the URL parser. We should not
|
|
* blindly escape all such characters, because this would lead
|
|
* to some very strange-looking URLs (e.g. if we were to
|
|
* always escape '/' as "%2F" even within the URI path).
|
|
*
|
|
* We do not need to be perfect. Our primary role is as a
|
|
* consumer of URIs rather than a producer; the main situation
|
|
* in which we produce a URI string is for display to a human
|
|
* user, who can probably tolerate some variance from the
|
|
* formal specification. The only situation in which we
|
|
* currently produce a URI string to be consumed by a computer
|
|
* is when constructing an HTTP request URI, which contains
|
|
* only the path and query fields.
|
|
*
|
|
* We can therefore sacrifice some correctness for the sake of
|
|
* code size. For example, colons within the URI host should
|
|
* be escaped unless they form part of an IPv6 literal
|
|
* address; doing this correctly would require the URI
|
|
* formatter to be aware of whether or not the URI host
|
|
* contained an IPv4 address, an IPv6 address, or a host name.
|
|
* We choose to simplify and never escape colons within the
|
|
* URI host field: in the event of a pathological hostname
|
|
* containing colons, this could potentially produce a URI
|
|
* string which could not be reparsed.
|
|
*
|
|
* After excluding non-printing characters, whitespace, and
|
|
* '%', 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_EPATH] = {
|
|
/* 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] = "#",
|
|
/* User name: escape everything */
|
|
[URI_USER] = "/#:@?",
|
|
/* Password: escape everything */
|
|
[URI_PASSWORD] = "/#:@?",
|
|
/* Host name: escape everything except ':', which may
|
|
* appear as part of an IPv6 literal address.
|
|
*/
|
|
[URI_HOST] = "/#@?",
|
|
/* Port number: escape everything */
|
|
[URI_PORT] = "/#:@?",
|
|
/* Path: escape everything except '/', which usually
|
|
* appears within paths.
|
|
*/
|
|
[URI_PATH] = "#:@?",
|
|
};
|
|
|
|
/* Always escape non-printing characters and whitespace */
|
|
if ( ( ! isprint ( c ) ) || ( c == ' ' ) )
|
|
return 1;
|
|
|
|
/* Escape nothing else in already-escaped fields */
|
|
if ( field >= URI_EPATH )
|
|
return 0;
|
|
|
|
/* Escape '%' and any field-specific characters */
|
|
if ( ( c == '%' ) || strchr ( escaped[field], c ) )
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Encode URI field
|
|
*
|
|
* @v field URI field index
|
|
* @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 ( 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;
|
|
|
|
/* Ensure encoded string is NUL-terminated even if empty */
|
|
if ( len > 0 )
|
|
buf[0] = '\0';
|
|
|
|
/* Copy string, escaping as necessary */
|
|
while ( raw_len-- ) {
|
|
c = *(raw_bytes++);
|
|
if ( uri_character_escaped ( c, field ) ) {
|
|
used = ssnprintf ( buf, remaining, "%%%02X", c );
|
|
} else {
|
|
used = ssnprintf ( buf, remaining, "%c", c );
|
|
}
|
|
buf += used;
|
|
remaining -= used;
|
|
}
|
|
|
|
return ( len - remaining );
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
static void uri_dump ( const struct uri *uri ) {
|
|
|
|
if ( ! uri )
|
|
return;
|
|
if ( uri->scheme )
|
|
DBGC ( uri, " scheme \"%s\"", uri->scheme );
|
|
if ( uri->opaque )
|
|
DBGC ( uri, " opaque \"%s\"", uri->opaque );
|
|
if ( uri->user )
|
|
DBGC ( uri, " user \"%s\"", uri->user );
|
|
if ( uri->password )
|
|
DBGC ( uri, " password \"%s\"", uri->password );
|
|
if ( uri->host )
|
|
DBGC ( uri, " host \"%s\"", uri->host );
|
|
if ( uri->port )
|
|
DBGC ( uri, " port \"%s\"", uri->port );
|
|
if ( uri->path )
|
|
DBGC ( uri, " path \"%s\"", uri->path );
|
|
if ( uri->epath )
|
|
DBGC ( uri, " epath \"%s\"", uri->epath );
|
|
if ( uri->equery )
|
|
DBGC ( uri, " equery \"%s\"", uri->equery );
|
|
if ( uri->efragment )
|
|
DBGC ( uri, " efragment \"%s\"", uri->efragment );
|
|
if ( uri->params )
|
|
DBGC ( uri, " params \"%s\"", uri->params->name );
|
|
}
|
|
|
|
/**
|
|
* Free URI
|
|
*
|
|
* @v refcnt Reference count
|
|
*/
|
|
static void uri_free ( struct refcnt *refcnt ) {
|
|
struct uri *uri = container_of ( refcnt, struct uri, refcnt );
|
|
|
|
params_put ( uri->params );
|
|
free ( uri );
|
|
}
|
|
|
|
/**
|
|
* Parse URI
|
|
*
|
|
* @v uri_string URI as a string
|
|
* @ret uri URI
|
|
*
|
|
* Splits a URI into its component parts. The return URI structure is
|
|
* dynamically allocated and must eventually be freed by calling
|
|
* uri_put().
|
|
*/
|
|
struct uri * parse_uri ( const char *uri_string ) {
|
|
struct uri *uri;
|
|
struct parameters *params;
|
|
char *raw;
|
|
char *tmp;
|
|
char *path;
|
|
char *epath;
|
|
char *authority;
|
|
size_t raw_len;
|
|
unsigned int field;
|
|
|
|
/* Allocate space for URI struct and two copies of the string */
|
|
raw_len = ( strlen ( uri_string ) + 1 /* NUL */ );
|
|
uri = zalloc ( sizeof ( *uri ) + ( 2 * raw_len ) );
|
|
if ( ! uri )
|
|
return NULL;
|
|
ref_init ( &uri->refcnt, uri_free );
|
|
raw = ( ( ( void * ) uri ) + sizeof ( *uri ) );
|
|
path = ( raw + raw_len );
|
|
|
|
/* Copy in the raw string */
|
|
memcpy ( raw, uri_string, raw_len );
|
|
|
|
/* Identify the parameter list, if present */
|
|
if ( ( tmp = strstr ( raw, "##params" ) ) ) {
|
|
*tmp = '\0';
|
|
tmp += 8 /* "##params" */;
|
|
params = find_parameters ( *tmp ? ( tmp + 1 ) : NULL );
|
|
if ( params ) {
|
|
uri->params = claim_parameters ( params );
|
|
} else {
|
|
/* Ignore non-existent submission blocks */
|
|
}
|
|
}
|
|
|
|
/* Chop off the fragment, if it exists */
|
|
if ( ( tmp = strchr ( raw, '#' ) ) ) {
|
|
*(tmp++) = '\0';
|
|
uri->efragment = tmp;
|
|
}
|
|
|
|
/* Identify absolute URIs */
|
|
epath = raw;
|
|
for ( tmp = raw ; ; tmp++ ) {
|
|
/* Possible scheme character (for our URI schemes) */
|
|
if ( isalpha ( *tmp ) || ( *tmp == '-' ) || ( *tmp == '_' ) )
|
|
continue;
|
|
/* Invalid scheme character or NUL: is a relative URI */
|
|
if ( *tmp != ':' )
|
|
break;
|
|
/* Absolute URI: identify hierarchical/opaque */
|
|
uri->scheme = raw;
|
|
*(tmp++) = '\0';
|
|
if ( *tmp == '/' ) {
|
|
/* Absolute URI with hierarchical part */
|
|
epath = tmp;
|
|
} else {
|
|
/* Absolute URI with opaque part */
|
|
uri->opaque = tmp;
|
|
epath = NULL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* If we don't have a path (i.e. we have an absolute URI with
|
|
* an opaque portion, we're already finished processing
|
|
*/
|
|
if ( ! epath )
|
|
goto done;
|
|
|
|
/* Chop off the query, if it exists */
|
|
if ( ( tmp = strchr ( epath, '?' ) ) ) {
|
|
*(tmp++) = '\0';
|
|
uri->equery = tmp;
|
|
}
|
|
|
|
/* If we have no path remaining, then we're already finished
|
|
* processing.
|
|
*/
|
|
if ( ! epath[0] )
|
|
goto done;
|
|
|
|
/* Identify net/absolute/relative path */
|
|
if ( uri->scheme && ( strncmp ( epath, "//", 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
|
|
* the authority down by one byte, overwriting one of
|
|
* the two slashes.
|
|
*/
|
|
authority = ( epath + 2 );
|
|
if ( ( tmp = strchr ( authority, '/' ) ) ) {
|
|
/* Shuffle down */
|
|
uri->epath = tmp;
|
|
memmove ( ( authority - 1 ), authority,
|
|
( tmp - authority ) );
|
|
authority--;
|
|
*(--tmp) = '\0';
|
|
}
|
|
} else {
|
|
/* Absolute/relative path */
|
|
uri->epath = epath;
|
|
authority = NULL;
|
|
}
|
|
|
|
/* Create copy of path for decoding */
|
|
if ( uri->epath ) {
|
|
strcpy ( path, uri->epath );
|
|
uri->path = path;
|
|
}
|
|
|
|
/* If we don't have an authority (i.e. we have a non-net
|
|
* path), we're already finished processing
|
|
*/
|
|
if ( ! authority )
|
|
goto done;
|
|
|
|
/* Split authority into user[:password] and host[:port] portions */
|
|
if ( ( tmp = strchr ( authority, '@' ) ) ) {
|
|
/* Has user[:password] */
|
|
*(tmp++) = '\0';
|
|
uri->host = tmp;
|
|
uri->user = authority;
|
|
if ( ( tmp = strchr ( authority, ':' ) ) ) {
|
|
/* Has password */
|
|
*(tmp++) = '\0';
|
|
uri->password = tmp;
|
|
}
|
|
} else {
|
|
/* No user:password */
|
|
uri->host = authority;
|
|
}
|
|
|
|
/* Split host into host[:port] */
|
|
if ( ( tmp = strrchr ( uri->host, ':' ) ) &&
|
|
( uri->host[ strlen ( uri->host ) - 1 ] != ']' ) ) {
|
|
*(tmp++) = '\0';
|
|
uri->port = tmp;
|
|
}
|
|
|
|
done:
|
|
/* Decode fields in-place */
|
|
for ( field = 0 ; field < URI_EPATH ; field++ )
|
|
uri_decode_inplace ( ( char * ) uri_field ( uri, field ) );
|
|
|
|
DBGC ( uri, "URI parsed \"%s\" to", uri_string );
|
|
uri_dump ( uri );
|
|
DBGC ( uri, "\n" );
|
|
|
|
return uri;
|
|
}
|
|
|
|
/**
|
|
* Get port from URI
|
|
*
|
|
* @v uri URI, or NULL
|
|
* @v default_port Default port to use if none specified in URI
|
|
* @ret port Port
|
|
*/
|
|
unsigned int uri_port ( const struct uri *uri, unsigned int default_port ) {
|
|
|
|
if ( ( ! uri ) || ( ! uri->port ) )
|
|
return default_port;
|
|
|
|
return ( strtoul ( uri->port, NULL, 0 ) );
|
|
}
|
|
|
|
/**
|
|
* Format URI
|
|
*
|
|
* @v uri URI
|
|
* @v buf Buffer to fill with URI string
|
|
* @v size Size of buffer
|
|
* @ret len Length of URI string
|
|
*/
|
|
size_t format_uri ( const struct uri *uri, char *buf, size_t len ) {
|
|
static const char prefixes[URI_FIELDS] = {
|
|
[URI_PASSWORD] = ':',
|
|
[URI_PORT] = ':',
|
|
[URI_EQUERY] = '?',
|
|
[URI_EFRAGMENT] = '#',
|
|
};
|
|
char prefix;
|
|
size_t used = 0;
|
|
unsigned int field;
|
|
|
|
/* Ensure buffer is NUL-terminated */
|
|
if ( len )
|
|
buf[0] = '\0';
|
|
|
|
/* Special-case NULL URI */
|
|
if ( ! uri )
|
|
return 0;
|
|
|
|
/* Generate fields */
|
|
for ( field = 0 ; field < URI_FIELDS ; field++ ) {
|
|
|
|
/* Skip non-existent fields */
|
|
if ( ! uri_field ( uri, field ) )
|
|
continue;
|
|
|
|
/* Skip path field if encoded path is present */
|
|
if ( ( field == URI_PATH ) && uri->epath )
|
|
continue;
|
|
|
|
/* Prefix this field, if applicable */
|
|
prefix = prefixes[field];
|
|
if ( ( field == URI_HOST ) && ( uri->user != NULL ) )
|
|
prefix = '@';
|
|
if ( prefix ) {
|
|
used += ssnprintf ( ( buf + used ), ( len - used ),
|
|
"%c", prefix );
|
|
}
|
|
|
|
/* Encode this field */
|
|
used += uri_encode_string ( field, uri_field ( uri, field ),
|
|
( buf + used ), ( len - used ) );
|
|
|
|
/* Suffix this field, if applicable */
|
|
if ( field == URI_SCHEME ) {
|
|
used += ssnprintf ( ( buf + used ), ( len - used ),
|
|
":%s", ( uri->host ? "//" : "" ) );
|
|
}
|
|
}
|
|
|
|
if ( len ) {
|
|
DBGC ( uri, "URI formatted" );
|
|
uri_dump ( uri );
|
|
DBGC ( uri, " to \"%s%s\"\n", buf,
|
|
( ( used > len ) ? "<TRUNCATED>" : "" ) );
|
|
}
|
|
|
|
return used;
|
|
}
|
|
|
|
/**
|
|
* Format URI
|
|
*
|
|
* @v uri URI
|
|
* @ret string URI string, or NULL on failure
|
|
*
|
|
* The caller is responsible for eventually freeing the allocated
|
|
* memory.
|
|
*/
|
|
char * format_uri_alloc ( const struct uri *uri ) {
|
|
size_t len;
|
|
char *string;
|
|
|
|
len = ( format_uri ( uri, NULL, 0 ) + 1 /* NUL */ );
|
|
string = malloc ( len );
|
|
if ( string )
|
|
format_uri ( uri, string, len );
|
|
return string;
|
|
}
|
|
|
|
/**
|
|
* Copy URI fields
|
|
*
|
|
* @v src Source URI
|
|
* @v dest Destination URI, or NULL to calculate length
|
|
* @ret len Length of raw URI
|
|
*/
|
|
static size_t uri_copy_fields ( const struct uri *src, struct uri *dest ) {
|
|
size_t len = sizeof ( *dest );
|
|
char *out = ( ( void * ) dest + len );
|
|
unsigned int field;
|
|
size_t field_len;
|
|
|
|
/* Copy existent fields */
|
|
for ( field = 0 ; field < URI_FIELDS ; field++ ) {
|
|
|
|
/* Skip non-existent fields */
|
|
if ( ! uri_field ( src, field ) )
|
|
continue;
|
|
|
|
/* Calculate field length */
|
|
field_len = ( strlen ( uri_field ( src, field ) )
|
|
+ 1 /* NUL */ );
|
|
len += field_len;
|
|
|
|
/* Copy field, if applicable */
|
|
if ( dest ) {
|
|
memcpy ( out, uri_field ( src, field ), field_len );
|
|
uri_field ( dest, field ) = out;
|
|
out += field_len;
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* Duplicate URI
|
|
*
|
|
* @v uri URI
|
|
* @ret uri Duplicate URI
|
|
*
|
|
* Creates a modifiable copy of a URI.
|
|
*/
|
|
struct uri * uri_dup ( const struct uri *uri ) {
|
|
struct uri *dup;
|
|
size_t len;
|
|
|
|
/* Allocate new URI */
|
|
len = uri_copy_fields ( uri, NULL );
|
|
dup = zalloc ( len );
|
|
if ( ! dup )
|
|
return NULL;
|
|
ref_init ( &dup->refcnt, uri_free );
|
|
|
|
/* Copy fields */
|
|
uri_copy_fields ( uri, dup );
|
|
|
|
/* Copy parameters */
|
|
dup->params = params_get ( uri->params );
|
|
|
|
DBGC ( uri, "URI duplicated" );
|
|
uri_dump ( uri );
|
|
DBGC ( uri, "\n" );
|
|
|
|
return dup;
|
|
}
|
|
|
|
/**
|
|
* Resolve base+relative path
|
|
*
|
|
* @v base_uri Base path
|
|
* @v relative_uri Relative 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
|
|
* (e.g. "/var/lib/tftpboot/initrd.gz"). Note that any non-directory
|
|
* portion of the base path will automatically be stripped; this
|
|
* matches the semantics used when resolving the path component of
|
|
* URIs.
|
|
*/
|
|
char * resolve_path ( const char *base_path,
|
|
const char *relative_path ) {
|
|
char *base_copy;
|
|
char *base_tmp;
|
|
char *resolved;
|
|
|
|
/* If relative path is absolute, just re-use it */
|
|
if ( relative_path[0] == '/' )
|
|
return strdup ( relative_path );
|
|
|
|
/* Create modifiable copy of path for dirname() */
|
|
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 == '.' ) {
|
|
relative_path++;
|
|
if ( *relative_path == 0 ) {
|
|
/* Do nothing */
|
|
} else if ( *relative_path == '/' ) {
|
|
relative_path++;
|
|
} else if ( *relative_path == '.' ) {
|
|
relative_path++;
|
|
if ( *relative_path == 0 ) {
|
|
base_tmp = dirname ( base_tmp );
|
|
} else if ( *relative_path == '/' ) {
|
|
base_tmp = dirname ( base_tmp );
|
|
relative_path++;
|
|
} else {
|
|
relative_path -= 2;
|
|
break;
|
|
}
|
|
} else {
|
|
relative_path--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Create and return new path */
|
|
if ( asprintf ( &resolved, "%s%s%s", base_tmp,
|
|
( ( base_tmp[ strlen ( base_tmp ) - 1 ] == '/' ) ?
|
|
"" : "/" ), relative_path ) < 0 )
|
|
resolved = NULL;
|
|
free ( base_copy );
|
|
return resolved;
|
|
}
|
|
|
|
/**
|
|
* Resolve base+relative URI
|
|
*
|
|
* @v base_uri Base URI, or NULL
|
|
* @v relative_uri Relative 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
|
|
* (e.g. "http://ipxe.org/initrds/initrd.gz").
|
|
*/
|
|
struct uri * resolve_uri ( const struct uri *base_uri,
|
|
struct uri *relative_uri ) {
|
|
struct uri tmp_uri;
|
|
char *tmp_epath = NULL;
|
|
char *tmp_path = NULL;
|
|
struct uri *new_uri;
|
|
|
|
/* If relative URI is absolute, just re-use it */
|
|
if ( uri_is_absolute ( relative_uri ) || ( ! base_uri ) )
|
|
return uri_get ( relative_uri );
|
|
|
|
/* Mangle URI */
|
|
memcpy ( &tmp_uri, base_uri, sizeof ( tmp_uri ) );
|
|
if ( relative_uri->epath ) {
|
|
tmp_epath = resolve_path ( ( base_uri->epath ?
|
|
base_uri->epath : "/" ),
|
|
relative_uri->epath );
|
|
if ( ! tmp_epath )
|
|
goto err_epath;
|
|
tmp_path = strdup ( tmp_epath );
|
|
if ( ! tmp_path )
|
|
goto err_path;
|
|
uri_decode_inplace ( tmp_path );
|
|
tmp_uri.epath = tmp_epath;
|
|
tmp_uri.path = tmp_path;
|
|
tmp_uri.equery = relative_uri->equery;
|
|
tmp_uri.efragment = relative_uri->efragment;
|
|
tmp_uri.params = relative_uri->params;
|
|
} else if ( relative_uri->equery ) {
|
|
tmp_uri.equery = relative_uri->equery;
|
|
tmp_uri.efragment = relative_uri->efragment;
|
|
tmp_uri.params = relative_uri->params;
|
|
} else if ( relative_uri->efragment ) {
|
|
tmp_uri.efragment = relative_uri->efragment;
|
|
tmp_uri.params = relative_uri->params;
|
|
} else if ( relative_uri->params ) {
|
|
tmp_uri.params = relative_uri->params;
|
|
}
|
|
|
|
/* Create demangled URI */
|
|
new_uri = uri_dup ( &tmp_uri );
|
|
free ( tmp_path );
|
|
free ( tmp_epath );
|
|
return new_uri;
|
|
|
|
free ( tmp_path );
|
|
err_path:
|
|
free ( tmp_epath );
|
|
err_epath:
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
tmp.epath = 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 sa_server Server address
|
|
* @v filename Filename
|
|
* @ret uri URI, or NULL on failure
|
|
*
|
|
* 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 * 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 );
|
|
}
|