14 * to license@zend.com so we can send you a copy immediately. |
14 * to license@zend.com so we can send you a copy immediately. |
15 * |
15 * |
16 * @category Zend |
16 * @category Zend |
17 * @package Zend_Http |
17 * @package Zend_Http |
18 * @subpackage Client |
18 * @subpackage Client |
19 * @version $Id: Client.php 23443 2010-11-24 11:53:13Z shahar $ |
19 * @version $Id: Client.php 24593 2012-01-05 20:35:02Z matthew $ |
20 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
20 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
21 * @license http://framework.zend.com/license/new-bsd New BSD License |
21 * @license http://framework.zend.com/license/new-bsd New BSD License |
22 */ |
22 */ |
23 |
23 |
24 /** |
24 /** |
25 * @see Zend_Loader |
25 * @see Zend_Loader |
58 * @todo Implement proxy settings |
58 * @todo Implement proxy settings |
59 * @category Zend |
59 * @category Zend |
60 * @package Zend_Http |
60 * @package Zend_Http |
61 * @subpackage Client |
61 * @subpackage Client |
62 * @throws Zend_Http_Client_Exception |
62 * @throws Zend_Http_Client_Exception |
63 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
63 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) |
64 * @license http://framework.zend.com/license/new-bsd New BSD License |
64 * @license http://framework.zend.com/license/new-bsd New BSD License |
65 */ |
65 */ |
66 class Zend_Http_Client |
66 class Zend_Http_Client |
67 { |
67 { |
68 /** |
68 /** |
99 /** |
99 /** |
100 * POST data encoding methods |
100 * POST data encoding methods |
101 */ |
101 */ |
102 const ENC_URLENCODED = 'application/x-www-form-urlencoded'; |
102 const ENC_URLENCODED = 'application/x-www-form-urlencoded'; |
103 const ENC_FORMDATA = 'multipart/form-data'; |
103 const ENC_FORMDATA = 'multipart/form-data'; |
|
104 |
|
105 /** |
|
106 * Value types for Body key/value pairs |
|
107 */ |
|
108 const VTYPE_SCALAR = 'SCALAR'; |
|
109 const VTYPE_FILE = 'FILE'; |
104 |
110 |
105 /** |
111 /** |
106 * Configuration array, set using the constructor or using ::setConfig() |
112 * Configuration array, set using the constructor or using ::setConfig() |
107 * |
113 * |
108 * @var array |
114 * @var array |
201 * @var array |
207 * @var array |
202 */ |
208 */ |
203 protected $files = array(); |
209 protected $files = array(); |
204 |
210 |
205 /** |
211 /** |
|
212 * Ordered list of keys from key/value pair data to include in body |
|
213 * |
|
214 * An associative array, where each element is of the format: |
|
215 * '<field name>' => VTYPE_SCALAR | VTYPE_FILE |
|
216 * |
|
217 * @var array |
|
218 */ |
|
219 protected $body_field_order = array(); |
|
220 |
|
221 /** |
206 * The client's cookie jar |
222 * The client's cookie jar |
207 * |
223 * |
208 * @var Zend_Http_CookieJar |
224 * @var Zend_Http_CookieJar |
209 */ |
225 */ |
210 protected $cookiejar = null; |
226 protected $cookiejar = null; |
227 * Redirection counter |
243 * Redirection counter |
228 * |
244 * |
229 * @var int |
245 * @var int |
230 */ |
246 */ |
231 protected $redirectCounter = 0; |
247 protected $redirectCounter = 0; |
|
248 |
|
249 /** |
|
250 * Status for unmasking GET array params |
|
251 * |
|
252 * @var boolean |
|
253 */ |
|
254 protected $_unmaskStatus = false; |
|
255 |
|
256 /** |
|
257 * Status if the http_build_query function escapes brackets |
|
258 * |
|
259 * @var boolean |
|
260 */ |
|
261 protected $_queryBracketsEscaped = true; |
232 |
262 |
233 /** |
263 /** |
234 * Fileinfo magic database resource |
264 * Fileinfo magic database resource |
235 * |
265 * |
236 * This variable is populated the first time _detectFileMimeType is called |
266 * This variable is populated the first time _detectFileMimeType is called |
264 * @return Zend_Http_Client |
296 * @return Zend_Http_Client |
265 * @throws Zend_Http_Client_Exception |
297 * @throws Zend_Http_Client_Exception |
266 */ |
298 */ |
267 public function setUri($uri) |
299 public function setUri($uri) |
268 { |
300 { |
269 if (is_string($uri)) { |
301 if ($uri instanceof Zend_Uri_Http) { |
|
302 // clone the URI in order to keep the passed parameter constant |
|
303 $uri = clone $uri; |
|
304 } elseif (is_string($uri)) { |
270 $uri = Zend_Uri::factory($uri); |
305 $uri = Zend_Uri::factory($uri); |
271 } |
306 } |
272 |
307 |
273 if (!$uri instanceof Zend_Uri_Http) { |
308 if (!$uri instanceof Zend_Uri_Http) { |
274 /** @see Zend_Http_Client_Exception */ |
309 /** @see Zend_Http_Client_Exception */ |
353 /** @see Zend_Http_Client_Exception */ |
388 /** @see Zend_Http_Client_Exception */ |
354 require_once 'Zend/Http/Client/Exception.php'; |
389 require_once 'Zend/Http/Client/Exception.php'; |
355 throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method."); |
390 throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method."); |
356 } |
391 } |
357 |
392 |
358 if ($method == self::POST && $this->enctype === null) { |
393 if (($method == self::POST || $method == self::PUT || $method == self::DELETE) && $this->enctype === null) { |
359 $this->setEncType(self::ENC_URLENCODED); |
394 $this->setEncType(self::ENC_URLENCODED); |
360 } |
395 } |
361 |
396 |
362 $this->method = $method; |
397 $this->method = $method; |
363 |
398 |
499 case 'get': |
534 case 'get': |
500 $parray = &$this->paramsGet; |
535 $parray = &$this->paramsGet; |
501 break; |
536 break; |
502 case 'post': |
537 case 'post': |
503 $parray = &$this->paramsPost; |
538 $parray = &$this->paramsPost; |
|
539 if ( $value === null ) { |
|
540 if (isset($this->body_field_order[$name])) |
|
541 unset($this->body_field_order[$name]); |
|
542 } else { |
|
543 $this->body_field_order[$name] = self::VTYPE_SCALAR; |
|
544 } |
504 break; |
545 break; |
505 } |
546 } |
506 |
547 |
507 if ($value === null) { |
548 if ($value === null) { |
508 if (isset($parray[$name])) unset($parray[$name]); |
549 if (isset($parray[$name])) unset($parray[$name]); |
762 } |
805 } |
763 return $this; |
806 return $this; |
764 } |
807 } |
765 |
808 |
766 /** |
809 /** |
|
810 * Set the unmask feature for GET parameters as array |
|
811 * |
|
812 * Example: |
|
813 * foo%5B0%5D=a&foo%5B1%5D=b |
|
814 * becomes |
|
815 * foo=a&foo=b |
|
816 * |
|
817 * This is usefull for some services |
|
818 * |
|
819 * @param boolean $status |
|
820 * @return Zend_Http_Client |
|
821 */ |
|
822 public function setUnmaskStatus($status = true) |
|
823 { |
|
824 $this->_unmaskStatus = (BOOL)$status; |
|
825 return $this; |
|
826 } |
|
827 |
|
828 /** |
|
829 * Returns the currently configured unmask status |
|
830 * |
|
831 * @return boolean |
|
832 */ |
|
833 public function getUnmaskStatus() |
|
834 { |
|
835 return $this->_unmaskStatus; |
|
836 } |
|
837 |
|
838 /** |
767 * Clear all GET and POST parameters |
839 * Clear all GET and POST parameters |
768 * |
840 * |
769 * Should be used to reset the request parameters if the client is |
841 * Should be used to reset the request parameters if the client is |
770 * used for several concurrent requests. |
842 * used for several concurrent requests. |
771 * |
843 * |
780 // Reset parameter data |
852 // Reset parameter data |
781 $this->paramsGet = array(); |
853 $this->paramsGet = array(); |
782 $this->paramsPost = array(); |
854 $this->paramsPost = array(); |
783 $this->files = array(); |
855 $this->files = array(); |
784 $this->raw_post_data = null; |
856 $this->raw_post_data = null; |
|
857 $this->enctype = null; |
785 |
858 |
786 if($clearAll) { |
859 if($clearAll) { |
787 $this->headers = array(); |
860 $this->headers = array(); |
788 $this->last_request = null; |
861 $this->last_request = null; |
789 $this->last_response = null; |
862 $this->last_response = null; |
864 * |
937 * |
865 * @return Zend_Http_Client_Adapter_Interface $adapter |
938 * @return Zend_Http_Client_Adapter_Interface $adapter |
866 */ |
939 */ |
867 public function getAdapter() |
940 public function getAdapter() |
868 { |
941 { |
|
942 if (null === $this->adapter) { |
|
943 $this->setAdapter($this->config['adapter']); |
|
944 } |
|
945 |
869 return $this->adapter; |
946 return $this->adapter; |
870 } |
947 } |
871 |
948 |
872 /** |
949 /** |
873 * Set streaming for received data |
950 * Set streaming for received data |
909 $this->adapter->close(); |
986 $this->adapter->close(); |
910 } |
987 } |
911 require_once 'Zend/Http/Client/Exception.php'; |
988 require_once 'Zend/Http/Client/Exception.php'; |
912 throw new Zend_Http_Client_Exception("Could not open temp file {$this->_stream_name}"); |
989 throw new Zend_Http_Client_Exception("Could not open temp file {$this->_stream_name}"); |
913 } |
990 } |
914 |
991 |
915 return $fp; |
992 return $fp; |
916 } |
993 } |
917 |
994 |
918 /** |
995 /** |
919 * Send the HTTP request and return an HTTP response object |
996 * Send the HTTP request and return an HTTP response object |
920 * |
997 * |
921 * @param string $method |
998 * @param string $method |
922 * @return Zend_Http_Response |
999 * @return Zend_Http_Response |
953 $query .= http_build_query($this->paramsGet, null, '&'); |
1030 $query .= http_build_query($this->paramsGet, null, '&'); |
954 if ($this->config['rfc3986_strict']) { |
1031 if ($this->config['rfc3986_strict']) { |
955 $query = str_replace('+', '%20', $query); |
1032 $query = str_replace('+', '%20', $query); |
956 } |
1033 } |
957 |
1034 |
|
1035 // @see ZF-11671 to unmask for some services to foo=val1&foo=val2 |
|
1036 if ($this->getUnmaskStatus()) { |
|
1037 if ($this->_queryBracketsEscaped) { |
|
1038 $query = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $query); |
|
1039 } else { |
|
1040 $query = preg_replace('/\\[(?:[0-9]|[1-9][0-9]+)\\]=/', '=', $query); |
|
1041 } |
|
1042 } |
|
1043 |
958 $uri->setQuery($query); |
1044 $uri->setQuery($query); |
959 } |
1045 } |
960 |
1046 |
961 $body = $this->_prepareBody(); |
1047 $body = $this->_prepareBody(); |
962 $headers = $this->_prepareHeaders(); |
1048 $headers = $this->_prepareHeaders(); |
992 require_once 'Zend/Http/Client/Exception.php'; |
1078 require_once 'Zend/Http/Client/Exception.php'; |
993 throw new Zend_Http_Client_Exception('Unable to read response, or response is empty'); |
1079 throw new Zend_Http_Client_Exception('Unable to read response, or response is empty'); |
994 } |
1080 } |
995 |
1081 |
996 if($this->config['output_stream']) { |
1082 if($this->config['output_stream']) { |
997 rewind($stream); |
1083 $streamMetaData = stream_get_meta_data($stream); |
|
1084 if ($streamMetaData['seekable']) { |
|
1085 rewind($stream); |
|
1086 } |
998 // cleanup the adapter |
1087 // cleanup the adapter |
999 $this->adapter->setOutputStream(null); |
1088 $this->adapter->setOutputStream(null); |
1000 $response = Zend_Http_Response_Stream::fromStream($response, $stream); |
1089 $response = Zend_Http_Response_Stream::fromStream($response, $stream); |
1001 $response->setStreamName($this->_stream_name); |
1090 $response->setStreamName($this->_stream_name); |
1002 if(!is_string($this->config['output_stream'])) { |
1091 if(!is_string($this->config['output_stream'])) { |
1017 } |
1106 } |
1018 |
1107 |
1019 // If we got redirected, look for the Location header |
1108 // If we got redirected, look for the Location header |
1020 if ($response->isRedirect() && ($location = $response->getHeader('location'))) { |
1109 if ($response->isRedirect() && ($location = $response->getHeader('location'))) { |
1021 |
1110 |
|
1111 // Avoid problems with buggy servers that add whitespace at the |
|
1112 // end of some headers (See ZF-11283) |
|
1113 $location = trim($location); |
|
1114 |
1022 // Check whether we send the exact same request again, or drop the parameters |
1115 // Check whether we send the exact same request again, or drop the parameters |
1023 // and send a GET request |
1116 // and send a GET request |
1024 if ($response->getStatus() == 303 || |
1117 if ($response->getStatus() == 303 || |
1025 ((! $this->config['strictredirects']) && ($response->getStatus() == 302 || |
1118 ((! $this->config['strictredirects']) && ($response->getStatus() == 302 || |
1026 $response->getStatus() == 301))) { |
1119 $response->getStatus() == 301))) { |
1028 $this->resetParameters(); |
1121 $this->resetParameters(); |
1029 $this->setMethod(self::GET); |
1122 $this->setMethod(self::GET); |
1030 } |
1123 } |
1031 |
1124 |
1032 // If we got a well formed absolute URI |
1125 // If we got a well formed absolute URI |
1033 if (Zend_Uri_Http::check($location)) { |
1126 if (($scheme = substr($location, 0, 6)) && ($scheme == 'http:/' || $scheme == 'https:')) { |
1034 $this->setHeaders('host', null); |
1127 $this->setHeaders('host', null); |
1035 $this->setUri($location); |
1128 $this->setUri($location); |
1036 |
1129 |
1037 } else { |
1130 } else { |
1038 |
1131 |
1196 case self::ENC_FORMDATA: |
1289 case self::ENC_FORMDATA: |
1197 // Encode body as multipart/form-data |
1290 // Encode body as multipart/form-data |
1198 $boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); |
1291 $boundary = '---ZENDHTTPCLIENT-' . md5(microtime()); |
1199 $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}"); |
1292 $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}"); |
1200 |
1293 |
1201 // Get POST parameters and encode them |
1294 // Encode all files and POST vars in the order they were given |
1202 $params = self::_flattenParametersArray($this->paramsPost); |
1295 foreach ($this->body_field_order as $fieldName=>$fieldType) { |
1203 foreach ($params as $pp) { |
1296 switch ($fieldType) { |
1204 $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); |
1297 case self::VTYPE_FILE: |
1205 } |
1298 foreach ($this->files as $file) { |
1206 |
1299 if ($file['formname']===$fieldName) { |
1207 // Encode files |
1300 $fhead = array(self::CONTENT_TYPE => $file['ctype']); |
1208 foreach ($this->files as $file) { |
1301 $body .= self::encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead); |
1209 $fhead = array(self::CONTENT_TYPE => $file['ctype']); |
1302 } |
1210 $body .= self::encodeFormData($boundary, $file['formname'], $file['data'], $file['filename'], $fhead); |
1303 } |
|
1304 break; |
|
1305 case self::VTYPE_SCALAR: |
|
1306 if (isset($this->paramsPost[$fieldName])) { |
|
1307 if (is_array($this->paramsPost[$fieldName])) { |
|
1308 $flattened = self::_flattenParametersArray($this->paramsPost[$fieldName], $fieldName); |
|
1309 foreach ($flattened as $pp) { |
|
1310 $body .= self::encodeFormData($boundary, $pp[0], $pp[1]); |
|
1311 } |
|
1312 } else { |
|
1313 $body .= self::encodeFormData($boundary, $fieldName, $this->paramsPost[$fieldName]); |
|
1314 } |
|
1315 } |
|
1316 break; |
|
1317 } |
1211 } |
1318 } |
1212 |
1319 |
1213 $body .= "--{$boundary}--\r\n"; |
1320 $body .= "--{$boundary}--\r\n"; |
1214 break; |
1321 break; |
1215 |
1322 |