http_aws_sigv4: fix query normalization of %2b

Reported-by: Nuno Goncalves
Fixes #20543
Closes #20550
This commit is contained in:
Daniel Stenberg 2026-02-09 14:59:39 +01:00
parent 5c250e2421
commit ee3a4dff1a
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
3 changed files with 45 additions and 34 deletions

View file

@ -222,44 +222,44 @@ static CURLcode uri_encode_path(struct Curl_str *original_path,
return CURLE_OK;
}
static CURLcode encode_query_component(char *component, size_t len,
struct dynbuf *db)
/* Normalize the query part. Make sure %2B is left percent encoded, and not
decoded to plus, then encoded to space.
*/
static CURLcode normalize_query(const char *string, size_t len,
struct dynbuf *db)
{
size_t i;
for(i = 0; i < len; i++) {
CURLcode result = CURLE_OK;
unsigned char this_char = component[i];
CURLcode result = CURLE_OK;
if(is_reserved_char(this_char))
while(len && !result) {
unsigned char in = (unsigned char)*string;
if(('%' == in) && (len > 2) &&
ISXDIGIT(string[1]) && ISXDIGIT(string[2])) {
/* this is two hexadecimal digits following a '%' */
in = (unsigned char)((curlx_hexval(string[1]) << 4) |
curlx_hexval(string[2]));
string += 3;
len -= 3;
if(in == '+') {
/* decodes to plus, so leave this encoded */
result = curlx_dyn_addn(db, "%2B", 3);
continue;
}
}
else {
string++;
len--;
}
if(is_reserved_char(in))
/* Escape unreserved chars from RFC 3986 */
result = curlx_dyn_addn(db, &this_char, 1);
else if(this_char == '+')
result = curlx_dyn_addn(db, &in, 1);
else if(in == '+')
/* Encode '+' as space */
result = curlx_dyn_add(db, "%20");
else
result = curlx_dyn_addf(db, "%%%02X", this_char);
if(result)
return result;
result = curlx_dyn_addf(db, "%%%02X", in);
}
return CURLE_OK;
}
/*
* Populates a dynbuf containing url_encode(url_decode(in))
*/
static CURLcode http_aws_decode_encode(const char *in, size_t in_len,
struct dynbuf *out)
{
char *out_s;
size_t out_s_len;
CURLcode result =
Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA);
if(!result) {
result = encode_query_component(out_s, out_s_len, out);
Curl_safefree(out_s);
}
return result;
}
@ -750,8 +750,8 @@ UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq)
counted_query_components++;
/* Decode/encode the key */
result = http_aws_decode_encode(in_key, in_key_len,
&encoded_query_array[index].key);
result = normalize_query(in_key, in_key_len,
&encoded_query_array[index].key);
if(result) {
goto fail;
}
@ -761,8 +761,8 @@ UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq)
size_t in_value_len;
const char *in_value = offset + 1;
in_value_len = query_part + query_part_len - (offset + 1);
result = http_aws_decode_encode(in_value, in_value_len,
&encoded_query_array[index].value);
result = normalize_query(in_value, in_value_len,
&encoded_query_array[index].value);
if(result) {
goto fail;
}

View file

@ -83,6 +83,12 @@ static CURLcode test_unit1979(const char *arg)
"/example space/",
"/example%20space/"
},
{
"get-plus-normalized",
true,
"/example+space/",
"/example%2Bspace/"
},
{
"get-slash-dot-slash-unnormalized",
false,

View file

@ -79,6 +79,11 @@ static CURLcode test_unit1980(const char *arg)
"p3= &p1=+&p2=%20",
"p1=%20&p2=%20&p3=%20"
},
{
"2b-incoming",
"p3=%2b&p1=+",
"p1=%20&p3=%2B"
},
};
size_t i;