Procházet zdrojové kódy

Fixing ZF-7038: Proper encoding of POST data in mutlipart/form-data
- Adding unit tests to test encoding of POST data
- Adding note in migration guide on function deprecation


git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@16898 44c647ce-9c0f-0410-b52a-842ac1e357ba

shahar před 16 roky
rodič
revize
308448aed1

+ 21 - 0
documentation/manual/en/module_specs/Zend_Http_Client-Migration.xml

@@ -74,5 +74,26 @@ $client->setFileUpload('file2.txt', 'userfile[]', 'some other data', 'applicatio
                 and as such should be noted. 
             </para>
         </sect3>
+        
+        <sect3 id="zend.http.client.migration.tozf19.getparamsrecursize">
+            <title>Deprecation of Zend_Http_Client::_getParametersRecursive()</title>
+
+            <para>
+                Starting from version 1.9, the protected method <classname>_getParametersRecursive()</classname>
+                is no longer used by <classname>Zend_Http_Client</classname> and is deprecated. 
+                Using it will cause an E_NOTICE message to be emitted by PHP. 
+            </para>
+            
+            <para>
+                If you subclass <classname>Zend_Http_Client</classname> and call this method, you
+                should look into using the <classname>Zend_Http_Client::_flattenParametersArray()</classname>
+                static method instead. 
+            </para>
+            
+            <para>
+                Again, since this <classname>_getParametersRecursive</classname> is a protected method,
+                this change will only affect users who subclass <classname>Zend_Http_Client</classname>. 
+            </para>
+        </sect3>
     </sect2>
 </sect1>

+ 59 - 3
library/Zend/Http/Client.php

@@ -1057,7 +1057,7 @@ class Zend_Http_Client
                     $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}");
 
                     // Get POST parameters and encode them
-                    $params = $this->_getParametersRecursive($this->paramsPost);
+                    $params = self::_flattenParametersArray($this->paramsPost);
                     foreach ($params as $pp) {
                         $body .= self::encodeFormData($boundary, $pp[0], $pp[1]);
                     }
@@ -1102,12 +1102,21 @@ class Zend_Http_Client
      * necessarily unique. If one of the parameters in as array, it will also
      * add a [] suffix to the key.
      *
-     * @param array $parray The parameters array
-     * @param bool $urlencode Whether to urlencode the name and value
+     * This method is deprecated since Zend Framework 1.9 in favour of 
+     * self::_flattenParametersArray() and will be dropped in 2.0
+     * 
+     * @deprecated since 1.9
+     * 
+     * @param  array $parray    The parameters array
+     * @param  bool  $urlencode Whether to urlencode the name and value
      * @return array
      */
     protected function _getParametersRecursive($parray, $urlencode = false)
     {
+        // Issue a deprecated notice
+        trigger_error("The " .  __METHOD__ . " method is deprecated and will be dropped in 2.0.", 
+            E_USER_NOTICE);
+            
         if (! is_array($parray)) {
             return $parray;
         }
@@ -1248,4 +1257,51 @@ class Zend_Http_Client
 
         return $authHeader;
     }
+
+    /**
+     * Convert an array of parameters into a flat array of (key, value) pairs
+     * 
+     * Will flatten a potentially multi-dimentional array of parameters (such 
+     * as POST parameters) into a flat array of (key, value) paris. In case 
+     * of multi-dimentional arrays, square brackets ([]) will be added to the
+     * key to indicate an array. 
+     * 
+     * @since  1.9
+     * 
+     * @param  array  $parray
+     * @param  string $prefix
+     * @return array
+     */
+    static protected function _flattenParametersArray($parray, $prefix = null)
+    {
+        if (! is_array($parray)) {
+            return $parray;
+        }
+        
+        $parameters = array();
+        
+        foreach($parray as $name => $value) {
+            
+            // Calculate array key
+            if ($prefix) {
+                if (is_int($name)) {
+                    $key = $prefix . '[]';
+                } else {
+                    $key = $prefix . "[$name]";
+                }
+            } else {
+                $key = $name;
+            }
+            
+            if (is_array($value)) {
+                $parameters = array_merge($parameters, self::_flattenParametersArray($value, $key));
+                
+            } else {
+                $parameters[] = array($key, $value);
+            }
+        }
+        
+        return $parameters;
+    }
+    
 }

+ 64 - 19
tests/Zend/Http/Client/SocketTest.php

@@ -70,14 +70,29 @@ class Zend_Http_Client_SocketTest extends PHPUnit_Framework_TestCase
 
             $this->baseuri = TESTS_ZEND_HTTP_CLIENT_BASEURI;
             if (substr($this->baseuri, -1) != '/') $this->baseuri .= '/';
-            $uri = $this->baseuri . $this->getName() . '.php';
+            
+            $name = $this->getName();
+            if (($pos = strpos($name, ' ')) !== false) {
+                $name = substr($name, 0, $pos);
+            }
+            
+            $uri = $this->baseuri . $name . '.php'; 
             $this->client = new Zend_Http_Client($uri, $this->config);
 
         } else {
             // Skip tests
-                        $this->markTestSkipped("Zend_Http_Client dynamic tests are not enabled in TestConfiguration.php");
+            $this->markTestSkipped("Zend_Http_Client dynamic tests are not enabled in TestConfiguration.php");
         }
     }
+    
+    /**
+     * Clean up the test environment
+     * 
+     */
+    protected function tearDown()
+    {
+        $this->client = null;
+    }
 
     /**
      * Simple request tests
@@ -180,24 +195,19 @@ class Zend_Http_Client_SocketTest extends PHPUnit_Framework_TestCase
         $qstr = 'foo=bar&foo=baz';
         $this->client->setUri($this->baseuri . 'testGetData.php?' . $qstr);
         $res = $this->client->request('GET');
-        $this->assertContains($qstr, $this->client->getLastRequest(), 'Request is expected to contain the entire query string');
+        $this->assertContains($qstr, $this->client->getLastRequest(), 
+            'Request is expected to contain the entire query string');
     }
 
     /**
      * Test we can properly send POST parameters with
      * application/x-www-form-urlencoded content type
      *
+     * @dataProvider parameterArrayProvider
      */
-    public function testPostDataUrlEncoded()
+    public function testPostDataUrlEncoded($params)
     {
         $this->client->setUri($this->baseuri . 'testPostData.php');
-        $params = array(
-            'quest' => 'To seek the holy grail',
-            'YourMother' => 'Was a hamster',
-            'specialChars' => '<>$+ &?=[]^%',
-            'array' => array('firstItem', 'secondItem', '3rdItem')
-        );
-
         $this->client->setEncType(Zend_Http_Client::ENC_URLENCODED);
         $this->client->setParameterPost($params);
         $res = $this->client->request('POST');
@@ -208,17 +218,11 @@ class Zend_Http_Client_SocketTest extends PHPUnit_Framework_TestCase
      * Test we can properly send POST parameters with
      * multipart/form-data content type
      *
+     * @dataProvider parameterArrayProvider
      */
-    public function testPostDataMultipart()
+    public function testPostDataMultipart($params)
     {
         $this->client->setUri($this->baseuri . 'testPostData.php');
-        $params = array(
-            'quest' => 'To seek the holy grail',
-            'YourMother' => 'Was a hamster',
-            'specialChars' => '<>$+ &?=[]^%',
-            'array' => array('firstItem', 'secondItem', '3rdItem')
-        );
-
         $this->client->setEncType(Zend_Http_Client::ENC_FORMDATA);
         $this->client->setParameterPost($params);
         $res = $this->client->request('POST');
@@ -828,4 +832,45 @@ class Zend_Http_Client_SocketTest extends PHPUnit_Framework_TestCase
         return file_get_contents(dirname(realpath(__FILE__)) . DIRECTORY_SEPARATOR .
            '_files' . DIRECTORY_SEPARATOR . $file);
     }
+
+    /**
+     * Data provider for complex, nesting parameter arrays
+     * 
+     * @return array
+     */
+    static public function parameterArrayProvider()
+    {
+        return array(
+            array(
+                array(
+                    'quest' => 'To seek the holy grail',
+                    'YourMother' => 'Was a hamster',
+                    'specialChars' => '<>$+ &?=[]^%',
+                    'array' => array('firstItem', 'secondItem', '3rdItem')
+                )
+            ),
+            
+            array(
+                array(
+                    'someData' => array(
+                        "1", 
+                        "2", 
+                        'key' => 'value',
+                        'nesting' => array(
+                            'a' => 'AAA',
+                            'b' => 'BBB'
+                        )
+                    ),
+                    'someOtherData' => array('foo', 'bar')
+                )
+            ),
+            
+            array(
+                array(
+                    'foo1' => 'bar',
+                    'foo2' => array('baz', 'w00t')
+                ) 
+            )
+        );
+    }
 }

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

@@ -336,6 +336,46 @@ class Zend_Http_Client_StaticTest extends PHPUnit_Framework_TestCase
         $adapterCfg = $this->getObjectAttribute($adapter, 'config');
         $this->assertEquals('value2', $adapterCfg['param']);
     }
+    
+    /**
+     * Test that POST data with mutli-dimentional array is properly encoded as 
+     * multipart/form-data
+     * 
+     */
+    public function testFormDataEncodingWithMultiArrayZF7038()
+    {
+        require_once 'Zend/Http/Client/Adapter/Test.php';
+        $adapter = new Zend_Http_Client_Adapter_Test();
+        
+        $this->_client->setAdapter($adapter);
+        $this->_client->setUri('http://example.com');
+        $this->_client->setEncType(Zend_Http_Client::ENC_FORMDATA);
+        
+        $this->_client->setParameterPost('test', array(
+            'v0.1',
+            'v0.2',
+            'k1' => 'v1.0',
+            'k2' => array(
+                'v2.1',
+                'k2.1' => 'v2.1.0'
+            )
+        ));
+        
+        $this->_client->request('POST');
+        
+        $expectedLines = file(dirname(__FILE__) . '/_files/ZF7038-multipartarrayrequest.txt');
+        $gotLines = explode("\n", $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);
+        }
+    }
 
     /**
      * Data providers 

+ 30 - 0
tests/Zend/Http/Client/_files/ZF7038-multipartarrayrequest.txt

@@ -0,0 +1,30 @@
+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="test\[\]"
+
+v0\.1
+-----ZENDHTTPCLIENT-\w+
+Content-Disposition: form-data; name="test\[\]"
+
+v0\.2
+-----ZENDHTTPCLIENT-\w+
+Content-Disposition: form-data; name="test\[k1\]"
+
+v1\.0
+-----ZENDHTTPCLIENT-\w+
+Content-Disposition: form-data; name="test\[k2\]\[\]"
+
+v2\.1
+-----ZENDHTTPCLIENT-\w+
+Content-Disposition: form-data; name="test\[k2\]\[k2.1\]"
+
+v2\.1\.0
+-----ZENDHTTPCLIENT-\w+--
+