diff --git a/src/core/settings.c b/src/core/settings.c index 12e6c7d6..f6f62d22 100644 --- a/src/core/settings.c +++ b/src/core/settings.c @@ -1666,15 +1666,43 @@ const struct setting_type setting_type_string __setting_type = { .format = format_string_setting, }; -/** A URI-encoded string setting type +/** + * Parse URI-encoded string setting value * - * This setting type is obsolete; the name ":uristring" is retained to - * avoid breaking existing scripts. + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error */ +static int parse_uristring_setting ( const struct setting_type *type __unused, + const char *value, void *buf, size_t len ){ + + return uri_decode ( value, buf, len ); +} + +/** + * Format URI-encoded string setting value + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_uristring_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, + char *buf, size_t len ) { + + return uri_encode ( 0, raw, raw_len, buf, len ); +} + +/** A URI-encoded string setting type */ const struct setting_type setting_type_uristring __setting_type = { .name = "uristring", - .parse = parse_string_setting, - .format = format_string_setting, + .parse = parse_uristring_setting, + .format = format_uristring_setting, }; /** diff --git a/src/core/uri.c b/src/core/uri.c index 3b5f270f..30f8f6ca 100644 --- a/src/core/uri.c +++ b/src/core/uri.c @@ -39,15 +39,19 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include /** - * 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 +59,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,10 +143,15 @@ 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). @@ -157,14 +190,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 +209,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 { @@ -187,6 +223,21 @@ size_t uri_encode ( const char *string, unsigned int field, 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 * @@ -368,10 +419,8 @@ struct uri * parse_uri ( const char *uri_string ) { } /* 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 ); @@ -444,8 +493,8 @@ size_t format_uri ( const struct uri *uri, char *buf, size_t len ) { } /* 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 ) ) { diff --git a/src/include/ipxe/uri.h b/src/include/ipxe/uri.h index 00e5a24c..ce6a684c 100644 --- a/src/include/ipxe/uri.h +++ b/src/include/ipxe/uri.h @@ -191,8 +191,11 @@ uri_put ( struct uri *uri ) { extern struct uri *cwuri; -extern size_t uri_encode ( const char *string, unsigned int field, +extern size_t uri_decode ( const char *encoded, void *buf, size_t len ); +extern size_t uri_encode ( unsigned int field, const void *raw, size_t raw_len, char *buf, ssize_t len ); +extern size_t uri_encode_string ( unsigned int field, const char *string, + char *buf, ssize_t len ); extern struct uri * parse_uri ( const char *uri_string ); extern size_t format_uri ( const struct uri *uri, char *buf, size_t len ); extern char * format_uri_alloc ( const struct uri *uri ); diff --git a/src/net/tcp/httpcore.c b/src/net/tcp/httpcore.c index af3ca978..f3685de0 100644 --- a/src/net/tcp/httpcore.c +++ b/src/net/tcp/httpcore.c @@ -1826,7 +1826,7 @@ static size_t http_params ( struct parameters *params, char *buf, size_t len ) { } /* URI-encode the key */ - frag_len = uri_encode ( param->key, 0, buf, remaining ); + frag_len = uri_encode_string ( 0, param->key, buf, remaining ); buf += frag_len; len += frag_len; remaining -= frag_len; @@ -1839,7 +1839,7 @@ static size_t http_params ( struct parameters *params, char *buf, size_t len ) { remaining--; /* URI-encode the value */ - frag_len = uri_encode ( param->value, 0, buf, remaining ); + frag_len = uri_encode_string ( 0, param->value, buf, remaining); buf += frag_len; len += frag_len; remaining -= frag_len; diff --git a/src/tests/settings_test.c b/src/tests/settings_test.c index f7fb35d0..89203d42 100644 --- a/src/tests/settings_test.c +++ b/src/tests/settings_test.c @@ -166,6 +166,12 @@ static struct setting test_string_setting = { .type = &setting_type_string, }; +/** Test URI-encoded string setting */ +static struct setting test_uristring_setting = { + .name = "test_uristring", + .type = &setting_type_uristring, +}; + /** Test IPv4 address setting type */ static struct setting test_ipv4_setting = { .name = "test_ipv4", @@ -265,6 +271,16 @@ static void settings_test_exec ( void ) { fetchf_ok ( &test_settings, &test_string_setting, RAW ( 'w', 'o', 'r', 'l', 'd' ), "world" ); + /* "uristring" setting type */ + storef_ok ( &test_settings, &test_uristring_setting, "hello%20world", + RAW ( 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', + 'd' ) ); + fetchf_ok ( &test_settings, &test_uristring_setting, + RAW ( 1, 2, 3, 4, 5 ), "%01%02%03%04%05" ); + fetchf_ok ( &test_settings, &test_uristring_setting, + RAW ( 0, ' ', '%', '/', '#', ':', '@', '?', '=', '&' ), + "%00%20%25%2F%23%3A%40%3F%3D%26" ); + /* "ipv4" setting type */ storef_ok ( &test_settings, &test_ipv4_setting, "192.168.0.1", RAW ( 192, 168, 0, 1 ) );