Procházet zdrojové kódy

ZF-4236: allow mixing order of POST and FILE fields

- Applied patch from Adam L.

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@24193 44c647ce-9c0f-0410-b52a-842ac1e357ba
matthew před 14 roky
rodič
revize
db85079b58

+ 54 - 9
library/Zend/Http/Client.php

@@ -101,6 +101,12 @@ class Zend_Http_Client
      */
     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 @@ class Zend_Http_Client
      * @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:
+     *   '<field name>' => VTYPE_SCALAR | VTYPE_FILE
+     * 
+     * @var array 
+     */
+    protected $body_field_order = array();
 
     /**
      * The client's cookie jar
@@ -501,6 +517,12 @@ class Zend_Http_Client
                 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 @@ class Zend_Http_Client
             'ctype'    => $ctype,
             'data'     => $data
         );
+        
+        $this->body_field_order[$formname] = self::VTYPE_FILE;
 
         return $this;
     }
@@ -1203,16 +1227,37 @@ class Zend_Http_Client
                     $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";

+ 48 - 0
tests/Zend/Http/Client/StaticTest.php

@@ -549,6 +549,54 @@ class Zend_Http_Client_StaticTest extends PHPUnit_Framework_TestCase
     }
 
     /**
+     * @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
      *

+ 22 - 0
tests/Zend/Http/Client/_files/ZF4236-clientbodyretainsfieldordering.txt

@@ -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+--

+ 14 - 0
tests/Zend/Http/Client/_files/ZF4236-fileuploadrequest.txt

@@ -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+--