Skip to content

Commit 8093ed6

Browse files
committed
Implemented FR #51634: Can't post multiple fields with the same name
This PR allows for multiple values with the same field name to be sent to an HTTP server. This server can't be PHP, because PHP's POST processing does not allow for two the same field names. But sending multiple fields with the same name is required for some operations by RFC 7578. This PR allows you to attach an array of values to a field name, and each of these will then be sent as its own distinct multipart/form-data field.
1 parent 4117063 commit 8093ed6

File tree

2 files changed

+121
-27
lines changed

2 files changed

+121
-27
lines changed

ext/curl/interface.c

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,54 @@ static void free_cb(void *arg) /* {{{ */
19911991
/* }}} */
19921992
#endif
19931993

1994+
static inline CURLcode add_simple_field(curl_mime *mime, zend_string *string_key, zval *current)
1995+
{
1996+
CURLcode error = CURLE_OK;
1997+
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
1998+
curl_mimepart *part;
1999+
CURLcode form_error;
2000+
#else
2001+
struct HttpPost *first = NULL;
2002+
struct HttpPost *last = NULL;
2003+
CURLFORMcode form_error;
2004+
#endif
2005+
zend_string *postval, *tmp_postval;
2006+
2007+
postval = zval_get_tmp_string(current, &tmp_postval);
2008+
2009+
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
2010+
part = curl_mime_addpart(mime);
2011+
if (part == NULL) {
2012+
zend_tmp_string_release(tmp_postval);
2013+
zend_string_release_ex(string_key, 0);
2014+
return CURLE_OUT_OF_MEMORY;
2015+
}
2016+
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
2017+
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK) {
2018+
error = form_error;
2019+
}
2020+
#else
2021+
/* The arguments after _NAMELENGTH and _CONTENTSLENGTH
2022+
* must be explicitly cast to long in curl_formadd
2023+
* use since curl needs a long not an int. */
2024+
form_error = curl_formadd(&first, &last,
2025+
CURLFORM_COPYNAME, ZSTR_VAL(string_key),
2026+
CURLFORM_NAMELENGTH, ZSTR_LEN(string_key),
2027+
CURLFORM_COPYCONTENTS, ZSTR_VAL(postval),
2028+
CURLFORM_CONTENTSLENGTH, ZSTR_LEN(postval),
2029+
CURLFORM_END
2030+
);
2031+
2032+
if (form_error != CURL_FORMADD_OK) {
2033+
/* Not nice to convert between enums but we only have place for one error type */
2034+
error = (CURLcode)form_error;
2035+
}
2036+
#endif
2037+
zend_tmp_string_release(tmp_postval);
2038+
2039+
return error;
2040+
}
2041+
19942042
static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpostfields) /* {{{ */
19952043
{
19962044
HashTable *postfields = Z_ARRVAL_P(zpostfields);
@@ -2018,7 +2066,7 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
20182066
#endif
20192067

20202068
ZEND_HASH_FOREACH_KEY_VAL(postfields, num_key, string_key, current) {
2021-
zend_string *postval, *tmp_postval;
2069+
zend_string *postval;
20222070
/* Pretend we have a string_key here */
20232071
if (!string_key) {
20242072
string_key = zend_long_to_str(num_key);
@@ -2181,36 +2229,19 @@ static inline zend_result build_mime_structure_from_hash(php_curl *ch, zval *zpo
21812229
continue;
21822230
}
21832231

2184-
postval = zval_get_tmp_string(current, &tmp_postval);
2232+
if (Z_TYPE_P(current) == IS_ARRAY) {
2233+
zval *current_element;
2234+
2235+
ZEND_HASH_FOREACH_VAL(HASH_OF(current), current_element) {
2236+
add_simple_field(mime, string_key, current_element);
2237+
} ZEND_HASH_FOREACH_END();
21852238

2186-
#if LIBCURL_VERSION_NUM >= 0x073800 /* 7.56.0 */
2187-
part = curl_mime_addpart(mime);
2188-
if (part == NULL) {
2189-
zend_tmp_string_release(tmp_postval);
21902239
zend_string_release_ex(string_key, 0);
2191-
return FAILURE;
2192-
}
2193-
if ((form_error = curl_mime_name(part, ZSTR_VAL(string_key))) != CURLE_OK
2194-
|| (form_error = curl_mime_data(part, ZSTR_VAL(postval), ZSTR_LEN(postval))) != CURLE_OK) {
2195-
error = form_error;
2240+
continue;
21962241
}
2197-
#else
2198-
/* The arguments after _NAMELENGTH and _CONTENTSLENGTH
2199-
* must be explicitly cast to long in curl_formadd
2200-
* use since curl needs a long not an int. */
2201-
form_error = curl_formadd(&first, &last,
2202-
CURLFORM_COPYNAME, ZSTR_VAL(string_key),
2203-
CURLFORM_NAMELENGTH, ZSTR_LEN(string_key),
2204-
CURLFORM_COPYCONTENTS, ZSTR_VAL(postval),
2205-
CURLFORM_CONTENTSLENGTH, ZSTR_LEN(postval),
2206-
CURLFORM_END);
22072242

2208-
if (form_error != CURL_FORMADD_OK) {
2209-
/* Not nice to convert between enums but we only have place for one error type */
2210-
error = (CURLcode)form_error;
2211-
}
2212-
#endif
2213-
zend_tmp_string_release(tmp_postval);
2243+
add_simple_field(mime, string_key, current);
2244+
22142245
zend_string_release_ex(string_key, 0);
22152246
} ZEND_HASH_FOREACH_END();
22162247

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
--TEST--
2+
CURLOPT_POSTFIELDS with multi-value fields
3+
--EXTENSIONS--
4+
sockets
5+
--FILE--
6+
<?php
7+
$socket = stream_socket_server("tcp://0.0.0.0:29999", $errno, $errstr);
8+
9+
if (!$socket) {
10+
echo "$errstr ($errno)<br />\n";
11+
return;
12+
}
13+
14+
$url = "http://127.0.0.1:29999/get.inc?test=raw";
15+
16+
$fields = [
17+
'single' => 'SingleValue',
18+
'multi' => [
19+
'Multi1',
20+
'Multi2',
21+
]
22+
];
23+
24+
$options = [
25+
CURLOPT_POST => 1,
26+
CURLOPT_HEADER => 0,
27+
CURLOPT_URL => $url,
28+
CURLOPT_FRESH_CONNECT => 1,
29+
CURLOPT_RETURNTRANSFER => 1,
30+
CURLOPT_FORBID_REUSE => 1,
31+
CURLOPT_TIMEOUT => 1,
32+
CURLOPT_POSTFIELDS => $fields,
33+
];
34+
35+
$ch = curl_init();
36+
curl_setopt_array($ch, $options);
37+
38+
$curl_content = curl_exec($ch);
39+
curl_close($ch);
40+
41+
$conn = stream_socket_accept($socket);
42+
echo stream_get_contents($conn);
43+
?>
44+
--EXPECTF--
45+
POST /get.inc?test=raw HTTP/1.1
46+
Host: %s
47+
Accept: */*
48+
Content-Length: %d
49+
Content-Type: multipart/form-data; boundary=------------------------%s
50+
51+
--------------------------%s
52+
Content-Disposition: form-data; name="single"
53+
54+
SingleValue
55+
--------------------------%s
56+
Content-Disposition: form-data; name="multi"
57+
58+
Multi1
59+
--------------------------%s
60+
Content-Disposition: form-data; name="multi"
61+
62+
Multi2
63+
--------------------------%s--

0 commit comments

Comments
 (0)