]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/HTTP/Request2/MultipartBody.php
PEAR::HTTP_Request2 updated to 2.2.1
[quix0rs-gnu-social.git] / extlib / HTTP / Request2 / MultipartBody.php
1 <?php\r
2 /**\r
3  * Helper class for building multipart/form-data request body\r
4  *\r
5  * PHP version 5\r
6  *\r
7  * LICENSE\r
8  *\r
9  * This source file is subject to BSD 3-Clause License that is bundled\r
10  * with this package in the file LICENSE and available at the URL\r
11  * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE\r
12  *\r
13  * @category  HTTP\r
14  * @package   HTTP_Request2\r
15  * @author    Alexey Borzov <avb@php.net>\r
16  * @copyright 2008-2014 Alexey Borzov <avb@php.net>\r
17  * @license   http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License\r
18  * @link      http://pear.php.net/package/HTTP_Request2\r
19  */\r
20 \r
21 /** Exception class for HTTP_Request2 package */\r
22 require_once 'HTTP/Request2/Exception.php';\r
23 \r
24 /**\r
25  * Class for building multipart/form-data request body\r
26  *\r
27  * The class helps to reduce memory consumption by streaming large file uploads\r
28  * from disk, it also allows monitoring of upload progress (see request #7630)\r
29  *\r
30  * @category HTTP\r
31  * @package  HTTP_Request2\r
32  * @author   Alexey Borzov <avb@php.net>\r
33  * @license  http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License\r
34  * @version  Release: 2.2.1\r
35  * @link     http://pear.php.net/package/HTTP_Request2\r
36  * @link     http://tools.ietf.org/html/rfc1867\r
37  */\r
38 class HTTP_Request2_MultipartBody\r
39 {\r
40     /**\r
41      * MIME boundary\r
42      * @var  string\r
43      */\r
44     private $_boundary;\r
45 \r
46     /**\r
47      * Form parameters added via {@link HTTP_Request2::addPostParameter()}\r
48      * @var  array\r
49      */\r
50     private $_params = array();\r
51 \r
52     /**\r
53      * File uploads added via {@link HTTP_Request2::addUpload()}\r
54      * @var  array\r
55      */\r
56     private $_uploads = array();\r
57 \r
58     /**\r
59      * Header for parts with parameters\r
60      * @var  string\r
61      */\r
62     private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n";\r
63 \r
64     /**\r
65      * Header for parts with uploads\r
66      * @var  string\r
67      */\r
68     private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";\r
69 \r
70     /**\r
71      * Current position in parameter and upload arrays\r
72      *\r
73      * First number is index of "current" part, second number is position within\r
74      * "current" part\r
75      *\r
76      * @var  array\r
77      */\r
78     private $_pos = array(0, 0);\r
79 \r
80 \r
81     /**\r
82      * Constructor. Sets the arrays with POST data.\r
83      *\r
84      * @param array $params      values of form fields set via\r
85      *                           {@link HTTP_Request2::addPostParameter()}\r
86      * @param array $uploads     file uploads set via\r
87      *                           {@link HTTP_Request2::addUpload()}\r
88      * @param bool  $useBrackets whether to append brackets to array variable names\r
89      */\r
90     public function __construct(array $params, array $uploads, $useBrackets = true)\r
91     {\r
92         $this->_params = self::_flattenArray('', $params, $useBrackets);\r
93         foreach ($uploads as $fieldName => $f) {\r
94             if (!is_array($f['fp'])) {\r
95                 $this->_uploads[] = $f + array('name' => $fieldName);\r
96             } else {\r
97                 for ($i = 0; $i < count($f['fp']); $i++) {\r
98                     $upload = array(\r
99                         'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName)\r
100                     );\r
101                     foreach (array('fp', 'filename', 'size', 'type') as $key) {\r
102                         $upload[$key] = $f[$key][$i];\r
103                     }\r
104                     $this->_uploads[] = $upload;\r
105                 }\r
106             }\r
107         }\r
108     }\r
109 \r
110     /**\r
111      * Returns the length of the body to use in Content-Length header\r
112      *\r
113      * @return   integer\r
114      */\r
115     public function getLength()\r
116     {\r
117         $boundaryLength     = strlen($this->getBoundary());\r
118         $headerParamLength  = strlen($this->_headerParam) - 4 + $boundaryLength;\r
119         $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength;\r
120         $length             = $boundaryLength + 6;\r
121         foreach ($this->_params as $p) {\r
122             $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2;\r
123         }\r
124         foreach ($this->_uploads as $u) {\r
125             $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) +\r
126                        strlen($u['filename']) + $u['size'] + 2;\r
127         }\r
128         return $length;\r
129     }\r
130 \r
131     /**\r
132      * Returns the boundary to use in Content-Type header\r
133      *\r
134      * @return   string\r
135      */\r
136     public function getBoundary()\r
137     {\r
138         if (empty($this->_boundary)) {\r
139             $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime());\r
140         }\r
141         return $this->_boundary;\r
142     }\r
143 \r
144     /**\r
145      * Returns next chunk of request body\r
146      *\r
147      * @param integer $length Number of bytes to read\r
148      *\r
149      * @return   string  Up to $length bytes of data, empty string if at end\r
150      * @throws   HTTP_Request2_LogicException\r
151      */\r
152     public function read($length)\r
153     {\r
154         $ret         = '';\r
155         $boundary    = $this->getBoundary();\r
156         $paramCount  = count($this->_params);\r
157         $uploadCount = count($this->_uploads);\r
158         while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) {\r
159             $oldLength = $length;\r
160             if ($this->_pos[0] < $paramCount) {\r
161                 $param = sprintf(\r
162                     $this->_headerParam, $boundary, $this->_params[$this->_pos[0]][0]\r
163                 ) . $this->_params[$this->_pos[0]][1] . "\r\n";\r
164                 $ret    .= substr($param, $this->_pos[1], $length);\r
165                 $length -= min(strlen($param) - $this->_pos[1], $length);\r
166 \r
167             } elseif ($this->_pos[0] < $paramCount + $uploadCount) {\r
168                 $pos    = $this->_pos[0] - $paramCount;\r
169                 $header = sprintf(\r
170                     $this->_headerUpload, $boundary, $this->_uploads[$pos]['name'],\r
171                     $this->_uploads[$pos]['filename'], $this->_uploads[$pos]['type']\r
172                 );\r
173                 if ($this->_pos[1] < strlen($header)) {\r
174                     $ret    .= substr($header, $this->_pos[1], $length);\r
175                     $length -= min(strlen($header) - $this->_pos[1], $length);\r
176                 }\r
177                 $filePos  = max(0, $this->_pos[1] - strlen($header));\r
178                 if ($filePos < $this->_uploads[$pos]['size']) {\r
179                     while ($length > 0 && !feof($this->_uploads[$pos]['fp'])) {\r
180                         if (false === ($chunk = fread($this->_uploads[$pos]['fp'], $length))) {\r
181                             throw new HTTP_Request2_LogicException(\r
182                                 'Failed reading file upload', HTTP_Request2_Exception::READ_ERROR\r
183                             );\r
184                         }\r
185                         $ret    .= $chunk;\r
186                         $length -= strlen($chunk);\r
187                     }\r
188                 }\r
189                 if ($length > 0) {\r
190                     $start   = $this->_pos[1] + ($oldLength - $length) -\r
191                                strlen($header) - $this->_uploads[$pos]['size'];\r
192                     $ret    .= substr("\r\n", $start, $length);\r
193                     $length -= min(2 - $start, $length);\r
194                 }\r
195 \r
196             } else {\r
197                 $closing  = '--' . $boundary . "--\r\n";\r
198                 $ret     .= substr($closing, $this->_pos[1], $length);\r
199                 $length  -= min(strlen($closing) - $this->_pos[1], $length);\r
200             }\r
201             if ($length > 0) {\r
202                 $this->_pos     = array($this->_pos[0] + 1, 0);\r
203             } else {\r
204                 $this->_pos[1] += $oldLength;\r
205             }\r
206         }\r
207         return $ret;\r
208     }\r
209 \r
210     /**\r
211      * Sets the current position to the start of the body\r
212      *\r
213      * This allows reusing the same body in another request\r
214      */\r
215     public function rewind()\r
216     {\r
217         $this->_pos = array(0, 0);\r
218         foreach ($this->_uploads as $u) {\r
219             rewind($u['fp']);\r
220         }\r
221     }\r
222 \r
223     /**\r
224      * Returns the body as string\r
225      *\r
226      * Note that it reads all file uploads into memory so it is a good idea not\r
227      * to use this method with large file uploads and rely on read() instead.\r
228      *\r
229      * @return   string\r
230      */\r
231     public function __toString()\r
232     {\r
233         $this->rewind();\r
234         return $this->read($this->getLength());\r
235     }\r
236 \r
237 \r
238     /**\r
239      * Helper function to change the (probably multidimensional) associative array\r
240      * into the simple one.\r
241      *\r
242      * @param string $name        name for item\r
243      * @param mixed  $values      item's values\r
244      * @param bool   $useBrackets whether to append [] to array variables' names\r
245      *\r
246      * @return   array   array with the following items: array('item name', 'item value');\r
247      */\r
248     private static function _flattenArray($name, $values, $useBrackets)\r
249     {\r
250         if (!is_array($values)) {\r
251             return array(array($name, $values));\r
252         } else {\r
253             $ret = array();\r
254             foreach ($values as $k => $v) {\r
255                 if (empty($name)) {\r
256                     $newName = $k;\r
257                 } elseif ($useBrackets) {\r
258                     $newName = $name . '[' . $k . ']';\r
259                 } else {\r
260                     $newName = $name;\r
261                 }\r
262                 $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets));\r
263             }\r
264             return $ret;\r
265         }\r
266     }\r
267 }\r
268 ?>\r