Index: library/Zend/Http/Client.php =================================================================== --- library/Zend/Http/Client.php (revision 24046) +++ library/Zend/Http/Client.php (working copy) @@ -101,6 +101,12 @@ */ const ENC_URLENCODED = 'application/x-www-form-urlencoded'; const ENC_FORMDATA = 'multipart/form-data'; + + /** + * Value types for Body key/value pairs + */ + const VTYPE_SCALAR = 'SCALAR'; + const VTYPE_FILE = 'FILE'; /** * Configuration array, set using the constructor or using ::setConfig() @@ -201,6 +207,16 @@ * @var array */ protected $files = array(); + + /** + * Ordered list of keys from key/value pair data to include in body + * + * An associative array, where each element is of the format: + * '' => VTYPE_SCALAR | VTYPE_FILE + * + * @var array + */ + protected $body_field_order = array(); /** * The client's cookie jar @@ -501,6 +517,12 @@ break; case 'post': $parray = &$this->paramsPost; + if ( $value === null ) { + if (isset($this->body_field_order[$name])) + unset($this->body_field_order[$name]); + } else { + $this->body_field_order[$name] = self::VTYPE_SCALAR; + } break; } @@ -718,6 +740,8 @@ 'ctype' => $ctype, 'data' => $data ); + + $this->body_field_order[$formname] = self::VTYPE_FILE; return $this; } @@ -1202,16 +1226,37 @@ $boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}"); - // Get POST parameters and encode them - $params = self::_flattenParametersArray($this->paramsPost); - foreach ($params as $pp) { - $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); + // Map the formname of each file to the array index it is stored in + $fileIndexMap = array(); + foreach ($this->files as $key=>$fdata ) { + $fileIndexMap[$fdata['formname']] = $key; } - - // Encode files - foreach ($this->files as $file) { - $fhead = array(self::CONTENT_TYPE => $file['ctype']); - $body .= self::encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead); + + // Encode all files and POST vars in the order they were given + foreach ($this->body_field_order as $fieldName=>$fieldType) { + switch ($fieldType) { + case self::VTYPE_FILE: + if (isset($fileIndexMap[$fieldName])) { + if (isset($this->files[$fileIndexMap[$fieldName]])) { + $file = $this->files[$fileIndexMap[$fieldName]]; + $fhead = array(self::CONTENT_TYPE => $file['ctype']); + $body .= self::encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead); + } + } + break; + case self::VTYPE_SCALAR: + if (isset($this->paramsPost[$fieldName])) { + if (is_array($this->paramsPost[$fieldName])) { + $flattened = self::_flattenParametersArray($this->paramsPost[$fieldName], $fieldName); + foreach ($flattened as $pp) { + $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); + } + } else { + $body .= self::encodeFormData($boundary, $fieldName, $this->paramsPost[$fieldName]); + } + } + break; + } } $body .= "--{$boundary}--\r\n"; Index: tests/Zend/Http/Client/StaticTest.php =================================================================== --- tests/Zend/Http/Client/StaticTest.php (revision 24046) +++ tests/Zend/Http/Client/StaticTest.php (working copy) @@ -549,6 +549,54 @@ } /** + * @group ZF-4236 + */ + public function testFormFileUpload() + { + $this->_client->setAdapter('Zend_Http_Client_Adapter_Test'); + $this->_client->setUri('http://example.com'); + $this->_client->setFileUpload('testFile.name', 'testFile', 'TESTDATA12345', 'text/plain'); + $this->_client->request('POST'); + + $expectedLines = file(dirname(__FILE__) . '/_files/ZF4236-fileuploadrequest.txt'); + $gotLines = explode("\n", trim($this->_client->getLastRequest())); + + $this->assertEquals(count($expectedLines), count($gotLines)); + while (($expected = array_shift($expectedLines)) && + ($got = array_shift($gotLines))) { + + $expected = trim($expected); + $got = trim($got); + $this->assertRegExp("/^$expected$/", $got); + } + } + + /** + * @group ZF-4236 + */ + public function testClientBodyRetainsFieldOrdering() + { + $this->_client->setAdapter('Zend_Http_Client_Adapter_Test'); + $this->_client->setUri('http://example.com'); + $this->_client->setParameterPost('testFirst', 'foo'); + $this->_client->setFileUpload('testFile.name', 'testFile', 'TESTDATA12345', 'text/plain'); + $this->_client->setParameterPost('testLast', 'bar'); + $this->_client->request('POST'); + + $expectedLines = file(dirname(__FILE__) . '/_files/ZF4236-clientbodyretainsfieldordering.txt'); + $gotLines = explode("\n", trim($this->_client->getLastRequest())); + + $this->assertEquals(count($expectedLines), count($gotLines)); + while (($expected = array_shift($expectedLines)) && + ($got = array_shift($gotLines))) { + + $expected = trim($expected); + $got = trim($got); + $this->assertRegExp("/^$expected$/", $got); + } + } + + /** * Test that we properly calculate the content-length of multibyte-encoded * request body * Index: tests/Zend/Http/Client/_files/ZF4236-clientbodyretainsfieldordering.txt =================================================================== --- tests/Zend/Http/Client/_files/ZF4236-clientbodyretainsfieldordering.txt (revision 0) +++ tests/Zend/Http/Client/_files/ZF4236-clientbodyretainsfieldordering.txt (revision 0) @@ -0,0 +1,22 @@ +POST \/ HTTP\/1.1 +Host: example.com +Connection: close +Accept-encoding: gzip, deflate +User-Agent: Zend_Http_Client +Content-Type: multipart\/form-data; boundary=---ZENDHTTPCLIENT-\w+ +Content-Length: \d+ + +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="testFirst" + +foo +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="testFile"; filename="testFile.name" +Content-Type: text\/plain + +TESTDATA12345 +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="testLast" + +bar +-----ZENDHTTPCLIENT-\w+-- \ No newline at end of file Index: tests/Zend/Http/Client/_files/ZF4236-fileuploadrequest.txt =================================================================== --- tests/Zend/Http/Client/_files/ZF4236-fileuploadrequest.txt (revision 0) +++ tests/Zend/Http/Client/_files/ZF4236-fileuploadrequest.txt (revision 0) @@ -0,0 +1,14 @@ +POST \/ HTTP\/1.1 +Host: example.com +Connection: close +Accept-encoding: gzip, deflate +User-Agent: Zend_Http_Client +Content-Type: multipart\/form-data; boundary=---ZENDHTTPCLIENT-\w+ +Content-Length: \d+ + +-----ZENDHTTPCLIENT-\w+ +Content-Disposition: form-data; name="testFile"; filename="testFile.name" +Content-Type: text\/plain + +TESTDATA12345 +-----ZENDHTTPCLIENT-\w+-- \ No newline at end of file