/packages/zendframework/http.php
[return to app]1
<?php
2 /**
3 * Zend Framework Http class isolated from ZF dependencies to work in Vork
4 *
5 * LICENSE
6 *
7 * This source file is subject to the new BSD license that is bundled
8 * with this package in the file LICENSE.txt.
9 * It is also available through the world-wide-web at this URL:
10 * http://framework.zend.com/license/new-bsd
11 * If you did not receive a copy of the license and are unable to
12 * obtain it through the world-wide-web, please send an email
13 * to license@zend.com so we can send you a copy immediately.
14 *
15 * A Zend_Http_CookieJar object is designed to contain and maintain HTTP cookies, and should
16 * be used along with Zend_Http_Client in order to manage cookies across HTTP requests and
17 * responses.
18 *
19 * The class contains an array of Zend_Http_Cookie objects. Cookies can be added to the jar
20 * automatically from a request or manually. Then, the jar can find and return the cookies
21 * needed for a specific HTTP request.
22 *
23 * A special parameter can be passed to all methods of this class that return cookies: Cookies
24 * can be returned either in their native form (as Zend_Http_Cookie objects) or as strings -
25 * the later is suitable for sending as the value of the "Cookie" header in an HTTP request.
26 * You can also choose, when returning more than one cookie, whether to get an array of strings
27 * (by passing Zend_Http_CookieJar::COOKIE_STRING_ARRAY) or one unified string for all cookies
28 * (by passing Zend_Http_CookieJar::COOKIE_STRING_CONCAT).
29 *
30 * @link http://wp.netscape.com/newsref/std/cookie_spec.html for some specs.
31 * @category Zend
32 * @package Zend_Http
33 * @subpackage CookieJar
34 * @version $Id: CookieJar.php 20096 2010-01-06 02:05:09Z bkarwin $
35 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
36 * @license http://framework.zend.com/license/new-bsd New BSD License
37 */
38 class Zend_Http_CookieJar implements Countable, IteratorAggregate {
39 /**
40 * Return cookie(s) as a Zend_Http_Cookie object
41 *
42 */
43 const COOKIE_OBJECT = 0;
44
45 /**
46 * Return cookie(s) as a string (suitable for sending in an HTTP request)
47 *
48 */
49 const COOKIE_STRING_ARRAY = 1;
50
51 /**
52 * Return all cookies as one long string (suitable for sending in an HTTP request)
53 *
54 */
55 const COOKIE_STRING_CONCAT = 2;
56
57 /**
58 * Array storing cookies
59 *
60 * Cookies are stored according to domain and path:
61 * $cookies
62 * + www.mydomain.com
63 * + /
64 * - cookie1
65 * - cookie2
66 * + /somepath
67 * - othercookie
68 * + www.otherdomain.net
69 * + /
70 * - alsocookie
71 *
72 * @var array
73 */
74 protected $cookies = array();
75
76 /**
77 * The Zend_Http_Cookie array
78 *
79 * @var array
80 */
81 protected $_rawCookies = array();
82
83 /**
84 * Construct a new CookieJar object
85 *
86 */
87 public function __construct() { }
88
89 /**
90 * Add a cookie to the jar. Cookie should be passed either as a Zend_Http_Cookie object
91 * or as a string - in which case an object is created from the string.
92 *
93 * @param Zend_Http_Cookie|string $cookie
94 * @param Zend_Uri_Http|string $ref_uri Optional reference URI (for domain, path, secure)
95 */
96 public function addCookie($cookie, $ref_uri = null) {
97 if (is_string($cookie)) {
98 $cookie = Zend_Http_Cookie::fromString($cookie, $ref_uri);
99 }
100
101 if ($cookie instanceof Zend_Http_Cookie) {
102 $domain = $cookie->getDomain();
103 $path = $cookie->getPath();
104 if (! isset($this->cookies[$domain])) $this->cookies[$domain] = array();
105 if (! isset($this->cookies[$domain][$path])) $this->cookies[$domain][$path] = array();
106 $this->cookies[$domain][$path][$cookie->getName()] = $cookie;
107 $this->_rawCookies[] = $cookie;
108 } else {
109 if (!class_exists('Zend_Exception')) require 'exception.php';
110 throw new Zend_Http_Exception('Supplient argument is not a valid cookie string or object');
111 }
112 }
113
114 /**
115 * Parse an HTTP response, adding all the cookies set in that response
116 * to the cookie jar.
117 *
118 * @param Zend_Http_Response $response
119 * @param Zend_Uri_Http|string $ref_uri Requested URI
120 */
121 public function addCookiesFromResponse($response, $ref_uri) {
122 if (! $response instanceof Zend_Http_Response) {
123 if (!class_exists('Zend_Exception')) require 'exception.php';
124 throw new Zend_Http_Exception('$response is expected to be a Response object, ' .
125 gettype($response) . ' was passed');
126 }
127
128 $cookie_hdrs = $response->getHeader('Set-Cookie');
129
130 if (is_array($cookie_hdrs)) {
131 foreach ($cookie_hdrs as $cookie) {
132 $this->addCookie($cookie, $ref_uri);
133 }
134 } elseif (is_string($cookie_hdrs)) {
135 $this->addCookie($cookie_hdrs, $ref_uri);
136 }
137 }
138
139 /**
140 * Get all cookies in the cookie jar as an array
141 *
142 * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings
143 * @return array|string
144 */
145 public function getAllCookies($ret_as = self::COOKIE_OBJECT) {
146 $cookies = $this->_flattenCookiesArray($this->cookies, $ret_as);
147 return $cookies;
148 }
149
150 /**
151 * Return an array of all cookies matching a specific request according to the request URI,
152 * whether session cookies should be sent or not, and the time to consider as "now" when
153 * checking cookie expiry time.
154 *
155 * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path)
156 * @param boolean $matchSessionCookies Whether to send session cookies
157 * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings
158 * @param int $now Override the current time when checking for expiry time
159 * @return array|string
160 */
161 public function getMatchingCookies($uri, $matchSessionCookies = true,
162 $ret_as = self::COOKIE_OBJECT, $now = null) {
163 if (is_string($uri)) $uri = Zend_Uri::factory($uri);
164 if (! $uri instanceof Zend_Uri_Http) {
165 if (!class_exists('Zend_Exception')) require 'exception.php';
166 throw new Zend_Http_Exception("Invalid URI string or object passed");
167 }
168
169 // First, reduce the array of cookies to only those matching domain and path
170 $cookies = $this->_matchDomain($uri->getHost());
171 $cookies = $this->_matchPath($cookies, $uri->getPath());
172 $cookies = $this->_flattenCookiesArray($cookies, self::COOKIE_OBJECT);
173
174 // Next, run Cookie->match on all cookies to check secure, time and session mathcing
175 $ret = array();
176 foreach ($cookies as $cookie)
177 if ($cookie->match($uri, $matchSessionCookies, $now))
178 $ret[] = $cookie;
179
180 // Now, use self::_flattenCookiesArray again - only to convert to the return format ;)
181 $ret = $this->_flattenCookiesArray($ret, $ret_as);
182
183 return $ret;
184 }
185
186 /**
187 * Get a specific cookie according to a URI and name
188 *
189 * @param Zend_Uri_Http|string $uri The uri (domain and path) to match
190 * @param string $cookie_name The cookie's name
191 * @param int $ret_as Whether to return cookies as objects of Zend_Http_Cookie or as strings
192 * @return Zend_Http_Cookie|string
193 */
194 public function getCookie($uri, $cookie_name, $ret_as = self::COOKIE_OBJECT) {
195 if (is_string($uri)) {
196 $uri = Zend_Uri::factory($uri);
197 }
198
199 if (! $uri instanceof Zend_Uri_Http) {
200 if (!class_exists('Zend_Exception')) require 'exception.php';
201 throw new Zend_Http_Exception('Invalid URI specified');
202 }
203
204 // Get correct cookie path
205 $path = $uri->getPath();
206 $path = substr($path, 0, strrpos($path, '/'));
207 if (! $path) $path = '/';
208
209 if (isset($this->cookies[$uri->getHost()][$path][$cookie_name])) {
210 $cookie = $this->cookies[$uri->getHost()][$path][$cookie_name];
211
212 switch ($ret_as) {
213 case self::COOKIE_OBJECT:
214 return $cookie;
215 break;
216
217 case self::COOKIE_STRING_ARRAY:
218 case self::COOKIE_STRING_CONCAT:
219 return $cookie->__toString();
220 break;
221
222 default:
223 if (!class_exists('Zend_Exception')) require 'exception.php';
224 throw new Zend_Http_Exception("Invalid value passed for \$ret_as: {$ret_as}");
225 break;
226 }
227 } else {
228 return false;
229 }
230 }
231
232 /**
233 * Helper function to recursivly flatten an array. Shoud be used when exporting the
234 * cookies array (or parts of it)
235 *
236 * @param Zend_Http_Cookie|array $ptr
237 * @param int $ret_as What value to return
238 * @return array|string
239 */
240 protected function _flattenCookiesArray($ptr, $ret_as = self::COOKIE_OBJECT) {
241 if (is_array($ptr)) {
242 $ret = ($ret_as == self::COOKIE_STRING_CONCAT ? '' : array());
243 foreach ($ptr as $item) {
244 if ($ret_as == self::COOKIE_STRING_CONCAT) {
245 $ret .= $this->_flattenCookiesArray($item, $ret_as);
246 } else {
247 $ret = array_merge($ret, $this->_flattenCookiesArray($item, $ret_as));
248 }
249 }
250 return $ret;
251 } elseif ($ptr instanceof Zend_Http_Cookie) {
252 switch ($ret_as) {
253 case self::COOKIE_STRING_ARRAY:
254 return array($ptr->__toString());
255 break;
256
257 case self::COOKIE_STRING_CONCAT:
258 return $ptr->__toString();
259 break;
260
261 case self::COOKIE_OBJECT:
262 default:
263 return array($ptr);
264 break;
265 }
266 }
267
268 return null;
269 }
270
271 /**
272 * Return a subset of the cookies array matching a specific domain
273 *
274 * @param string $domain
275 * @return array
276 */
277 protected function _matchDomain($domain) {
278 $ret = array();
279
280 foreach (array_keys($this->cookies) as $cdom) {
281 if (Zend_Http_Cookie::matchCookieDomain($cdom, $domain)) {
282 $ret[$cdom] = $this->cookies[$cdom];
283 }
284 }
285
286 return $ret;
287 }
288
289 /**
290 * Return a subset of a domain-matching cookies that also match a specified path
291 *
292 * @param array $dom_array
293 * @param string $path
294 * @return array
295 */
296 protected function _matchPath($domains, $path) {
297 $ret = array();
298
299 foreach ($domains as $dom => $paths_array) {
300 foreach (array_keys($paths_array) as $cpath) {
301 if (Zend_Http_Cookie::matchCookiePath($cpath, $path)) {
302 if (! isset($ret[$dom])) {
303 $ret[$dom] = array();
304 }
305
306 $ret[$dom][$cpath] = $paths_array[$cpath];
307 }
308 }
309 }
310
311 return $ret;
312 }
313
314 /**
315 * Create a new CookieJar object and automatically load into it all the
316 * cookies set in an Http_Response object. If $uri is set, it will be
317 * considered as the requested URI for setting default domain and path
318 * of the cookie.
319 *
320 * @param Zend_Http_Response $response HTTP Response object
321 * @param Zend_Uri_Http|string $uri The requested URI
322 * @return Zend_Http_CookieJar
323 * @todo Add the $uri functionality.
324 */
325 public static function fromResponse(Zend_Http_Response $response, $ref_uri) {
326 $jar = new self();
327 $jar->addCookiesFromResponse($response, $ref_uri);
328 return $jar;
329 }
330
331 /**
332 * Required by Countable interface
333 *
334 * @return int
335 */
336 public function count() {
337 return count($this->_rawCookies);
338 }
339
340 /**
341 * Required by IteratorAggregate interface
342 *
343 * @return ArrayIterator
344 */
345 public function getIterator() {
346 return new ArrayIterator($this->_rawCookies);
347 }
348
349 /**
350 * Tells if the jar is empty of any cookie
351 *
352 * @return bool
353 */
354 public function isEmpty() {
355 return count($this) == 0;
356 }
357
358 /**
359 * Empties the cookieJar of any cookie
360 *
361 * @return Zend_Http_CookieJar
362 */
363 public function reset() {
364 $this->cookies = $this->_rawCookies = array();
365 return $this;
366 }
367 }
368
369 /**
370 * A testing-purposes adapter.
371 *
372 * Should be used to test all components that rely on Zend_Http_Client,
373 * without actually performing an HTTP request. You should instantiate this
374 * object manually, and then set it as the client's adapter. Then, you can
375 * set the expected response using the setResponse() method.
376 *
377 * @category Zend
378 * @package Zend_Http
379 * @subpackage Client_Adapter
380 * @version $Id: Test.php 20096 2010-01-06 02:05:09Z bkarwin $
381 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
382 * @license http://framework.zend.com/license/new-bsd New BSD License
383 */
384 class Zend_Http_Client_Adapter_Test implements Zend_Http_Client_Adapter_Interface {
385 /**
386 * Parameters array
387 *
388 * @var array
389 */
390 protected $config = array();
391
392 /**
393 * Buffer of responses to be returned by the read() method. Can be
394 * set using setResponse() and addResponse().
395 *
396 * @var array
397 */
398 protected $responses = array("HTTP/1.1 400 Bad Request\r\n\r\n");
399
400 /**
401 * Current position in the response buffer
402 *
403 * @var integer
404 */
405 protected $responseIndex = 0;
406
407 /**
408 * Wether or not the next request will fail with an exception
409 *
410 * @var boolean
411 */
412 protected $_nextRequestWillFail = false;
413
414 /**
415 * Adapter constructor, currently empty. Config is set using setConfig()
416 *
417 */
418 public function __construct() { }
419
420 /**
421 * Set the nextRequestWillFail flag
422 *
423 * @param boolean $flag
424 * @return Zend_Http_Client_Adapter_Test
425 */
426 public function setNextRequestWillFail($flag) {
427 $this->_nextRequestWillFail = (bool) $flag;
428
429 return $this;
430 }
431
432 /**
433 * Set the configuration array for the adapter
434 *
435 * @param Zend_Config | array $config
436 */
437 public function setConfig($config = array()) {
438 if ($config instanceof Zend_Config) {
439 $config = $config->toArray();
440
441 } elseif (! is_array($config)) {
442 if (!class_exists('Zend_Exception')) require 'exception.php';
443 throw new Zend_Http_Client_Adapter_Exception(
444 'Array or Zend_Config object expected, got ' . gettype($config)
445 );
446 }
447
448 foreach ($config as $k => $v) {
449 $this->config[strtolower($k)] = $v;
450 }
451 }
452
453 /**
454 * Connect to the remote server
455 *
456 * @param string $host
457 * @param int $port
458 * @param boolean $secure
459 * @param int $timeout
460 * @throws Zend_Http_Client_Adapter_Exception
461 */
462 public function connect($host, $port = 80, $secure = false) {
463 if ($this->_nextRequestWillFail) {
464 $this->_nextRequestWillFail = false;
465 if (!class_exists('Zend_Exception')) require 'exception.php';
466 throw new Zend_Http_Client_Adapter_Exception('Request failed');
467 }
468 }
469
470 /**
471 * Send request to the remote server
472 *
473 * @param string $method
474 * @param Zend_Uri_Http $uri
475 * @param string $http_ver
476 * @param array $headers
477 * @param string $body
478 * @return string Request as string
479 */
480 public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') {
481 $host = $uri->getHost();
482 $host = (strtolower($uri->getScheme()) == 'https' ? 'sslv2://' . $host : $host);
483
484 // Build request headers
485 $path = $uri->getPath();
486 if ($uri->getQuery()) $path .= '?' . $uri->getQuery();
487 $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
488 foreach ($headers as $k => $v) {
489 if (is_string($k)) $v = ucfirst($k) . ": $v";
490 $request .= "$v\r\n";
491 }
492
493 // Add the request body
494 $request .= "\r\n" . $body;
495
496 // Do nothing - just return the request as string
497 return $request;
498 }
499
500 /**
501 * Return the response set in $this->setResponse()
502 *
503 * @return string
504 */
505 public function read() {
506 if ($this->responseIndex >= count($this->responses)) {
507 $this->responseIndex = 0;
508 }
509 return $this->responses[$this->responseIndex++];
510 }
511
512 /**
513 * Close the connection (dummy)
514 *
515 */
516 public function close() { }
517
518 /**
519 * Set the HTTP response(s) to be returned by this adapter
520 *
521 * @param Zend_Http_Response|array|string $response
522 */
523 public function setResponse($response) {
524 if ($response instanceof Zend_Http_Response) {
525 $response = $response->asString("\r\n");
526 }
527
528 $this->responses = (array)$response;
529 $this->responseIndex = 0;
530 }
531
532 /**
533 * Add another response to the response buffer.
534 *
535 * @param string Zend_Http_Response|$response
536 */
537 public function addResponse($response) {
538 if ($response instanceof Zend_Http_Response) {
539 $response = $response->asString("\r\n");
540 }
541
542 $this->responses[] = $response;
543 }
544
545 /**
546 * Sets the position of the response buffer. Selects which
547 * response will be returned on the next call to read().
548 *
549 * @param integer $index
550 */
551 public function setResponseIndex($index) {
552 if ($index < 0 || $index >= count($this->responses)) {
553 if (!class_exists('Zend_Exception')) require 'exception.php';
554 throw new Zend_Http_Client_Adapter_Exception(
555 'Index out of range of response buffer size');
556 }
557 $this->responseIndex = $index;
558 }
559 }
560
561 /**
562 * An adapter class for Zend_Http_Client based on the curl extension.
563 * Curl requires libcurl. See for full requirements the PHP manual: http://php.net/curl
564 *
565 * @category Zend
566 * @package Zend_Http
567 * @subpackage Client_Adapter
568 * @version $Id: Curl.php 22221 2010-05-21 07:00:58Z dragonbe $
569 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
570 * @license http://framework.zend.com/license/new-bsd New BSD License
571 */
572 class Zend_Http_Client_Adapter_Curl implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
{
573 /**
574 * Parameters array
575 *
576 * @var array
577 */
578 protected $_config = array();
579
580 /**
581 * What host/port are we connected to?
582 *
583 * @var array
584 */
585 protected $_connected_to = array(null, null);
586
587 /**
588 * The curl session handle
589 *
590 * @var resource|null
591 */
592 protected $_curl = null;
593
594 /**
595 * List of cURL options that should never be overwritten
596 *
597 * @var array
598 */
599 protected $_invalidOverwritableCurlOptions;
600
601 /**
602 * Response gotten from server
603 *
604 * @var string
605 */
606 protected $_response = null;
607
608 /**
609 * Stream for storing output
610 *
611 * @var resource
612 */
613 protected $out_stream;
614
615 /**
616 * Adapter constructor
617 *
618 * Config is set using setConfig()
619 *
620 * @return void
621 * @throws Zend_Http_Client_Adapter_Exception
622 */
623 public function __construct() {
624 if (!extension_loaded('curl')) {
625 if (!class_exists('Zend_Exception')) require 'exception.php';
626 throw new Zend_Http_Client_Adapter_Exception('cURL extension has to be loaded to use this
Zend_Http_Client adapter.');
627 }
628 $this->_invalidOverwritableCurlOptions = array(
629 CURLOPT_HTTPGET,
630 CURLOPT_POST,
631 CURLOPT_PUT,
632 CURLOPT_CUSTOMREQUEST,
633 CURLOPT_HEADER,
634 CURLOPT_RETURNTRANSFER,
635 CURLOPT_HTTPHEADER,
636 CURLOPT_POSTFIELDS,
637 CURLOPT_INFILE,
638 CURLOPT_INFILESIZE,
639 CURLOPT_PORT,
640 CURLOPT_MAXREDIRS,
641 CURLOPT_CONNECTTIMEOUT,
642 CURL_HTTP_VERSION_1_1,
643 CURL_HTTP_VERSION_1_0,
644 );
645 }
646
647 /**
648 * Set the configuration array for the adapter
649 *
650 * @throws Zend_Http_Client_Adapter_Exception
651 * @param Zend_Config | array $config
652 * @return Zend_Http_Client_Adapter_Curl
653 */
654 public function setConfig($config = array()) {
655 if ($config instanceof Zend_Config) {
656 $config = $config->toArray();
657
658 } elseif (! is_array($config)) {
659 if (!class_exists('Zend_Exception')) require 'exception.php';
660 throw new Zend_Http_Client_Adapter_Exception(
661 'Array or Zend_Config object expected, got ' . gettype($config)
662 );
663 }
664
665 if(isset($config['proxy_user']) && isset($config['proxy_pass'])) {
666 $this->setCurlOption(CURLOPT_PROXYUSERPWD, $config['proxy_user'].":".$config['proxy_pass']);
667 unset($config['proxy_user'], $config['proxy_pass']);
668 }
669
670 foreach ($config as $k => $v) {
671 $option = strtolower($k);
672 switch($option) {
673 case 'proxy_host':
674 $this->setCurlOption(CURLOPT_PROXY, $v);
675 break;
676 case 'proxy_port':
677 $this->setCurlOption(CURLOPT_PROXYPORT, $v);
678 break;
679 default:
680 $this->_config[$option] = $v;
681 break;
682 }
683 }
684
685 return $this;
686 }
687
688 /**
689 * Retrieve the array of all configuration options
690 *
691 * @return array
692 */
693 public function getConfig() {
694 return $this->_config;
695 }
696
697 /**
698 * Direct setter for cURL adapter related options.
699 *
700 * @param string|int $option
701 * @param mixed $value
702 * @return Zend_Http_Adapter_Curl
703 */
704 public function setCurlOption($option, $value) {
705 if (!isset($this->_config['curloptions'])) {
706 $this->_config['curloptions'] = array();
707 }
708 $this->_config['curloptions'][$option] = $value;
709 return $this;
710 }
711
712 /**
713 * Initialize curl
714 *
715 * @param string $host
716 * @param int $port
717 * @param boolean $secure
718 * @return void
719 * @throws Zend_Http_Client_Adapter_Exception if unable to connect
720 */
721 public function connect($host, $port = 80, $secure = false) {
722 // If we're already connected, disconnect first
723 if ($this->_curl) {
724 $this->close();
725 }
726
727 // If we are connected to a different server or port, disconnect first
728 if ($this->_curl
729 && is_array($this->_connected_to)
730 && ($this->_connected_to[0] != $host
731 || $this->_connected_to[1] != $port)
732 ) {
733 $this->close();
734 }
735
736 // Do the actual connection
737 $this->_curl = curl_init();
738 if ($port != 80) {
739 curl_setopt($this->_curl, CURLOPT_PORT, intval($port));
740 }
741
742 // Set timeout
743 curl_setopt($this->_curl, CURLOPT_CONNECTTIMEOUT, $this->_config['timeout']);
744
745 // Set Max redirects
746 curl_setopt($this->_curl, CURLOPT_MAXREDIRS, $this->_config['maxredirects']);
747
748 if (!$this->_curl) {
749 $this->close();
750
751 if (!class_exists('Zend_Exception')) require 'exception.php';
752 throw new Zend_Http_Client_Adapter_Exception('Unable to Connect to ' . $host . ':' . $port);
753 }
754
755 if ($secure !== false) {
756 // Behave the same like Zend_Http_Adapter_Socket on SSL options.
757 if (isset($this->_config['sslcert'])) {
758 curl_setopt($this->_curl, CURLOPT_SSLCERT, $this->_config['sslcert']);
759 }
760 if (isset($this->_config['sslpassphrase'])) {
761 curl_setopt($this->_curl, CURLOPT_SSLCERTPASSWD, $this->_config['sslpassphrase']);
762 }
763 }
764
765 // Update connected_to
766 $this->_connected_to = array($host, $port);
767 }
768
769 /**
770 * Send request to the remote server
771 *
772 * @param string $method
773 * @param Zend_Uri_Http $uri
774 * @param float $http_ver
775 * @param array $headers
776 * @param string $body
777 * @return string $request
778 * @throws Zend_Http_Client_Adapter_Exception If connection fails, connected to wrong host, no PUT file
defined,
779 * unsupported method, or unsupported cURL option
780 */
781 public function write($method, $uri, $httpVersion = 1.1, $headers = array(), $body = '') {
782 // Make sure we're properly connected
783 if (!$this->_curl) {
784 if (!class_exists('Zend_Exception')) require 'exception.php';
785 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
786 }
787
788 if ($this->_connected_to[0] != $uri->getHost() || $this->_connected_to[1] != $uri->getPort()) {
789 if (!class_exists('Zend_Exception')) require 'exception.php';
790 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong
host");
791 }
792
793 // set URL
794 curl_setopt($this->_curl, CURLOPT_URL, $uri->__toString());
795
796 // ensure correct curl call
797 $curlValue = true;
798 switch ($method) {
799 case Zend_Http_Client::GET:
800 $curlMethod = CURLOPT_HTTPGET;
801 break;
802
803 case Zend_Http_Client::POST:
804 $curlMethod = CURLOPT_POST;
805 break;
806
807 case Zend_Http_Client::PUT:
808 // There are two different types of PUT request, either a Raw Data string has been set
809 // or CURLOPT_INFILE and CURLOPT_INFILESIZE are used.
810 if(is_resource($body)) {
811 $this->_config['curloptions'][CURLOPT_INFILE] = $body;
812 }
813 if (isset($this->_config['curloptions'][CURLOPT_INFILE])) {
814 // Now we will probably already have Content-Length set, so that we have to delete it
815 // from $headers at this point:
816 foreach ($headers AS $k => $header) {
817 if (preg_match('/Content-Length:\s*(\d+)/i', $header, $m)) {
818 if(is_resource($body)) {
819 $this->_config['curloptions'][CURLOPT_INFILESIZE] = (int)$m[1];
820 }
821 unset($headers[$k]);
822 }
823 }
824
825 if (!isset($this->_config['curloptions'][CURLOPT_INFILESIZE])) {
826 if (!class_exists('Zend_Exception')) require 'exception.php';
827 throw new Zend_Http_Client_Adapter_Exception("Cannot set a file-handle for cURL option "
828 . "CURLOPT_INFILE without also setting its size in
CURLOPT_INFILESIZE.");
829 }
830
831 if(is_resource($body)) {
832 $body = '';
833 }
834
835 $curlMethod = CURLOPT_PUT;
836 } else {
837 $curlMethod = CURLOPT_CUSTOMREQUEST;
838 $curlValue = "PUT";
839 }
840 break;
841
842 case Zend_Http_Client::DELETE:
843 $curlMethod = CURLOPT_CUSTOMREQUEST;
844 $curlValue = "DELETE";
845 break;
846
847 case Zend_Http_Client::OPTIONS:
848 $curlMethod = CURLOPT_CUSTOMREQUEST;
849 $curlValue = "OPTIONS";
850 break;
851
852 case Zend_Http_Client::TRACE:
853 $curlMethod = CURLOPT_CUSTOMREQUEST;
854 $curlValue = "TRACE";
855 break;
856
857 case Zend_Http_Client::HEAD:
858 $curlMethod = CURLOPT_CUSTOMREQUEST;
859 $curlValue = "HEAD";
860 break;
861
862 default:
863 // For now, through an exception for unsupported request methods
864 if (!class_exists('Zend_Exception')) require 'exception.php';
865 throw new Zend_Http_Client_Adapter_Exception("Method currently not supported");
866 }
867
868 if(is_resource($body) && $curlMethod != CURLOPT_PUT) {
869 if (!class_exists('Zend_Exception')) require 'exception.php';
870 throw new Zend_Http_Client_Adapter_Exception("Streaming requests are allowed only with PUT");
871 }
872
873 // get http version to use
874 $curlHttp = ($httpVersion == 1.1) ? CURL_HTTP_VERSION_1_1 : CURL_HTTP_VERSION_1_0;
875
876 // mark as HTTP request and set HTTP method
877 curl_setopt($this->_curl, $curlHttp, true);
878 curl_setopt($this->_curl, $curlMethod, $curlValue);
879
880 if($this->out_stream) {
881 // headers will be read into the response
882 curl_setopt($this->_curl, CURLOPT_HEADER, false);
883 curl_setopt($this->_curl, CURLOPT_HEADERFUNCTION, array($this, "readHeader"));
884 // and data will be written into the file
885 curl_setopt($this->_curl, CURLOPT_FILE, $this->out_stream);
886 } else {
887 // ensure headers are also returned
888 curl_setopt($this->_curl, CURLOPT_HEADER, true);
889
890 // ensure actual response is returned
891 curl_setopt($this->_curl, CURLOPT_RETURNTRANSFER, true);
892 }
893
894 // set additional headers
895 $headers['Accept'] = '';
896 curl_setopt($this->_curl, CURLOPT_HTTPHEADER, $headers);
897
898 /**
899 * Make sure POSTFIELDS is set after $curlMethod is set:
900 * @link http://de2.php.net/manual/en/function.curl-setopt.php#81161
901 */
902 if ($method == Zend_Http_Client::POST) {
903 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
904 } elseif ($curlMethod == CURLOPT_PUT) {
905 // this covers a PUT by file-handle:
906 // Make the setting of this options explicit (rather than setting it through the loop following a bit
lower)
907 // to group common functionality together.
908 curl_setopt($this->_curl, CURLOPT_INFILE, $this->_config['curloptions'][CURLOPT_INFILE]);
909 curl_setopt($this->_curl, CURLOPT_INFILESIZE, $this->_config['curloptions'][CURLOPT_INFILESIZE]);
910 unset($this->_config['curloptions'][CURLOPT_INFILE]);
911 unset($this->_config['curloptions'][CURLOPT_INFILESIZE]);
912 } elseif ($method == Zend_Http_Client::PUT) {
913 // This is a PUT by a setRawData string, not by file-handle
914 curl_setopt($this->_curl, CURLOPT_POSTFIELDS, $body);
915 }
916
917 // set additional curl options
918 if (isset($this->_config['curloptions'])) {
919 foreach ((array)$this->_config['curloptions'] as $k => $v) {
920 if (!in_array($k, $this->_invalidOverwritableCurlOptions)) {
921 if (curl_setopt($this->_curl, $k, $v) == false) {
922 if (!class_exists('Zend_Exception')) require 'exception.php';
923 throw new Zend_Http_Client_Exception(sprintf("Unknown or erroreous cURL option '%s' set",
$k));
924 }
925 }
926 }
927 }
928
929 // send the request
930 $response = curl_exec($this->_curl);
931
932 // if we used streaming, headers are already there
933 if(!is_resource($this->out_stream)) {
934 $this->_response = $response;
935 }
936
937 $request = curl_getinfo($this->_curl, CURLINFO_HEADER_OUT);
938 $request .= $body;
939
940 if (empty($this->_response)) {
941 if (!class_exists('Zend_Exception')) require 'exception.php';
942 throw new Zend_Http_Client_Exception("Error in cURL request: " . curl_error($this->_curl));
943 }
944
945 // cURL automatically decodes chunked-messages, this means we have to disallow the Zend_Http_Response to
do it again
946 if (stripos($this->_response, "Transfer-Encoding: chunked\r\n")) {
947 $this->_response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $this->_response);
948 }
949
950 // Eliminate multiple HTTP responses.
951 do {
952 $parts = preg_split('|(?:\r?\n){2}|m', $this->_response, 2);
953 $again = false;
954
955 if (isset($parts[1]) && preg_match("|^HTTP/1\.[01](.*?)\r\n|mi", $parts[1])) {
956 $this->_response = $parts[1];
957 $again = true;
958 }
959 } while ($again);
960
961 // cURL automatically handles Proxy rewrites, remove the "HTTP/1.0 200 Connection established" string:
962 if (stripos($this->_response, "HTTP/1.0 200 Connection established\r\n\r\n") !== false) {
963 $this->_response = str_ireplace("HTTP/1.0 200 Connection established\r\n\r\n", '', $this->_response);
964 }
965
966 return $request;
967 }
968
969 /**
970 * Return read response from server
971 *
972 * @return string
973 */
974 public function read() {
975 return $this->_response;
976 }
977
978 /**
979 * Close the connection to the server
980 *
981 */
982 public function close() {
983 if(is_resource($this->_curl)) {
984 curl_close($this->_curl);
985 }
986 $this->_curl = null;
987 $this->_connected_to = array(null, null);
988 }
989
990 /**
991 * Get cUrl Handle
992 *
993 * @return resource
994 */
995 public function getHandle() {
996 return $this->_curl;
997 }
998
999 /**
1000 * Set output stream for the response
1001 *
1002 * @param resource $stream
1003 * @return Zend_Http_Client_Adapter_Socket
1004 */
1005 public function setOutputStream($stream) {
1006 $this->out_stream = $stream;
1007 return $this;
1008 }
1009
1010 /**
1011 * Header reader function for CURL
1012 *
1013 * @param resource $curl
1014 * @param string $header
1015 * @return int
1016 */
1017 public function readHeader($curl, $header) {
1018 $this->_response .= $header;
1019 return strlen($header);
1020 }
1021 }
1022
1023 /**
1024 * A sockets based (stream_socket_client) adapter class for Zend_Http_Client. Can be used
1025 * on almost every PHP environment, and does not require any special extensions.
1026 *
1027 * @category Zend
1028 * @package Zend_Http
1029 * @subpackage Client_Adapter
1030 * @version $Id: Socket.php 21778 2010-04-06 11:19:35Z shahar $
1031 * @version $Id: Exception.php 20096 2010-01-06 02:05:09Z bkarwin $
1032 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
1033 * @license http://framework.zend.com/license/new-bsd New BSD License
1034 */
1035 class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface,
Zend_Http_Client_Adapter_Stream {
1036 /**
1037 * The socket for server connection
1038 *
1039 * @var resource|null
1040 */
1041 protected $socket = null;
1042
1043 /**
1044 * What host/port are we connected to?
1045 *
1046 * @var array
1047 */
1048 protected $connected_to = array(null, null);
1049
1050 /**
1051 * Stream for storing output
1052 *
1053 * @var resource
1054 */
1055 protected $out_stream = null;
1056
1057 /**
1058 * Parameters array
1059 *
1060 * @var array
1061 */
1062 protected $config = array(
1063 'persistent' => false,
1064 'ssltransport' => 'ssl',
1065 'sslcert' => null,
1066 'sslpassphrase' => null,
1067 'sslusecontext' => false
1068 );
1069
1070 /**
1071 * Request method - will be set by write() and might be used by read()
1072 *
1073 * @var string
1074 */
1075 protected $method = null;
1076
1077 /**
1078 * Stream context
1079 *
1080 * @var resource
1081 */
1082 protected $_context = null;
1083
1084 /**
1085 * Adapter constructor, currently empty. Config is set using setConfig()
1086 *
1087 */
1088 public function __construct() {
1089 }
1090
1091 /**
1092 * Set the configuration array for the adapter
1093 *
1094 * @param Zend_Config | array $config
1095 */
1096 public function setConfig($config = array()) {
1097 if ($config instanceof Zend_Config) {
1098 $config = $config->toArray();
1099
1100 } elseif (! is_array($config)) {
1101 if (!class_exists('Zend_Exception')) require 'exception.php';
1102 throw new Zend_Http_Client_Adapter_Exception(
1103 'Array or Zend_Config object expected, got ' . gettype($config)
1104 );
1105 }
1106
1107 foreach ($config as $k => $v) {
1108 $this->config[strtolower($k)] = $v;
1109 }
1110 }
1111
1112 /**
1113 * Retrieve the array of all configuration options
1114 *
1115 * @return array
1116 */
1117 public function getConfig() {
1118 return $this->config;
1119 }
1120
1121 /**
1122 * Set the stream context for the TCP connection to the server
1123 *
1124 * Can accept either a pre-existing stream context resource, or an array
1125 * of stream options, similar to the options array passed to the
1126 * stream_context_create() PHP function. In such case a new stream context
1127 * will be created using the passed options.
1128 *
1129 * @since Zend Framework 1.9
1130 *
1131 * @param mixed $context Stream context or array of context options
1132 * @return Zend_Http_Client_Adapter_Socket
1133 */
1134 public function setStreamContext($context) {
1135 if (is_resource($context) && get_resource_type($context) == 'stream-context') {
1136 $this->_context = $context;
1137
1138 } elseif (is_array($context)) {
1139 $this->_context = stream_context_create($context);
1140
1141 } else {
1142 // Invalid parameter
1143 if (!class_exists('Zend_Exception')) require 'exception.php';
1144 throw new Zend_Http_Client_Adapter_Exception(
1145 "Expecting either a stream context resource or array, got " . gettype($context)
1146 );
1147 }
1148
1149 return $this;
1150 }
1151
1152 /**
1153 * Get the stream context for the TCP connection to the server.
1154 *
1155 * If no stream context is set, will create a default one.
1156 *
1157 * @return resource
1158 */
1159 public function getStreamContext() {
1160 if (! $this->_context) {
1161 $this->_context = stream_context_create();
1162 }
1163
1164 return $this->_context;
1165 }
1166
1167 /**
1168 * Connect to the remote server
1169 *
1170 * @param string $host
1171 * @param int $port
1172 * @param boolean $secure
1173 */
1174 public function connect($host, $port = 80, $secure = false) {
1175 // If the URI should be accessed via SSL, prepend the Hostname with ssl://
1176 $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host;
1177
1178 // If we are connected to the wrong host, disconnect first
1179 if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) {
1180 if (is_resource($this->socket)) $this->close();
1181 }
1182
1183 // Now, if we are not connected, connect
1184 if (! is_resource($this->socket) || ! $this->config['keepalive']) {
1185 $context = $this->getStreamContext();
1186 if ($secure || $this->config['sslusecontext']) {
1187 if ($this->config['sslcert'] !== null) {
1188 if (! stream_context_set_option($context, 'ssl', 'local_cert',
1189 $this->config['sslcert'])) {
1190 if (!class_exists('Zend_Exception')) require 'exception.php';
1191 throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option');
1192 }
1193 }
1194 if ($this->config['sslpassphrase'] !== null) {
1195 if (! stream_context_set_option($context, 'ssl', 'passphrase',
1196 $this->config['sslpassphrase'])) {
1197 if (!class_exists('Zend_Exception')) require 'exception.php';
1198 throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option');
1199 }
1200 }
1201 }
1202
1203 $flags = STREAM_CLIENT_CONNECT;
1204 if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT;
1205
1206 $this->socket = @stream_socket_client($host . ':' . $port,
1207 $errno,
1208 $errstr,
1209 (int) $this->config['timeout'],
1210 $flags,
1211 $context);
1212
1213 if (! $this->socket) {
1214 $this->close();
1215 if (!class_exists('Zend_Exception')) require 'exception.php';
1216 throw new Zend_Http_Client_Adapter_Exception(
1217 'Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr);
1218 }
1219
1220 // Set the stream timeout
1221 if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) {
1222 if (!class_exists('Zend_Exception')) require 'exception.php';
1223 throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout');
1224 }
1225
1226 // Update connected_to
1227 $this->connected_to = array($host, $port);
1228 }
1229 }
1230
1231 /**
1232 * Send request to the remote server
1233 *
1234 * @param string $method
1235 * @param Zend_Uri_Http $uri
1236 * @param string $http_ver
1237 * @param array $headers
1238 * @param string $body
1239 * @return string Request as string
1240 */
1241 public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') {
1242 // Make sure we're properly connected
1243 if (! $this->socket) {
1244 if (!class_exists('Zend_Exception')) require 'exception.php';
1245 throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected');
1246 }
1247
1248 $host = $uri->getHost();
1249 $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' .
$host;
1250 if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) {
1251 if (!class_exists('Zend_Exception')) require 'exception.php';
1252 throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong
host');
1253 }
1254
1255 // Save request method for later
1256 $this->method = $method;
1257
1258 // Build request headers
1259 $path = $uri->getPath();
1260 if ($uri->getQuery()) $path .= '?' . $uri->getQuery();
1261 $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
1262 foreach ($headers as $k => $v) {
1263 if (is_string($k)) $v = ucfirst($k) . ": $v";
1264 $request .= "$v\r\n";
1265 }
1266
1267 if(is_resource($body)) {
1268 $request .= "\r\n";
1269 } else {
1270 // Add the request body
1271 $request .= "\r\n" . $body;
1272 }
1273
1274 // Send the request
1275 if (! @fwrite($this->socket, $request)) {
1276 if (!class_exists('Zend_Exception')) require 'exception.php';
1277 throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
1278 }
1279
1280 if(is_resource($body)) {
1281 if(stream_copy_to_stream($body, $this->socket) == 0) {
1282 if (!class_exists('Zend_Exception')) require 'exception.php';
1283 throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
1284 }
1285 }
1286
1287 return $request;
1288 }
1289
1290 /**
1291 * Read response from server
1292 *
1293 * @return string
1294 */
1295 public function read() {
1296 // First, read headers only
1297 $response = '';
1298 $gotStatus = false;
1299 $stream = !empty($this->config['stream']);
1300
1301 while (($line = @fgets($this->socket)) !== false) {
1302 $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
1303 if ($gotStatus) {
1304 $response .= $line;
1305 if (rtrim($line) === '') break;
1306 }
1307 }
1308
1309 $this->_checkSocketReadTimeout();
1310
1311 $statusCode = Zend_Http_Response::extractCode($response);
1312
1313 // Handle 100 and 101 responses internally by restarting the read again
1314 if ($statusCode == 100 || $statusCode == 101) return $this->read();
1315
1316 // Check headers to see what kind of connection / transfer encoding we have
1317 $headers = Zend_Http_Response::extractHeaders($response);
1318
1319 /**
1320 * Responses to HEAD requests and 204 or 304 responses are not expected
1321 * to have a body - stop reading here
1322 */
1323 if ($statusCode == 304 || $statusCode == 204 ||
1324 $this->method == Zend_Http_Client::HEAD) {
1325
1326 // Close the connection if requested to do so by the server
1327 if (isset($headers['connection']) && $headers['connection'] == 'close') {
1328 $this->close();
1329 }
1330 return $response;
1331 }
1332
1333 // If we got a 'transfer-encoding: chunked' header
1334 if (isset($headers['transfer-encoding'])) {
1335
1336 if (strtolower($headers['transfer-encoding']) == 'chunked') {
1337
1338 do {
1339 $line = @fgets($this->socket);
1340 $this->_checkSocketReadTimeout();
1341
1342 $chunk = $line;
1343
1344 // Figure out the next chunk size
1345 $chunksize = trim($line);
1346 if (! ctype_xdigit($chunksize)) {
1347 $this->close();
1348 if (!class_exists('Zend_Exception')) require 'exception.php';
1349 throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' .
1350 $chunksize . '" unable to read chunked body');
1351 }
1352
1353 // Convert the hexadecimal value to plain integer
1354 $chunksize = hexdec($chunksize);
1355
1356 // Read next chunk
1357 $read_to = ftell($this->socket) + $chunksize;
1358
1359 do {
1360 $current_pos = ftell($this->socket);
1361 if ($current_pos >= $read_to) break;
1362
1363 if($this->out_stream) {
1364 if(stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) ==
0) {
1365 $this->_checkSocketReadTimeout();
1366 break;
1367 }
1368 } else {
1369 $line = @fread($this->socket, $read_to - $current_pos);
1370 if ($line === false || strlen($line) === 0) {
1371 $this->_checkSocketReadTimeout();
1372 break;
1373 }
1374 $chunk .= $line;
1375 }
1376 } while (! feof($this->socket));
1377
1378 $chunk .= @fgets($this->socket);
1379 $this->_checkSocketReadTimeout();
1380
1381 if(!$this->out_stream) {
1382 $response .= $chunk;
1383 }
1384 } while ($chunksize > 0);
1385 } else {
1386 $this->close();
1387 throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' .
1388 $headers['transfer-encoding'] . '" transfer encoding');
1389 }
1390
1391 // We automatically decode chunked-messages when writing to a stream
1392 // this means we have to disallow the Zend_Http_Response to do it again
1393 if ($this->out_stream) {
1394 $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response);
1395 }
1396 // Else, if we got the content-length header, read this number of bytes
1397 } elseif (isset($headers['content-length'])) {
1398
1399 // If we got more than one Content-Length header (see ZF-9404) use
1400 // the last value sent
1401 if (is_array($headers['content-length'])) {
1402 $contentLength = $headers['content-length'][count($headers['content-length']) - 1];
1403 } else {
1404 $contentLength = $headers['content-length'];
1405 }
1406
1407 $current_pos = ftell($this->socket);
1408 $chunk = '';
1409
1410 for ($read_to = $current_pos + $contentLength;
1411 $read_to > $current_pos;
1412 $current_pos = ftell($this->socket)) {
1413
1414 if($this->out_stream) {
1415 if(@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
1416 $this->_checkSocketReadTimeout();
1417 break;
1418 }
1419 } else {
1420 $chunk = @fread($this->socket, $read_to - $current_pos);
1421 if ($chunk === false || strlen($chunk) === 0) {
1422 $this->_checkSocketReadTimeout();
1423 break;
1424 }
1425
1426 $response .= $chunk;
1427 }
1428
1429 // Break if the connection ended prematurely
1430 if (feof($this->socket)) break;
1431 }
1432
1433 // Fallback: just read the response until EOF
1434 } else {
1435
1436 do {
1437 if($this->out_stream) {
1438 if(@stream_copy_to_stream($this->socket, $this->out_stream) == 0) {
1439 $this->_checkSocketReadTimeout();
1440 break;
1441 }
1442 } else {
1443 $buff = @fread($this->socket, 8192);
1444 if ($buff === false || strlen($buff) === 0) {
1445 $this->_checkSocketReadTimeout();
1446 break;
1447 } else {
1448 $response .= $buff;
1449 }
1450 }
1451
1452 } while (feof($this->socket) === false);
1453
1454 $this->close();
1455 }
1456
1457 // Close the connection if requested to do so by the server
1458 if (isset($headers['connection']) && $headers['connection'] == 'close') {
1459 $this->close();
1460 }
1461
1462 return $response;
1463 }
1464
1465 /**
1466 * Close the connection to the server
1467 *
1468 */
1469 public function close() {
1470 if (is_resource($this->socket)) @fclose($this->socket);
1471 $this->socket = null;
1472 $this->connected_to = array(null, null);
1473 }
1474
1475 /**
1476 * Check if the socket has timed out - if so close connection and throw
1477 * an exception
1478 *
1479 * @throws Zend_Http_Client_Adapter_Exception with READ_TIMEOUT code
1480 */
1481 protected function _checkSocketReadTimeout() {
1482 if ($this->socket) {
1483 $info = stream_get_meta_data($this->socket);
1484 $timedout = $info['timed_out'];
1485 if ($timedout) {
1486 $this->close();
1487 if (!class_exists('Zend_Exception')) require 'exception.php';
1488 throw new Zend_Http_Client_Adapter_Exception(
1489 "Read timed out after {$this->config['timeout']} seconds",
1490 Zend_Http_Client_Adapter_Exception::READ_TIMEOUT
1491 );
1492 }
1493 }
1494 }
1495
1496 /**
1497 * Set output stream for the response
1498 *
1499 * @param resource $stream
1500 * @return Zend_Http_Client_Adapter_Socket
1501 */
1502 public function setOutputStream($stream) {
1503 $this->out_stream = $stream;
1504 return $this;
1505 }
1506
1507 /**
1508 * Destructor: make sure the socket is disconnected
1509 *
1510 * If we are in persistent TCP mode, will not close the connection
1511 *
1512 */
1513 public function __destruct() {
1514 if (! $this->config['persistent']) {
1515 if ($this->socket) $this->close();
1516 }
1517 }
1518 }
1519
1520 /**
1521 * An interface description for Zend_Http_Client_Adapter_Stream classes.
1522 *
1523 * This interface decribes Zend_Http_Client_Adapter which supports streaming.
1524 *
1525 * @category Zend
1526 * @package Zend_Http
1527 * @subpackage Client_Adapter
1528 * @version $Id: Interface.php 16214 2009-06-21 19:34:03Z thomas $
1529 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
1530 * @license http://framework.zend.com/license/new-bsd New BSD License
1531 */
1532 interface Zend_Http_Client_Adapter_Stream {
1533 /**
1534 * Set output stream
1535 *
1536 * This function sets output stream where the result will be stored.
1537 *
1538 * @param resource $stream Stream to write the output to
1539 *
1540 */
1541 function setOutputStream($stream);
1542 }
1543
1544 /**
1545 * HTTP Proxy-supporting Zend_Http_Client adapter class, based on the default
1546 * socket based adapter.
1547 *
1548 * Should be used if proxy HTTP access is required. If no proxy is set, will
1549 * fall back to Zend_Http_Client_Adapter_Socket behavior. Just like the
1550 * default Socket adapter, this adapter does not require any special extensions
1551 * installed.
1552 *
1553 * @category Zend
1554 * @package Zend_Http
1555 * @subpackage Client_Adapter
1556 * @version $Id: Proxy.php 22445 2010-06-16 09:09:12Z bate $
1557 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
1558 * @license http://framework.zend.com/license/new-bsd New BSD License
1559 */
1560 class Zend_Http_Client_Adapter_Proxy extends Zend_Http_Client_Adapter_Socket {
1561 /**
1562 * Parameters array
1563 *
1564 * @var array
1565 */
1566 protected $config = array(
1567 'ssltransport' => 'ssl',
1568 'sslcert' => null,
1569 'sslpassphrase' => null,
1570 'sslusecontext' => false,
1571 'proxy_host' => '',
1572 'proxy_port' => 8080,
1573 'proxy_user' => '',
1574 'proxy_pass' => '',
1575 'proxy_auth' => Zend_Http_Client::AUTH_BASIC,
1576 'persistent' => false
1577 );
1578
1579 /**
1580 * Whether HTTPS CONNECT was already negotiated with the proxy or not
1581 *
1582 * @var boolean
1583 */
1584 protected $negotiated = false;
1585
1586 /**
1587 * Connect to the remote server
1588 *
1589 * Will try to connect to the proxy server. If no proxy was set, will
1590 * fall back to the target server (behave like regular Socket adapter)
1591 *
1592 * @param string $host
1593 * @param int $port
1594 * @param boolean $secure
1595 */
1596 public function connect($host, $port = 80, $secure = false) {
1597 // If no proxy is set, fall back to Socket adapter
1598 if (! $this->config['proxy_host']) {
1599 return parent::connect($host, $port, $secure);
1600 }
1601
1602 /* Url might require stream context even if proxy connection doesn't */
1603 if ($secure) {
1604 $this->config['sslusecontext'] = true;
1605 }
1606
1607 // Connect (a non-secure connection) to the proxy server
1608 return parent::connect(
1609 $this->config['proxy_host'],
1610 $this->config['proxy_port'],
1611 false
1612 );
1613 }
1614
1615 /**
1616 * Send request to the proxy server
1617 *
1618 * @param string $method
1619 * @param Zend_Uri_Http $uri
1620 * @param string $http_ver
1621 * @param array $headers
1622 * @param string $body
1623 * @return string Request as string
1624 */
1625 public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '') {
1626 // If no proxy is set, fall back to default Socket adapter
1627 if (! $this->config['proxy_host']) return parent::write($method, $uri, $http_ver, $headers, $body);
1628
1629 // Make sure we're properly connected
1630 if (! $this->socket) {
1631 if (!class_exists('Zend_Exception')) require 'exception.php';
1632 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are not connected");
1633 }
1634
1635 $host = $this->config['proxy_host'];
1636 $port = $this->config['proxy_port'];
1637
1638 if ($this->connected_to[0] != "tcp://$host" || $this->connected_to[1] != $port) {
1639 if (!class_exists('Zend_Exception')) require 'exception.php';
1640 throw new Zend_Http_Client_Adapter_Exception("Trying to write but we are connected to the wrong proxy
server");
1641 }
1642
1643 // Add Proxy-Authorization header
1644 if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) {
1645 $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader(
1646 $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth']
1647 );
1648 }
1649
1650 // if we are proxying HTTPS, preform CONNECT handshake with the proxy
1651 if ($uri->getScheme() == 'https' && (! $this->negotiated)) {
1652 $this->connectHandshake($uri->getHost(), $uri->getPort(), $http_ver, $headers);
1653 $this->negotiated = true;
1654 }
1655
1656 // Save request method for later
1657 $this->method = $method;
1658
1659 // Build request headers
1660 if ($this->negotiated) {
1661 $path = $uri->getPath();
1662 if ($uri->getQuery()) {
1663 $path .= '?' . $uri->getQuery();
1664 }
1665 $request = "$method $path HTTP/$http_ver\r\n";
1666 } else {
1667 $request = "$method $uri HTTP/$http_ver\r\n";
1668 }
1669
1670 // Add all headers to the request string
1671 foreach ($headers as $k => $v) {
1672 if (is_string($k)) $v = "$k: $v";
1673 $request .= "$v\r\n";
1674 }
1675
1676 if(is_resource($body)) {
1677 $request .= "\r\n";
1678 } else {
1679 // Add the request body
1680 $request .= "\r\n" . $body;
1681 }
1682
1683 // Send the request
1684 if (! @fwrite($this->socket, $request)) {
1685 if (!class_exists('Zend_Exception')) require 'exception.php';
1686 throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
1687 }
1688
1689 if(is_resource($body)) {
1690 if(stream_copy_to_stream($body, $this->socket) == 0) {
1691 if (!class_exists('Zend_Exception')) require 'exception.php';
1692 throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
1693 }
1694 }
1695
1696 return $request;
1697 }
1698
1699 /**
1700 * Preform handshaking with HTTPS proxy using CONNECT method
1701 *
1702 * @param string $host
1703 * @param integer $port
1704 * @param string $http_ver
1705 * @param array $headers
1706 */
1707 protected function connectHandshake($host, $port = 443, $http_ver = '1.1', array &$headers = array()) {
1708 $request = "CONNECT $host:$port HTTP/$http_ver\r\n" .
1709 "Host: " . $this->config['proxy_host'] . "\r\n";
1710
1711 // Add the user-agent header
1712 if (isset($this->config['useragent'])) {
1713 $request .= "User-agent: " . $this->config['useragent'] . "\r\n";
1714 }
1715
1716 // If the proxy-authorization header is set, send it to proxy but remove
1717 // it from headers sent to target host
1718 if (isset($headers['proxy-authorization'])) {
1719 $request .= "Proxy-authorization: " . $headers['proxy-authorization'] . "\r\n";
1720 unset($headers['proxy-authorization']);
1721 }
1722
1723 $request .= "\r\n";
1724
1725 // Send the request
1726 if (! @fwrite($this->socket, $request)) {
1727 if (!class_exists('Zend_Exception')) require 'exception.php';
1728 throw new Zend_Http_Client_Adapter_Exception("Error writing request to proxy server");
1729 }
1730
1731 // Read response headers only
1732 $response = '';
1733 $gotStatus = false;
1734 while ($line = @fgets($this->socket)) {
1735 $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
1736 if ($gotStatus) {
1737 $response .= $line;
1738 if (!chop($line)) break;
1739 }
1740 }
1741
1742 // Check that the response from the proxy is 200
1743 if (Zend_Http_Response::extractCode($response) != 200) {
1744 if (!class_exists('Zend_Exception')) require 'exception.php';
1745 throw new Zend_Http_Client_Adapter_Exception("Unable to connect to HTTPS proxy. Server response: " .
$response);
1746 }
1747
1748 // If all is good, switch socket to secure mode. We have to fall back
1749 // through the different modes
1750 $modes = array(
1751 STREAM_CRYPTO_METHOD_TLS_CLIENT,
1752 STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
1753 STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
1754 STREAM_CRYPTO_METHOD_SSLv2_CLIENT
1755 );
1756
1757 $success = false;
1758 foreach($modes as $mode) {
1759 $success = stream_socket_enable_crypto($this->socket, true, $mode);
1760 if ($success) break;
1761 }
1762
1763 if (! $success) {
1764 if (!class_exists('Zend_Exception')) require 'exception.php';
1765 throw new Zend_Http_Client_Adapter_Exception("Unable to connect to" .
1766 " HTTPS server through proxy: could not negotiate secure connection.");
1767 }
1768 }
1769
1770 /**
1771 * Close the connection to the server
1772 *
1773 */
1774 public function close() {
1775 parent::close();
1776 $this->negotiated = false;
1777 }
1778
1779 /**
1780 * Destructor: make sure the socket is disconnected
1781 *
1782 */
1783 public function __destruct() {
1784 if ($this->socket) $this->close();
1785 }
1786 }
1787
1788 /**
1789 * An interface description for Zend_Http_Client_Adapter classes.
1790 *
1791 * These classes are used as connectors for Zend_Http_Client, performing the
1792 * tasks of connecting, writing, reading and closing connection to the server.
1793 *
1794 * @category Zend
1795 * @package Zend_Http
1796 * @subpackage Client_Adapter
1797 * @version $Id: Interface.php 20096 2010-01-06 02:05:09Z bkarwin $
1798 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
1799 * @license http://framework.zend.com/license/new-bsd New BSD License
1800 */
1801 interface Zend_Http_Client_Adapter_Interface {
1802 /**
1803 * Set the configuration array for the adapter
1804 *
1805 * @param array $config
1806 */
1807 public function setConfig($config = array());
1808
1809 /**
1810 * Connect to the remote server
1811 *
1812 * @param string $host
1813 * @param int $port
1814 * @param boolean $secure
1815 */
1816 public function connect($host, $port = 80, $secure = false);
1817
1818 /**
1819 * Send request to the remote server
1820 *
1821 * @param string $method
1822 * @param Zend_Uri_Http $url
1823 * @param string $http_ver
1824 * @param array $headers
1825 * @param string $body
1826 * @return string Request as text
1827 */
1828 public function write($method, $url, $http_ver = '1.1', $headers = array(), $body = '');
1829
1830 /**
1831 * Read response from server
1832 *
1833 * @return string
1834 */
1835 public function read();
1836
1837 /**
1838 * Close the connection to the server
1839 *
1840 */
1841 public function close();
1842 }
1843
1844 /**
1845 * Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
1846 * includes easy access to all the response's different elemts, as well as some
1847 * convenience methods for parsing and validating HTTP responses.
1848 *
1849 * @package Zend_Http
1850 * @subpackage Response
1851 * @version $Id: Response.php 20096 2010-01-06 02:05:09Z bkarwin $
1852 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
1853 * @license http://framework.zend.com/license/new-bsd New BSD License
1854 */
1855 class Zend_Http_Response {
1856 /**
1857 * List of all known HTTP response codes - used by responseCodeAsText() to
1858 * translate numeric codes to messages.
1859 *
1860 * @var array
1861 */
1862 protected static $messages = array(
1863 // Informational 1xx
1864 100 => 'Continue',
1865 101 => 'Switching Protocols',
1866
1867 // Success 2xx
1868 200 => 'OK',
1869 201 => 'Created',
1870 202 => 'Accepted',
1871 203 => 'Non-Authoritative Information',
1872 204 => 'No Content',
1873 205 => 'Reset Content',
1874 206 => 'Partial Content',
1875
1876 // Redirection 3xx
1877 300 => 'Multiple Choices',
1878 301 => 'Moved Permanently',
1879 302 => 'Found', // 1.1
1880 303 => 'See Other',
1881 304 => 'Not Modified',
1882 305 => 'Use Proxy',
1883 // 306 is deprecated but reserved
1884 307 => 'Temporary Redirect',
1885
1886 // Client Error 4xx
1887 400 => 'Bad Request',
1888 401 => 'Unauthorized',
1889 402 => 'Payment Required',
1890 403 => 'Forbidden',
1891 404 => 'Not Found',
1892 405 => 'Method Not Allowed',
1893 406 => 'Not Acceptable',
1894 407 => 'Proxy Authentication Required',
1895 408 => 'Request Timeout',
1896 409 => 'Conflict',
1897 410 => 'Gone',
1898 411 => 'Length Required',
1899 412 => 'Precondition Failed',
1900 413 => 'Request Entity Too Large',
1901 414 => 'Request-URI Too Long',
1902 415 => 'Unsupported Media Type',
1903 416 => 'Requested Range Not Satisfiable',
1904 417 => 'Expectation Failed',
1905
1906 // Server Error 5xx
1907 500 => 'Internal Server Error',
1908 501 => 'Not Implemented',
1909 502 => 'Bad Gateway',
1910 503 => 'Service Unavailable',
1911 504 => 'Gateway Timeout',
1912 505 => 'HTTP Version Not Supported',
1913 509 => 'Bandwidth Limit Exceeded'
1914 );
1915
1916 /**
1917 * The HTTP version (1.0, 1.1)
1918 *
1919 * @var string
1920 */
1921 protected $version;
1922
1923 /**
1924 * The HTTP response code
1925 *
1926 * @var int
1927 */
1928 protected $code;
1929
1930 /**
1931 * The HTTP response code as string
1932 * (e.g. 'Not Found' for 404 or 'Internal Server Error' for 500)
1933 *
1934 * @var string
1935 */
1936 protected $message;
1937
1938 /**
1939 * The HTTP response headers array
1940 *
1941 * @var array
1942 */
1943 protected $headers = array();
1944
1945 /**
1946 * The HTTP response body
1947 *
1948 * @var string
1949 */
1950 protected $body;
1951
1952 /**
1953 * HTTP response constructor
1954 *
1955 * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
1956 * response string and create a new Zend_Http_Response object.
1957 *
1958 * NOTE: The constructor no longer accepts nulls or empty values for the code and
1959 * headers and will throw an exception if the passed values do not form a valid HTTP
1960 * responses.
1961 *
1962 * If no message is passed, the message will be guessed according to the response code.
1963 *
1964 * @param int $code Response code (200, 404, ...)
1965 * @param array $headers Headers array
1966 * @param string $body Response body
1967 * @param string $version HTTP version
1968 * @param string $message Response code as text
1969 * @throws Zend_Http_Exception
1970 */
1971 public function __construct($code, $headers, $body = null, $version = '1.1', $message = null) {
1972 // Make sure the response code is valid and set it
1973 if (self::responseCodeAsText($code) === null) {
1974 if (!class_exists('Zend_Exception')) require 'exception.php';
1975 throw new Zend_Http_Exception("{$code} is not a valid HTTP response code");
1976 }
1977
1978 $this->code = $code;
1979
1980 // Make sure we got valid headers and set them
1981 if (! is_array($headers)) {
1982 if (!class_exists('Zend_Exception')) require 'exception.php';
1983 throw new Zend_Http_Exception('No valid headers were passed');
1984 }
1985
1986 foreach ($headers as $name => $value) {
1987 if (is_int($name))
1988 list($name, $value) = explode(": ", $value, 1);
1989
1990 $this->headers[ucwords(strtolower($name))] = $value;
1991 }
1992
1993 // Set the body
1994 $this->body = $body;
1995
1996 // Set the HTTP version
1997 if (! preg_match('|^\d\.\d$|', $version)) {
1998 if (!class_exists('Zend_Exception')) require 'exception.php';
1999 throw new Zend_Http_Exception("Invalid HTTP response version: $version");
2000 }
2001
2002 $this->version = $version;
2003
2004 // If we got the response message, set it. Else, set it according to
2005 // the response code
2006 if (is_string($message)) {
2007 $this->message = $message;
2008 } else {
2009 $this->message = self::responseCodeAsText($code);
2010 }
2011 }
2012
2013 /**
2014 * Check whether the response is an error
2015 *
2016 * @return boolean
2017 */
2018 public function isError() {
2019 $restype = floor($this->code / 100);
2020 if ($restype == 4 || $restype == 5) {
2021 return true;
2022 }
2023
2024 return false;
2025 }
2026
2027 /**
2028 * Check whether the response in successful
2029 *
2030 * @return boolean
2031 */
2032 public function isSuccessful() {
2033 $restype = floor($this->code / 100);
2034 if ($restype == 2 || $restype == 1) { // Shouldn't 3xx count as success as well ???
2035 return true;
2036 }
2037
2038 return false;
2039 }
2040
2041 /**
2042 * Check whether the response is a redirection
2043 *
2044 * @return boolean
2045 */
2046 public function isRedirect() {
2047 $restype = floor($this->code / 100);
2048 if ($restype == 3) {
2049 return true;
2050 }
2051
2052 return false;
2053 }
2054
2055 /**
2056 * Get the response body as string
2057 *
2058 * This method returns the body of the HTTP response (the content), as it
2059 * should be in it's readable version - that is, after decoding it (if it
2060 * was decoded), deflating it (if it was gzip compressed), etc.
2061 *
2062 * If you want to get the raw body (as transfered on wire) use
2063 * $this->getRawBody() instead.
2064 *
2065 * @return string
2066 */
2067 public function getBody() {
2068 $body = '';
2069
2070 // Decode the body if it was transfer-encoded
2071 switch (strtolower($this->getHeader('transfer-encoding'))) {
2072
2073 // Handle chunked body
2074 case 'chunked':
2075 $body = self::decodeChunkedBody($this->body);
2076 break;
2077
2078 // No transfer encoding, or unknown encoding extension:
2079 // return body as is
2080 default:
2081 $body = $this->body;
2082 break;
2083 }
2084
2085 // Decode any content-encoding (gzip or deflate) if needed
2086 switch (strtolower($this->getHeader('content-encoding'))) {
2087
2088 // Handle gzip encoding
2089 case 'gzip':
2090 $body = self::decodeGzip($body);
2091 break;
2092
2093 // Handle deflate encoding
2094 case 'deflate':
2095 $body = self::decodeDeflate($body);
2096 break;
2097
2098 default:
2099 break;
2100 }
2101
2102 return $body;
2103 }
2104
2105 /**
2106 * Get the raw response body (as transfered "on wire") as string
2107 *
2108 * If the body is encoded (with Transfer-Encoding, not content-encoding -
2109 * IE "chunked" body), gzip compressed, etc. it will not be decoded.
2110 *
2111 * @return string
2112 */
2113 public function getRawBody() {
2114 return $this->body;
2115 }
2116
2117 /**
2118 * Get the HTTP version of the response
2119 *
2120 * @return string
2121 */
2122 public function getVersion() {
2123 return $this->version;
2124 }
2125
2126 /**
2127 * Get the HTTP response status code
2128 *
2129 * @return int
2130 */
2131 public function getStatus() {
2132 return $this->code;
2133 }
2134
2135 /**
2136 * Return a message describing the HTTP response code
2137 * (Eg. "OK", "Not Found", "Moved Permanently")
2138 *
2139 * @return string
2140 */
2141 public function getMessage() {
2142 return $this->message;
2143 }
2144
2145 /**
2146 * Get the response headers
2147 *
2148 * @return array
2149 */
2150 public function getHeaders() {
2151 return $this->headers;
2152 }
2153
2154 /**
2155 * Get a specific header as string, or null if it is not set
2156 *
2157 * @param string$header
2158 * @return string|array|null
2159 */
2160 public function getHeader($header) {
2161 $header = ucwords(strtolower($header));
2162 if (! is_string($header) || ! isset($this->headers[$header])) return null;
2163
2164 return $this->headers[$header];
2165 }
2166
2167 /**
2168 * Get all headers as string
2169 *
2170 * @param boolean $status_line Whether to return the first status line (IE "HTTP 200 OK")
2171 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
2172 * @return string
2173 */
2174 public function getHeadersAsString($status_line = true, $br = "\n") {
2175 $str = '';
2176
2177 if ($status_line) {
2178 $str = "HTTP/{$this->version} {$this->code} {$this->message}{$br}";
2179 }
2180
2181 // Iterate over the headers and stringify them
2182 foreach ($this->headers as $name => $value) {
2183 if (is_string($value))
2184 $str .= "{$name}: {$value}{$br}";
2185
2186 elseif (is_array($value)) {
2187 foreach ($value as $subval) {
2188 $str .= "{$name}: {$subval}{$br}";
2189 }
2190 }
2191 }
2192
2193 return $str;
2194 }
2195
2196 /**
2197 * Get the entire response as string
2198 *
2199 * @param string $br Line breaks (eg. "\n", "\r\n", "<br />")
2200 * @return string
2201 */
2202 public function asString($br = "\n") {
2203 return $this->getHeadersAsString(true, $br) . $br . $this->getRawBody();
2204 }
2205
2206 /**
2207 * Implements magic __toString()
2208 *
2209 * @return string
2210 */
2211 public function __toString() {
2212 return $this->asString();
2213 }
2214
2215 /**
2216 * A convenience function that returns a text representation of
2217 * HTTP response codes. Returns 'Unknown' for unknown codes.
2218 * Returns array of all codes, if $code is not specified.
2219 *
2220 * Conforms to HTTP/1.1 as defined in RFC 2616 (except for 'Unknown')
2221 * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10 for reference
2222 *
2223 * @param int $code HTTP response code
2224 * @param boolean $http11 Use HTTP version 1.1
2225 * @return string
2226 */
2227 public static function responseCodeAsText($code = null, $http11 = true) {
2228 $messages = self::$messages;
2229 if (! $http11) $messages[302] = 'Moved Temporarily';
2230
2231 if ($code === null) {
2232 return $messages;
2233 } elseif (isset($messages[$code])) {
2234 return $messages[$code];
2235 } else {
2236 return 'Unknown';
2237 }
2238 }
2239
2240 /**
2241 * Extract the response code from a response string
2242 *
2243 * @param string $response_str
2244 * @return int
2245 */
2246 public static function extractCode($response_str) {
2247 preg_match("|^HTTP/[\d\.x]+ (\d+)|", $response_str, $m);
2248
2249 if (isset($m[1])) {
2250 return (int) $m[1];
2251 } else {
2252 return false;
2253 }
2254 }
2255
2256 /**
2257 * Extract the HTTP message from a response
2258 *
2259 * @param string $response_str
2260 * @return string
2261 */
2262 public static function extractMessage($response_str) {
2263 preg_match("|^HTTP/[\d\.x]+ \d+ ([^\r\n]+)|", $response_str, $m);
2264
2265 if (isset($m[1])) {
2266 return $m[1];
2267 } else {
2268 return false;
2269 }
2270 }
2271
2272 /**
2273 * Extract the HTTP version from a response
2274 *
2275 * @param string $response_str
2276 * @return string
2277 */
2278 public static function extractVersion($response_str) {
2279 preg_match("|^HTTP/([\d\.x]+) \d+|", $response_str, $m);
2280
2281 if (isset($m[1])) {
2282 return $m[1];
2283 } else {
2284 return false;
2285 }
2286 }
2287
2288 /**
2289 * Extract the headers from a response string
2290 *
2291 * @param string $response_str
2292 * @return array
2293 */
2294 public static function extractHeaders($response_str) {
2295 $headers = array();
2296
2297 // First, split body and headers
2298 $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
2299 if (! $parts[0]) return $headers;
2300
2301 // Split headers part to lines
2302 $lines = explode("\n", $parts[0]);
2303 unset($parts);
2304 $last_header = null;
2305
2306 foreach($lines as $line) {
2307 $line = trim($line, "\r\n");
2308 if ($line == "") break;
2309
2310 if (preg_match("|^([\w-]+):\s+(.+)|", $line, $m)) {
2311 unset($last_header);
2312 $h_name = strtolower($m[1]);
2313 $h_value = $m[2];
2314
2315 if (isset($headers[$h_name])) {
2316 if (! is_array($headers[$h_name])) {
2317 $headers[$h_name] = array($headers[$h_name]);
2318 }
2319
2320 $headers[$h_name][] = $h_value;
2321 } else {
2322 $headers[$h_name] = $h_value;
2323 }
2324 $last_header = $h_name;
2325 } elseif (preg_match("|^\s+(.+)$|", $line, $m) && $last_header !== null) {
2326 if (is_array($headers[$last_header])) {
2327 end($headers[$last_header]);
2328 $last_header_key = key($headers[$last_header]);
2329 $headers[$last_header][$last_header_key] .= $m[1];
2330 } else {
2331 $headers[$last_header] .= $m[1];
2332 }
2333 }
2334 }
2335
2336 return $headers;
2337 }
2338
2339 /**
2340 * Extract the body from a response string
2341 *
2342 * @param string $response_str
2343 * @return string
2344 */
2345 public static function extractBody($response_str) {
2346 $parts = preg_split('|(?:\r?\n){2}|m', $response_str, 2);
2347 if (isset($parts[1])) {
2348 return $parts[1];
2349 }
2350 return '';
2351 }
2352
2353 /**
2354 * Decode a "chunked" transfer-encoded body and return the decoded text
2355 *
2356 * @param string $body
2357 * @return string
2358 */
2359 public static function decodeChunkedBody($body) {
2360 $decBody = '';
2361
2362 // If mbstring overloads substr and strlen functions, we have to
2363 // override it's internal encoding
2364 if (function_exists('mb_internal_encoding') &&
2365 ((int) ini_get('mbstring.func_overload')) & 2) {
2366
2367 $mbIntEnc = mb_internal_encoding();
2368 mb_internal_encoding('ASCII');
2369 }
2370
2371 while (trim($body)) {
2372 if (! preg_match("/^([\da-fA-F]+)[^\r\n]*\r\n/sm", $body, $m)) {
2373 if (!class_exists('Zend_Exception')) require 'exception.php';
2374 throw new Zend_Http_Exception("Error parsing body - doesn't seem to be a chunked message");
2375 }
2376
2377 $length = hexdec(trim($m[1]));
2378 $cut = strlen($m[0]);
2379 $decBody .= substr($body, $cut, $length);
2380 $body = substr($body, $cut + $length + 2);
2381 }
2382
2383 if (isset($mbIntEnc)) {
2384 mb_internal_encoding($mbIntEnc);
2385 }
2386
2387 return $decBody;
2388 }
2389
2390 /**
2391 * Decode a gzip encoded message (when Content-encoding = gzip)
2392 *
2393 * Currently requires PHP with zlib support
2394 *
2395 * @param string $body
2396 * @return string
2397 */
2398 public static function decodeGzip($body) {
2399 if (! function_exists('gzinflate')) {
2400 if (!class_exists('Zend_Exception')) require 'exception.php';
2401 throw new Zend_Http_Exception(
2402 'zlib extension is required in order to decode "gzip" encoding'
2403 );
2404 }
2405
2406 return gzinflate(substr($body, 10));
2407 }
2408
2409 /**
2410 * Decode a zlib deflated message (when Content-encoding = deflate)
2411 *
2412 * Currently requires PHP with zlib support
2413 *
2414 * @param string $body
2415 * @return string
2416 */
2417 public static function decodeDeflate($body) {
2418 if (! function_exists('gzuncompress')) {
2419 if (!class_exists('Zend_Exception')) require 'exception.php';
2420 throw new Zend_Http_Exception(
2421 'zlib extension is required in order to decode "deflate" encoding'
2422 );
2423 }
2424
2425 /**
2426 * Some servers (IIS ?) send a broken deflate response, without the
2427 * RFC-required zlib header.
2428 *
2429 * We try to detect the zlib header, and if it does not exsit we
2430 * teat the body is plain DEFLATE content.
2431 *
2432 * This method was adapted from PEAR HTTP_Request2 by (c) Alexey Borzov
2433 *
2434 * @link http://framework.zend.com/issues/browse/ZF-6040
2435 */
2436 $zlibHeader = unpack('n', substr($body, 0, 2));
2437 if ($zlibHeader[1] % 31 == 0) {
2438 return gzuncompress($body);
2439 } else {
2440 return gzinflate($body);
2441 }
2442 }
2443
2444 /**
2445 * Create a new Zend_Http_Response object from a string
2446 *
2447 * @param string $response_str
2448 * @return Zend_Http_Response
2449 */
2450 public static function fromString($response_str) {
2451 $code = self::extractCode($response_str);
2452 $headers = self::extractHeaders($response_str);
2453 $body = self::extractBody($response_str);
2454 $version = self::extractVersion($response_str);
2455 $message = self::extractMessage($response_str);
2456
2457 return new Zend_Http_Response($code, $headers, $body, $version, $message);
2458 }
2459 }
2460
2461 /**
2462 * Zend_Http_Client is an implemetation of an HTTP client in PHP. The client
2463 * supports basic features like sending different HTTP requests and handling
2464 * redirections, as well as more advanced features like proxy settings, HTTP
2465 * authentication and cookie persistance (using a Zend_Http_CookieJar object)
2466 *
2467 * @todo Implement proxy settings
2468 * @category Zend
2469 * @package Zend_Http
2470 * @subpackage Client
2471 * @throws Zend_Http_Client_Exception
2472 * @version $Id: Exception.php 20096 2010-01-06 02:05:09Z bkarwin $
2473 * @version $Id: Client.php 21952 2010-04-19 18:44:26Z shahar $
2474 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
2475 * @license http://framework.zend.com/license/new-bsd New BSD License
2476 */
2477 class Zend_Http_Client {
2478 /**
2479 * HTTP request methods
2480 */
2481 const GET = 'GET';
2482 const POST = 'POST';
2483 const PUT = 'PUT';
2484 const HEAD = 'HEAD';
2485 const DELETE = 'DELETE';
2486 const TRACE = 'TRACE';
2487 const OPTIONS = 'OPTIONS';
2488 const CONNECT = 'CONNECT';
2489 const MERGE = 'MERGE';
2490
2491 /**
2492 * Supported HTTP Authentication methods
2493 */
2494 const AUTH_BASIC = 'basic';
2495 //const AUTH_DIGEST = 'digest'; <-- not implemented yet
2496
2497 /**
2498 * HTTP protocol versions
2499 */
2500 const HTTP_1 = '1.1';
2501 const HTTP_0 = '1.0';
2502
2503 /**
2504 * Content attributes
2505 */
2506 const CONTENT_TYPE = 'Content-Type';
2507 const CONTENT_LENGTH = 'Content-Length';
2508
2509 /**
2510 * POST data encoding methods
2511 */
2512 const ENC_URLENCODED = 'application/x-www-form-urlencoded';
2513 const ENC_FORMDATA = 'multipart/form-data';
2514
2515 /**
2516 * Configuration array, set using the constructor or using ::setConfig()
2517 *
2518 * @var array
2519 */
2520 protected $config = array(
2521 'maxredirects' => 5,
2522 'strictredirects' => false,
2523 'useragent' => 'Zend_Http_Client',
2524 'timeout' => 10,
2525 'adapter' => 'Zend_Http_Client_Adapter_Socket',
2526 'httpversion' => self::HTTP_1,
2527 'keepalive' => false,
2528 'storeresponse' => true,
2529 'strict' => true,
2530 'output_stream' => false,
2531 'encodecookies' => true,
2532 );
2533
2534 /**
2535 * The adapter used to preform the actual connection to the server
2536 *
2537 * @var Zend_Http_Client_Adapter_Interface
2538 */
2539 protected $adapter = null;
2540
2541 /**
2542 * Request URI
2543 *
2544 * @var Zend_Uri_Http
2545 */
2546 protected $uri = null;
2547
2548 /**
2549 * Associative array of request headers
2550 *
2551 * @var array
2552 */
2553 protected $headers = array();
2554
2555 /**
2556 * HTTP request method
2557 *
2558 * @var string
2559 */
2560 protected $method = self::GET;
2561
2562 /**
2563 * Associative array of GET parameters
2564 *
2565 * @var array
2566 */
2567 protected $paramsGet = array();
2568
2569 /**
2570 * Assiciative array of POST parameters
2571 *
2572 * @var array
2573 */
2574 protected $paramsPost = array();
2575
2576 /**
2577 * Request body content type (for POST requests)
2578 *
2579 * @var string
2580 */
2581 protected $enctype = null;
2582
2583 /**
2584 * The raw post data to send. Could be set by setRawData($data, $enctype).
2585 *
2586 * @var string
2587 */
2588 protected $raw_post_data = null;
2589
2590 /**
2591 * HTTP Authentication settings
2592 *
2593 * Expected to be an associative array with this structure:
2594 * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic')
2595 * Where 'type' should be one of the supported authentication types (see the AUTH_*
2596 * constants), for example 'basic' or 'digest'.
2597 *
2598 * If null, no authentication will be used.
2599 *
2600 * @var array|null
2601 */
2602 protected $auth;
2603
2604 /**
2605 * File upload arrays (used in POST requests)
2606 *
2607 * An associative array, where each element is of the format:
2608 * 'name' => array('filename.txt', 'text/plain', 'This is the actual file contents')
2609 *
2610 * @var array
2611 */
2612 protected $files = array();
2613
2614 /**
2615 * The client's cookie jar
2616 *
2617 * @var Zend_Http_CookieJar
2618 */
2619 protected $cookiejar = null;
2620
2621 /**
2622 * The last HTTP request sent by the client, as string
2623 *
2624 * @var string
2625 */
2626 protected $last_request = null;
2627
2628 /**
2629 * The last HTTP response received by the client
2630 *
2631 * @var Zend_Http_Response
2632 */
2633 protected $last_response = null;
2634
2635 /**
2636 * Redirection counter
2637 *
2638 * @var int
2639 */
2640 protected $redirectCounter = 0;
2641
2642 /**
2643 * Fileinfo magic database resource
2644 *
2645 * This varaiable is populated the first time _detectFileMimeType is called
2646 * and is then reused on every call to this method
2647 *
2648 * @var resource
2649 */
2650 static protected $_fileInfoDb = null;
2651
2652 /**
2653 * Contructor method. Will create a new HTTP client. Accepts the target
2654 * URL and optionally configuration array.
2655 *
2656 * @param Zend_Uri_Http|string $uri
2657 * @param array $config Configuration key-value pairs.
2658 */
2659 public function __construct($uri = null, $config = null) {
2660 if ($uri !== null) {
2661 $this->setUri($uri);
2662 }
2663 if ($config !== null) {
2664 $this->setConfig($config);
2665 }
2666 }
2667
2668 /**
2669 * Set the URI for the next request
2670 *
2671 * @param Zend_Uri_Http|string $uri
2672 * @return Zend_Http_Client
2673 * @throws Zend_Http_Client_Exception
2674 */
2675 public function setUri($uri) {
2676 if (is_string($uri)) {
2677 $uri = Zend_Uri::factory($uri);
2678 }
2679
2680 if (!$uri instanceof Zend_Uri_Http) {
2681 /** @see Zend_Http_Client_Exception */
2682 if (!class_exists('Zend_Exception')) require 'exception.php';
2683 throw new Zend_Http_Client_Exception('Passed parameter is not a valid HTTP URI.');
2684 }
2685
2686 // Set auth if username and password has been specified in the uri
2687 if ($uri->getUsername() && $uri->getPassword()) {
2688 $this->setAuth($uri->getUsername(), $uri->getPassword());
2689 }
2690
2691 // We have no ports, set the defaults
2692 if (! $uri->getPort()) {
2693 $uri->setPort(($uri->getScheme() == 'https' ? 443 : 80));
2694 }
2695
2696 $this->uri = $uri;
2697
2698 return $this;
2699 }
2700
2701 /**
2702 * Get the URI for the next request
2703 *
2704 * @param boolean $as_string If true, will return the URI as a string
2705 * @return Zend_Uri_Http|string
2706 */
2707 public function getUri($as_string = false) {
2708 if ($as_string && $this->uri instanceof Zend_Uri_Http) {
2709 return $this->uri->__toString();
2710 } else {
2711 return $this->uri;
2712 }
2713 }
2714
2715 /**
2716 * Set configuration parameters for this HTTP client
2717 *
2718 * @param Zend_Config | array $config
2719 * @return Zend_Http_Client
2720 * @throws Zend_Http_Client_Exception
2721 */
2722 public function setConfig($config = array()) {
2723 if ($config instanceof Zend_Config) {
2724 $config = $config->toArray();
2725
2726 } elseif (! is_array($config)) {
2727 /** @see Zend_Http_Client_Exception */
2728 if (!class_exists('Zend_Exception')) require 'exception.php';
2729 throw new Zend_Http_Client_Exception('Array or Zend_Config object expected, got ' .
gettype($config));
2730 }
2731
2732 foreach ($config as $k => $v) {
2733 $this->config[strtolower($k)] = $v;
2734 }
2735
2736 // Pass configuration options to the adapter if it exists
2737 if ($this->adapter instanceof Zend_Http_Client_Adapter_Interface) {
2738 $this->adapter->setConfig($config);
2739 }
2740
2741 return $this;
2742 }
2743
2744 /**
2745 * Set the next request's method
2746 *
2747 * Validated the passed method and sets it. If we have files set for
2748 * POST requests, and the new method is not POST, the files are silently
2749 * dropped.
2750 *
2751 * @param string $method
2752 * @return Zend_Http_Client
2753 * @throws Zend_Http_Client_Exception
2754 */
2755 public function setMethod($method = self::GET) {
2756 if (! preg_match('/^[^\x00-\x1f\x7f-\xff\(\)<>@,;:\\\\"\/\[\]\?={}\s]+$/', $method)) {
2757 /** @see Zend_Http_Client_Exception */
2758 if (!class_exists('Zend_Exception')) require 'exception.php';
2759 throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method.");
2760 }
2761
2762 if ($method == self::POST && $this->enctype === null) {
2763 $this->setEncType(self::ENC_URLENCODED);
2764 }
2765
2766 $this->method = $method;
2767
2768 return $this;
2769 }
2770
2771 /**
2772 * Set one or more request headers
2773 *
2774 * This function can be used in several ways to set the client's request
2775 * headers:
2776 * 1. By providing two parameters: $name as the header to set (eg. 'Host')
2777 * and $value as it's value (eg. 'www.example.com').
2778 * 2. By providing a single header string as the only parameter
2779 * eg. 'Host: www.example.com'
2780 * 3. By providing an array of headers as the first parameter
2781 * eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case
2782 * the function will call itself recursively for each array item.
2783 *
2784 * @param string|array $name Header name, full header string ('Header: value')
2785 * or an array of headers
2786 * @param mixed $value Header value or null
2787 * @return Zend_Http_Client
2788 * @throws Zend_Http_Client_Exception
2789 */
2790 public function setHeaders($name, $value = null) {
2791 // If we got an array, go recusive!
2792 if (is_array($name)) {
2793 foreach ($name as $k => $v) {
2794 if (is_string($k)) {
2795 $this->setHeaders($k, $v);
2796 } else {
2797 $this->setHeaders($v, null);
2798 }
2799 }
2800 } else {
2801 // Check if $name needs to be split
2802 if ($value === null && (strpos($name, ':') > 0)) {
2803 list($name, $value) = explode(':', $name, 2);
2804 }
2805
2806 // Make sure the name is valid if we are in strict mode
2807 if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) {
2808 /** @see Zend_Http_Client_Exception */
2809 if (!class_exists('Zend_Exception')) require 'exception.php';
2810 throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name");
2811 }
2812
2813 $normalized_name = strtolower($name);
2814
2815 // If $value is null or false, unset the header
2816 if ($value === null || $value === false) {
2817 unset($this->headers[$normalized_name]);
2818
2819 // Else, set the header
2820 } else {
2821 // Header names are stored lowercase internally.
2822 if (is_string($value)) {
2823 $value = trim($value);
2824 }
2825 $this->headers[$normalized_name] = array($name, $value);
2826 }
2827 }
2828
2829 return $this;
2830 }
2831
2832 /**
2833 * Get the value of a specific header
2834 *
2835 * Note that if the header has more than one value, an array
2836 * will be returned.
2837 *
2838 * @param string $key
2839 * @return string|array|null The header value or null if it is not set
2840 */
2841 public function getHeader($key) {
2842 $key = strtolower($key);
2843 if (isset($this->headers[$key])) {
2844 return $this->headers[$key][1];
2845 } else {
2846 return null;
2847 }
2848 }
2849
2850 /**
2851 * Set a GET parameter for the request. Wrapper around _setParameter
2852 *
2853 * @param string|array $name
2854 * @param string $value
2855 * @return Zend_Http_Client
2856 */
2857 public function setParameterGet($name, $value = null) {
2858 if (is_array($name)) {
2859 foreach ($name as $k => $v)
2860 $this->_setParameter('GET', $k, $v);
2861 } else {
2862 $this->_setParameter('GET', $name, $value);
2863 }
2864
2865 return $this;
2866 }
2867
2868 /**
2869 * Set a POST parameter for the request. Wrapper around _setParameter
2870 *
2871 * @param string|array $name
2872 * @param string $value
2873 * @return Zend_Http_Client
2874 */
2875 public function setParameterPost($name, $value = null) {
2876 if (is_array($name)) {
2877 foreach ($name as $k => $v)
2878 $this->_setParameter('POST', $k, $v);
2879 } else {
2880 $this->_setParameter('POST', $name, $value);
2881 }
2882
2883 return $this;
2884 }
2885
2886 /**
2887 * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost
2888 *
2889 * @param string $type GET or POST
2890 * @param string $name
2891 * @param string $value
2892 * @return null
2893 */
2894 protected function _setParameter($type, $name, $value) {
2895 $parray = array();
2896 $type = strtolower($type);
2897 switch ($type) {
2898 case 'get':
2899 $parray = &$this->paramsGet;
2900 break;
2901 case 'post':
2902 $parray = &$this->paramsPost;
2903 break;
2904 }
2905
2906 if ($value === null) {
2907 if (isset($parray[$name])) unset($parray[$name]);
2908 } else {
2909 $parray[$name] = $value;
2910 }
2911 }
2912
2913 /**
2914 * Get the number of redirections done on the last request
2915 *
2916 * @return int
2917 */
2918 public function getRedirectionsCount() {
2919 return $this->redirectCounter;
2920 }
2921
2922 /**
2923 * Set HTTP authentication parameters
2924 *
2925 * $type should be one of the supported types - see the self::AUTH_*
2926 * constants.
2927 *
2928 * To enable authentication:
2929 * <code>
2930 * $this->setAuth('shahar', 'secret', Zend_Http_Client::AUTH_BASIC);
2931 * </code>
2932 *
2933 * To disable authentication:
2934 * <code>
2935 * $this->setAuth(false);
2936 * </code>
2937 *
2938 * @see http://www.faqs.org/rfcs/rfc2617.html
2939 * @param string|false $user User name or false disable authentication
2940 * @param string $password Password
2941 * @param string $type Authentication type
2942 * @return Zend_Http_Client
2943 * @throws Zend_Http_Client_Exception
2944 */
2945 public function setAuth($user, $password = '', $type = self::AUTH_BASIC) {
2946 // If we got false or null, disable authentication
2947 if ($user === false || $user === null) {
2948 $this->auth = null;
2949
2950 // Clear the auth information in the uri instance as well
2951 if ($this->uri instanceof Zend_Uri_Http) {
2952 $this->getUri()->setUsername('');
2953 $this->getUri()->setPassword('');
2954 }
2955 // Else, set up authentication
2956 } else {
2957 // Check we got a proper authentication type
2958 if (! defined('self::AUTH_' . strtoupper($type))) {
2959 /** @see Zend_Http_Client_Exception */
2960 if (!class_exists('Zend_Exception')) require 'exception.php';
2961 throw new Zend_Http_Client_Exception("Invalid or not supported authentication type: '$type'");
2962 }
2963
2964 $this->auth = array(
2965 'user' => (string) $user,
2966 'password' => (string) $password,
2967 'type' => $type
2968 );
2969 }
2970
2971 return $this;
2972 }
2973
2974 /**
2975 * Return the current cookie jar or null if none.
2976 *
2977 * @return Zend_Http_CookieJar|null
2978 */
2979 public function getCookieJar() {
2980 return $this->cookiejar;
2981 }
2982
2983 /**
2984 * Set a file to upload (using a POST request)
2985 *
2986 * Can be used in two ways:
2987 *
2988 * 1. $data is null (default): $filename is treated as the name if a local file which
2989 * will be read and sent. Will try to guess the content type using mime_content_type().
2990 * 2. $data is set - $filename is sent as the file name, but $data is sent as the file
2991 * contents and no file is read from the file system. In this case, you need to
2992 * manually set the Content-Type ($ctype) or it will default to
2993 * application/octet-stream.
2994 *
2995 * @param string $filename Name of file to upload, or name to save as
2996 * @param string $formname Name of form element to send as
2997 * @param string $data Data to send (if null, $filename is read and sent)
2998 * @param string $ctype Content type to use (if $data is set and $ctype is
2999 * null, will be application/octet-stream)
3000 * @return Zend_Http_Client
3001 * @throws Zend_Http_Client_Exception
3002 */
3003 public function setFileUpload($filename, $formname, $data = null, $ctype = null) {
3004 if ($data === null) {
3005 if (($data = @file_get_contents($filename)) === false) {
3006 /** @see Zend_Http_Client_Exception */
3007 if (!class_exists('Zend_Exception')) require 'exception.php';
3008 throw new Zend_Http_Client_Exception("Unable to read file '{$filename}' for upload");
3009 }
3010
3011 if (! $ctype) {
3012 $ctype = $this->_detectFileMimeType($filename);
3013 }
3014 }
3015
3016 // Force enctype to multipart/form-data
3017 $this->setEncType(self::ENC_FORMDATA);
3018
3019 $this->files[] = array(
3020 'formname' => $formname,
3021 'filename' => basename($filename),
3022 'ctype' => $ctype,
3023 'data' => $data
3024 );
3025
3026 return $this;
3027 }
3028
3029 /**
3030 * Set the encoding type for POST data
3031 *
3032 * @param string $enctype
3033 * @return Zend_Http_Client
3034 */
3035 public function setEncType($enctype = self::ENC_URLENCODED) {
3036 $this->enctype = $enctype;
3037
3038 return $this;
3039 }
3040
3041 /**
3042 * Set the raw (already encoded) POST data.
3043 *
3044 * This function is here for two reasons:
3045 * 1. For advanced user who would like to set their own data, already encoded
3046 * 2. For backwards compatibilty: If someone uses the old post($data) method.
3047 * this method will be used to set the encoded data.
3048 *
3049 * $data can also be stream (such as file) from which the data will be read.
3050 *
3051 * @param string|resource $data
3052 * @param string $enctype
3053 * @return Zend_Http_Client
3054 */
3055 public function setRawData($data, $enctype = null) {
3056 $this->raw_post_data = $data;
3057 $this->setEncType($enctype);
3058 if (is_resource($data)) {
3059 // We've got stream data
3060 $stat = @fstat($data);
3061 if($stat) {
3062 $this->setHeaders(self::CONTENT_LENGTH, $stat['size']);
3063 }
3064 }
3065 return $this;
3066 }
3067
3068 /**
3069 * Clear all GET and POST parameters
3070 *
3071 * Should be used to reset the request parameters if the client is
3072 * used for several concurrent requests.
3073 *
3074 * clearAll parameter controls if we clean just parameters or also
3075 * headers and last_*
3076 *
3077 * @param bool $clearAll Should all data be cleared?
3078 * @return Zend_Http_Client
3079 */
3080 public function resetParameters($clearAll = false) {
3081 // Reset parameter data
3082 $this->paramsGet = array();
3083 $this->paramsPost = array();
3084 $this->files = array();
3085 $this->raw_post_data = null;
3086
3087 if($clearAll) {
3088 $this->headers = array();
3089 $this->last_request = null;
3090 $this->last_response = null;
3091 } else {
3092 // Clear outdated headers
3093 if (isset($this->headers[strtolower(self::CONTENT_TYPE)])) {
3094 unset($this->headers[strtolower(self::CONTENT_TYPE)]);
3095 }
3096 if (isset($this->headers[strtolower(self::CONTENT_LENGTH)])) {
3097 unset($this->headers[strtolower(self::CONTENT_LENGTH)]);
3098 }
3099 }
3100
3101 return $this;
3102 }
3103
3104 /**
3105 * Get the last HTTP request as string
3106 *
3107 * @return string
3108 */
3109 public function getLastRequest() {
3110 return $this->last_request;
3111 }
3112
3113 /**
3114 * Get the last HTTP response received by this client
3115 *
3116 * If $config['storeresponse'] is set to false, or no response was
3117 * stored yet, will return null
3118 *
3119 * @return Zend_Http_Response or null if none
3120 */
3121 public function getLastResponse() {
3122 return $this->last_response;
3123 }
3124
3125 /**
3126 * Load the connection adapter
3127 *
3128 * While this method is not called more than one for a client, it is
3129 * seperated from ->request() to preserve logic and readability
3130 *
3131 * @param Zend_Http_Client_Adapter_Interface|string $adapter
3132 * @return null
3133 * @throws Zend_Http_Client_Exception
3134 */
3135 public function setAdapter($adapter) {
3136 if (is_string($adapter)) {
3137 if (!class_exists($adapter)) {
3138 try {
3139 require_once 'Zend/Loader.php';
3140 Zend_Loader::loadClass($adapter);
3141 } catch (Zend_Exception $e) {
3142 /** @see Zend_Http_Client_Exception */
3143 if (!class_exists('Zend_Exception')) require 'exception.php';
3144 throw new Zend_Http_Client_Exception("Unable to load adapter '$adapter': {$e->getMessage()}",
0, $e);
3145 }
3146 }
3147
3148 $adapter = new $adapter;
3149 }
3150
3151 if (! $adapter instanceof Zend_Http_Client_Adapter_Interface) {
3152 /** @see Zend_Http_Client_Exception */
3153 if (!class_exists('Zend_Exception')) require 'exception.php';
3154 throw new Zend_Http_Client_Exception('Passed adapter is not a HTTP connection adapter');
3155 }
3156
3157 $this->adapter = $adapter;
3158 $config = $this->config;
3159 unset($config['adapter']);
3160 $this->adapter->setConfig($config);
3161 }
3162
3163 /**
3164 * Load the connection adapter
3165 *
3166 * @return Zend_Http_Client_Adapter_Interface $adapter
3167 */
3168 public function getAdapter() {
3169 return $this->adapter;
3170 }
3171
3172 /**
3173 * Set streaming for received data
3174 *
3175 * @param string|boolean $streamfile Stream file, true for temp file, false/null for no streaming
3176 * @return Zend_Http_Client
3177 */
3178 public function setStream($streamfile = true) {
3179 $this->setConfig(array("output_stream" => $streamfile));
3180 return $this;
3181 }
3182
3183 /**
3184 * Get status of streaming for received data
3185 * @return boolean|string
3186 */
3187 public function getStream() {
3188 return $this->config["output_stream"];
3189 }
3190
3191 /**
3192 * Create temporary stream
3193 *
3194 * @return resource
3195 */
3196 protected function _openTempStream() {
3197 $this->_stream_name = $this->config['output_stream'];
3198 if(!is_string($this->_stream_name)) {
3199 // If name is not given, create temp name
3200 $this->_stream_name = tempnam(isset($this->config['stream_tmp_dir']) ?
$this->config['stream_tmp_dir']
3201 : sys_get_temp_dir(),
3202 'Zend_Http_Client');
3203 }
3204
3205 if (false === ($fp = @fopen($this->_stream_name, "w+b"))) {
3206 if ($this->adapter instanceof Zend_Http_Client_Adapter_Interface) {
3207 $this->adapter->close();
3208 }
3209 if (!class_exists('Zend_Exception')) require 'exception.php';
3210 throw new Zend_Http_Client_Exception("Could not open temp file {$this->_stream_name}");
3211 }
3212
3213 return $fp;
3214 }
3215
3216 /**
3217 * Send the HTTP request and return an HTTP response object
3218 *
3219 * @param string $method
3220 * @return Zend_Http_Response
3221 * @throws Zend_Http_Client_Exception
3222 */
3223 public function request($method = null) {
3224 if (! $this->uri instanceof Zend_Uri_Http) {
3225 /** @see Zend_Http_Client_Exception */
3226 if (!class_exists('Zend_Exception')) require 'exception.php';
3227 throw new Zend_Http_Client_Exception('No valid URI has been passed to the client');
3228 }
3229
3230 if ($method) {
3231 $this->setMethod($method);
3232 }
3233 $this->redirectCounter = 0;
3234 $response = null;
3235
3236 // Make sure the adapter is loaded
3237 if ($this->adapter == null) {
3238 $this->setAdapter($this->config['adapter']);
3239 }
3240
3241 // Send the first request. If redirected, continue.
3242 do {
3243 // Clone the URI and add the additional GET parameters to it
3244 $uri = clone $this->uri;
3245 if (! empty($this->paramsGet)) {
3246 $query = $uri->getQuery();
3247 if (! empty($query)) {
3248 $query .= '&';
3249 }
3250 $query .= http_build_query($this->paramsGet, null, '&');
3251
3252 $uri->setQuery($query);
3253 }
3254
3255 $body = $this->_prepareBody();
3256 $headers = $this->_prepareHeaders();
3257
3258 // check that adapter supports streaming before using it
3259 if(is_resource($body) && !($this->adapter instanceof Zend_Http_Client_Adapter_Stream)) {
3260 /** @see Zend_Http_Client_Exception */
3261 if (!class_exists('Zend_Exception')) require 'exception.php';
3262 throw new Zend_Http_Client_Exception('Adapter does not support streaming');
3263 }
3264
3265 // Open the connection, send the request and read the response
3266 $this->adapter->connect($uri->getHost(), $uri->getPort(),
3267 ($uri->getScheme() == 'https' ? true : false));
3268
3269 if($this->config['output_stream']) {
3270 if($this->adapter instanceof Zend_Http_Client_Adapter_Stream) {
3271 $stream = $this->_openTempStream();
3272 $this->adapter->setOutputStream($stream);
3273 } else {
3274 /** @see Zend_Http_Client_Exception */
3275 if (!class_exists('Zend_Exception')) require 'exception.php';
3276 throw new Zend_Http_Client_Exception('Adapter does not support streaming');
3277 }
3278 }
3279
3280 $this->last_request = $this->adapter->write($this->method,
3281 $uri, $this->config['httpversion'], $headers, $body);
3282
3283 $response = $this->adapter->read();
3284 if (! $response) {
3285 /** @see Zend_Http_Client_Exception */
3286 if (!class_exists('Zend_Exception')) require 'exception.php';
3287 throw new Zend_Http_Client_Exception('Unable to read response, or response is empty');
3288 }
3289
3290 if($this->config['output_stream']) {
3291 rewind($stream);
3292 // cleanup the adapter
3293 $this->adapter->setOutputStream(null);
3294 $response = Zend_Http_Response_Stream::fromStream($response, $stream);
3295 $response->setStreamName($this->_stream_name);
3296 if(!is_string($this->config['output_stream'])) {
3297 // we used temp name, will need to clean up
3298 $response->setCleanup(true);
3299 }
3300 } else {
3301 $response = Zend_Http_Response::fromString($response);
3302 }
3303
3304 if ($this->config['storeresponse']) {
3305 $this->last_response = $response;
3306 }
3307
3308 // Load cookies into cookie jar
3309 if (isset($this->cookiejar)) {
3310 $this->cookiejar->addCookiesFromResponse($response, $uri);
3311 }
3312
3313 // If we got redirected, look for the Location header
3314 if ($response->isRedirect() && ($location = $response->getHeader('location'))) {
3315
3316 // Check whether we send the exact same request again, or drop the parameters
3317 // and send a GET request
3318 if ($response->getStatus() == 303 ||
3319 ((! $this->config['strictredirects']) && ($response->getStatus() == 302 ||
3320 $response->getStatus() == 301))) {
3321
3322 $this->resetParameters();
3323 $this->setMethod(self::GET);
3324 }
3325
3326 // If we got a well formed absolute URI
3327 if (Zend_Uri_Http::check($location)) {
3328 $this->setHeaders('host', null);
3329 $this->setUri($location);
3330
3331 } else {
3332
3333 // Split into path and query and set the query
3334 if (strpos($location, '?') !== false) {
3335 list($location, $query) = explode('?', $location, 2);
3336 } else {
3337 $query = '';
3338 }
3339 $this->uri->setQuery($query);
3340
3341 // Else, if we got just an absolute path, set it
3342 if(strpos($location, '/') === 0) {
3343 $this->uri->setPath($location);
3344
3345 // Else, assume we have a relative path
3346 } else {
3347 // Get the current path directory, removing any trailing slashes
3348 $path = $this->uri->getPath();
3349 $path = rtrim(substr($path, 0, strrpos($path, '/')), "/");
3350 $this->uri->setPath($path . '/' . $location);
3351 }
3352 }
3353 ++$this->redirectCounter;
3354
3355 } else {
3356 // If we didn't get any location, stop redirecting
3357 break;
3358 }
3359
3360 } while ($this->redirectCounter < $this->config['maxredirects']);
3361
3362 return $response;
3363 }
3364
3365 /**
3366 * Prepare the request headers
3367 *
3368 * @return array
3369 */
3370 protected function _prepareHeaders() {
3371 $headers = array();
3372
3373 // Set the host header
3374 if (! isset($this->headers['host'])) {
3375 $host = $this->uri->getHost();
3376
3377 // If the port is not default, add it
3378 if (! (($this->uri->getScheme() == 'http' && $this->uri->getPort() == 80) ||
3379 ($this->uri->getScheme() == 'https' && $this->uri->getPort() == 443))) {
3380 $host .= ':' . $this->uri->getPort();
3381 }
3382
3383 $headers[] = "Host: {$host}";
3384 }
3385
3386 // Set the connection header
3387 if (! isset($this->headers['connection'])) {
3388 if (! $this->config['keepalive']) {
3389 $headers[] = "Connection: close";
3390 }
3391 }
3392
3393 // Set the Accept-encoding header if not set - depending on whether
3394 // zlib is available or not.
3395 if (! isset($this->headers['accept-encoding'])) {
3396 if (function_exists('gzinflate')) {
3397 $headers[] = 'Accept-encoding: gzip, deflate';
3398 } else {
3399 $headers[] = 'Accept-encoding: identity';
3400 }
3401 }
3402
3403 // Set the Content-Type header
3404 if ($this->method == self::POST &&
3405 (! isset($this->headers[strtolower(self::CONTENT_TYPE)]) && isset($this->enctype))) {
3406
3407 $headers[] = self::CONTENT_TYPE . ': ' . $this->enctype;
3408 }
3409
3410 // Set the user agent header
3411 if (! isset($this->headers['user-agent']) && isset($this->config['useragent'])) {
3412 $headers[] = "User-Agent: {$this->config['useragent']}";
3413 }
3414
3415 // Set HTTP authentication if needed
3416 if (is_array($this->auth)) {
3417 $auth = self::encodeAuthHeader($this->auth['user'], $this->auth['password'], $this->auth['type']);
3418 $headers[] = "Authorization: {$auth}";
3419 }
3420
3421 // Load cookies from cookie jar
3422 if (isset($this->cookiejar)) {
3423 $cookstr = $this->cookiejar->getMatchingCookies($this->uri,
3424 true, Zend_Http_CookieJar::COOKIE_STRING_CONCAT);
3425
3426 if ($cookstr) {
3427 $headers[] = "Cookie: {$cookstr}";
3428 }
3429 }
3430
3431 // Add all other user defined headers
3432 foreach ($this->headers as $header) {
3433 list($name, $value) = $header;
3434 if (is_array($value)) {
3435 $value = implode(', ', $value);
3436 }
3437
3438 $headers[] = "$name: $value";
3439 }
3440
3441 return $headers;
3442 }
3443
3444 /**
3445 * Prepare the request body (for POST and PUT requests)
3446 *
3447 * @return string
3448 * @throws Zend_Http_Client_Exception
3449 */
3450 protected function _prepareBody() {
3451 // According to RFC2616, a TRACE request should not have a body.
3452 if ($this->method == self::TRACE) {
3453 return '';
3454 }
3455
3456 if (isset($this->raw_post_data) && is_resource($this->raw_post_data)) {
3457 return $this->raw_post_data;
3458 }
3459 // If mbstring overloads substr and strlen functions, we have to
3460 // override it's internal encoding
3461 if (function_exists('mb_internal_encoding') &&
3462 ((int) ini_get('mbstring.func_overload')) & 2) {
3463
3464 $mbIntEnc = mb_internal_encoding();
3465 mb_internal_encoding('ASCII');
3466 }
3467
3468 // If we have raw_post_data set, just use it as the body.
3469 if (isset($this->raw_post_data)) {
3470 $this->setHeaders(self::CONTENT_LENGTH, strlen($this->raw_post_data));
3471 if (isset($mbIntEnc)) {
3472 mb_internal_encoding($mbIntEnc);
3473 }
3474
3475 return $this->raw_post_data;
3476 }
3477
3478 $body = '';
3479
3480 // If we have files to upload, force enctype to multipart/form-data
3481 if (count ($this->files) > 0) {
3482 $this->setEncType(self::ENC_FORMDATA);
3483 }
3484
3485 // If we have POST parameters or files, encode and add them to the body
3486 if (count($this->paramsPost) > 0 || count($this->files) > 0) {
3487 switch($this->enctype) {
3488 case self::ENC_FORMDATA:
3489 // Encode body as multipart/form-data
3490 $boundary = '---ZENDHTTPCLIENT-' . md5(microtime());
3491 $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}");
3492
3493 // Get POST parameters and encode them
3494 $params = self::_flattenParametersArray($this->paramsPost);
3495 foreach ($params as $pp) {
3496 $body .= self::encodeFormData($boundary, $pp[0], $pp[1]);
3497 }
3498
3499 // Encode files
3500 foreach ($this->files as $file) {
3501 $fhead = array(self::CONTENT_TYPE => $file['ctype']);
3502 $body .= self::encodeFormData($boundary, $file['formname'], $file['data'],
$file['filename'], $fhead);
3503 }
3504
3505 $body .= "--{$boundary}--\r\n";
3506 break;
3507
3508 case self::ENC_URLENCODED:
3509 // Encode body as application/x-www-form-urlencoded
3510 $this->setHeaders(self::CONTENT_TYPE, self::ENC_URLENCODED);
3511 $body = http_build_query($this->paramsPost, '', '&');
3512 break;
3513
3514 default:
3515 if (isset($mbIntEnc)) {
3516 mb_internal_encoding($mbIntEnc);
3517 }
3518
3519 /** @see Zend_Http_Client_Exception */
3520 if (!class_exists('Zend_Exception')) require 'exception.php';
3521 throw new Zend_Http_Client_Exception("Cannot handle content type '{$this->enctype}'
automatically."
3522 . " Please use Zend_Http_Client::setRawData to send this kind of content.");
3523 break;
3524 }
3525 }
3526
3527 // Set the Content-Length if we have a body or if request is POST/PUT
3528 if ($body || $this->method == self::POST || $this->method == self::PUT) {
3529 $this->setHeaders(self::CONTENT_LENGTH, strlen($body));
3530 }
3531
3532 if (isset($mbIntEnc)) {
3533 mb_internal_encoding($mbIntEnc);
3534 }
3535
3536 return $body;
3537 }
3538
3539 /**
3540 * Helper method that gets a possibly multi-level parameters array (get or
3541 * post) and flattens it.
3542 *
3543 * The method returns an array of (key, value) pairs (because keys are not
3544 * necessarily unique. If one of the parameters in as array, it will also
3545 * add a [] suffix to the key.
3546 *
3547 * This method is deprecated since Zend Framework 1.9 in favour of
3548 * self::_flattenParametersArray() and will be dropped in 2.0
3549 *
3550 * @deprecated since 1.9
3551 *
3552 * @param array $parray The parameters array
3553 * @param bool $urlencode Whether to urlencode the name and value
3554 * @return array
3555 */
3556 protected function _getParametersRecursive($parray, $urlencode = false) {
3557 // Issue a deprecated notice
3558 trigger_error("The " . __METHOD__ . " method is deprecated and will be dropped in 2.0.",
3559 E_USER_NOTICE);
3560
3561 if (! is_array($parray)) {
3562 return $parray;
3563 }
3564 $parameters = array();
3565
3566 foreach ($parray as $name => $value) {
3567 if ($urlencode) {
3568 $name = urlencode($name);
3569 }
3570
3571 // If $value is an array, iterate over it
3572 if (is_array($value)) {
3573 $name .= ($urlencode ? '%5B%5D' : '[]');
3574 foreach ($value as $subval) {
3575 if ($urlencode) {
3576 $subval = urlencode($subval);
3577 }
3578 $parameters[] = array($name, $subval);
3579 }
3580 } else {
3581 if ($urlencode) {
3582 $value = urlencode($value);
3583 }
3584 $parameters[] = array($name, $value);
3585 }
3586 }
3587
3588 return $parameters;
3589 }
3590
3591 /**
3592 * Attempt to detect the MIME type of a file using available extensions
3593 *
3594 * This method will try to detect the MIME type of a file. If the fileinfo
3595 * extension is available, it will be used. If not, the mime_magic
3596 * extension which is deprected but is still available in many PHP setups
3597 * will be tried.
3598 *
3599 * If neither extension is available, the default application/octet-stream
3600 * MIME type will be returned
3601 *
3602 * @param string $file File path
3603 * @return string MIME type
3604 */
3605 protected function _detectFileMimeType($file) {
3606 $type = null;
3607
3608 // First try with fileinfo functions
3609 if (function_exists('finfo_open')) {
3610 if (self::$_fileInfoDb === null) {
3611 self::$_fileInfoDb = @finfo_open(FILEINFO_MIME);
3612 }
3613
3614 if (self::$_fileInfoDb) {
3615 $type = finfo_file(self::$_fileInfoDb, $file);
3616 }
3617
3618 } elseif (function_exists('mime_content_type')) {
3619 $type = mime_content_type($file);
3620 }
3621
3622 // Fallback to the default application/octet-stream
3623 if (! $type) {
3624 $type = 'application/octet-stream';
3625 }
3626
3627 return $type;
3628 }
3629
3630 /**
3631 * Encode data to a multipart/form-data part suitable for a POST request.
3632 *
3633 * @param string $boundary
3634 * @param string $name
3635 * @param mixed $value
3636 * @param string $filename
3637 * @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" =>
"binary")
3638 * @return string
3639 */
3640 public static function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) {
3641 $ret = "--{$boundary}\r\n" .
3642 'Content-Disposition: form-data; name="' . $name .'"';
3643
3644 if ($filename) {
3645 $ret .= '; filename="' . $filename . '"';
3646 }
3647 $ret .= "\r\n";
3648
3649 foreach ($headers as $hname => $hvalue) {
3650 $ret .= "{$hname}: {$hvalue}\r\n";
3651 }
3652 $ret .= "\r\n";
3653
3654 $ret .= "{$value}\r\n";
3655
3656 return $ret;
3657 }
3658
3659 /**
3660 * Create a HTTP authentication "Authorization:" header according to the
3661 * specified user, password and authentication method.
3662 *
3663 * @see http://www.faqs.org/rfcs/rfc2617.html
3664 * @param string $user
3665 * @param string $password
3666 * @param string $type
3667 * @return string
3668 * @throws Zend_Http_Client_Exception
3669 */
3670 public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC) {
3671 $authHeader = null;
3672
3673 switch ($type) {
3674 case self::AUTH_BASIC:
3675 // In basic authentication, the user name cannot contain ":"
3676 if (strpos($user, ':') !== false) {
3677 /** @see Zend_Http_Client_Exception */
3678 if (!class_exists('Zend_Exception')) require 'exception.php';
3679 throw new Zend_Http_Client_Exception("The user name cannot contain ':' in 'Basic' HTTP
authentication");
3680 }
3681
3682 $authHeader = 'Basic ' . base64_encode($user . ':' . $password);
3683 break;
3684
3685 //case self::AUTH_DIGEST:
3686 /**
3687 * @todo Implement digest authentication
3688 */
3689 // break;
3690
3691 default:
3692 /** @see Zend_Http_Client_Exception */
3693 if (!class_exists('Zend_Exception')) require 'exception.php';
3694 throw new Zend_Http_Client_Exception("Not a supported HTTP authentication type: '$type'");
3695 }
3696
3697 return $authHeader;
3698 }
3699
3700 /**
3701 * Convert an array of parameters into a flat array of (key, value) pairs
3702 *
3703 * Will flatten a potentially multi-dimentional array of parameters (such
3704 * as POST parameters) into a flat array of (key, value) paris. In case
3705 * of multi-dimentional arrays, square brackets ([]) will be added to the
3706 * key to indicate an array.
3707 *
3708 * @since 1.9
3709 *
3710 * @param array $parray
3711 * @param string $prefix
3712 * @return array
3713 */
3714 static protected function _flattenParametersArray($parray, $prefix = null) {
3715 if (! is_array($parray)) {
3716 return $parray;
3717 }
3718
3719 $parameters = array();
3720
3721 foreach($parray as $name => $value) {
3722
3723 // Calculate array key
3724 if ($prefix) {
3725 if (is_int($name)) {
3726 $key = $prefix . '[]';
3727 } else {
3728 $key = $prefix . "[$name]";
3729 }
3730 } else {
3731 $key = $name;
3732 }
3733
3734 if (is_array($value)) {
3735 $parameters = array_merge($parameters, self::_flattenParametersArray($value, $key));
3736
3737 } else {
3738 $parameters[] = array($key, $value);
3739 }
3740 }
3741
3742 return $parameters;
3743 }
3744
3745 }
3746
3747 /**
3748 * Zend_Http_Cookie is a class describing an HTTP cookie and all it's parameters.
3749 *
3750 * Zend_Http_Cookie is a class describing an HTTP cookie and all it's parameters. The
3751 * class also enables validating whether the cookie should be sent to the server in
3752 * a specified scenario according to the request URI, the expiry time and whether
3753 * session cookies should be used or not. Generally speaking cookies should be
3754 * contained in a Cookiejar object, or instantiated manually and added to an HTTP
3755 * request.
3756 *
3757 * See http://wp.netscape.com/newsref/std/cookie_spec.html for some specs.
3758 *
3759 * @category Zend
3760 * @package Zend_Http
3761 * @subpackage Cookie
3762 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
3763 * @version $Id: Cookie.php 21020 2010-02-11 17:27:23Z shahar $
3764 * @license http://framework.zend.com/license/new-bsd New BSD License
3765 */
3766 class Zend_Http_Cookie {
3767 /**
3768 * Cookie name
3769 *
3770 * @var string
3771 */
3772 protected $name;
3773
3774 /**
3775 * Cookie value
3776 *
3777 * @var string
3778 */
3779 protected $value;
3780
3781 /**
3782 * Cookie expiry date
3783 *
3784 * @var int
3785 */
3786 protected $expires;
3787
3788 /**
3789 * Cookie domain
3790 *
3791 * @var string
3792 */
3793 protected $domain;
3794
3795 /**
3796 * Cookie path
3797 *
3798 * @var string
3799 */
3800 protected $path;
3801
3802 /**
3803 * Whether the cookie is secure or not
3804 *
3805 * @var boolean
3806 */
3807 protected $secure;
3808
3809 /**
3810 * Whether the cookie value has been encoded/decoded
3811 *
3812 * @var boolean
3813 */
3814 protected $encodeValue;
3815
3816 /**
3817 * Cookie object constructor
3818 *
3819 * @todo Add validation of each one of the parameters (legal domain, etc.)
3820 *
3821 * @param string $name
3822 * @param string $value
3823 * @param string $domain
3824 * @param int $expires
3825 * @param string $path
3826 * @param bool $secure
3827 */
3828 public function __construct($name, $value, $domain, $expires = null, $path = null, $secure = false) {
3829 if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
3830 if (!class_exists('Zend_Exception')) require 'exception.php';
3831 throw new Zend_Http_Exception("Cookie name cannot contain these characters: =,; \\t\\r\\n\\013\\014
({$name})");
3832 }
3833
3834 if (! $this->name = (string) $name) {
3835 if (!class_exists('Zend_Exception')) require 'exception.php';
3836 throw new Zend_Http_Exception('Cookies must have a name');
3837 }
3838
3839 if (! $this->domain = (string) $domain) {
3840 if (!class_exists('Zend_Exception')) require 'exception.php';
3841 throw new Zend_Http_Exception('Cookies must have a domain');
3842 }
3843
3844 $this->value = (string) $value;
3845 $this->expires = ($expires === null ? null : (int) $expires);
3846 $this->path = ($path ? $path : '/');
3847 $this->secure = $secure;
3848 }
3849
3850 /**
3851 * Get Cookie name
3852 *
3853 * @return string
3854 */
3855 public function getName() {
3856 return $this->name;
3857 }
3858
3859 /**
3860 * Get cookie value
3861 *
3862 * @return string
3863 */
3864 public function getValue() {
3865 return $this->value;
3866 }
3867
3868 /**
3869 * Get cookie domain
3870 *
3871 * @return string
3872 */
3873 public function getDomain() {
3874 return $this->domain;
3875 }
3876
3877 /**
3878 * Get the cookie path
3879 *
3880 * @return string
3881 */
3882 public function getPath() {
3883 return $this->path;
3884 }
3885
3886 /**
3887 * Get the expiry time of the cookie, or null if no expiry time is set
3888 *
3889 * @return int|null
3890 */
3891 public function getExpiryTime() {
3892 return $this->expires;
3893 }
3894
3895 /**
3896 * Check whether the cookie should only be sent over secure connections
3897 *
3898 * @return boolean
3899 */
3900 public function isSecure() {
3901 return $this->secure;
3902 }
3903
3904 /**
3905 * Check whether the cookie has expired
3906 *
3907 * Always returns false if the cookie is a session cookie (has no expiry time)
3908 *
3909 * @param int $now Timestamp to consider as "now"
3910 * @return boolean
3911 */
3912 public function isExpired($now = null) {
3913 if ($now === null) $now = time();
3914 if (is_int($this->expires) && $this->expires < $now) {
3915 return true;
3916 } else {
3917 return false;
3918 }
3919 }
3920
3921 /**
3922 * Check whether the cookie is a session cookie (has no expiry time set)
3923 *
3924 * @return boolean
3925 */
3926 public function isSessionCookie() {
3927 return ($this->expires === null);
3928 }
3929
3930 /**
3931 * Checks whether the cookie should be sent or not in a specific scenario
3932 *
3933 * @param string|Zend_Uri_Http $uri URI to check against (secure, domain, path)
3934 * @param boolean $matchSessionCookies Whether to send session cookies
3935 * @param int $now Override the current time when checking for expiry time
3936 * @return boolean
3937 */
3938 public function match($uri, $matchSessionCookies = true, $now = null) {
3939 if (is_string ($uri)) {
3940 $uri = Zend_Uri_Http::factory($uri);
3941 }
3942
3943 // Make sure we have a valid Zend_Uri_Http object
3944 if (! ($uri->valid() && ($uri->getScheme() == 'http' || $uri->getScheme() == 'https'))) {
3945 if (!class_exists('Zend_Exception')) require 'exception.php';
3946 throw new Zend_Http_Exception('Passed URI is not a valid HTTP or HTTPS URI');
3947 }
3948
3949 // Check that the cookie is secure (if required) and not expired
3950 if ($this->secure && $uri->getScheme() != 'https') return false;
3951 if ($this->isExpired($now)) return false;
3952 if ($this->isSessionCookie() && ! $matchSessionCookies) return false;
3953
3954 // Check if the domain matches
3955 if (! self::matchCookieDomain($this->getDomain(), $uri->getHost())) {
3956 return false;
3957 }
3958
3959 // Check that path matches using prefix match
3960 if (! self::matchCookiePath($this->getPath(), $uri->getPath())) {
3961 return false;
3962 }
3963
3964 // If we didn't die until now, return true.
3965 return true;
3966 }
3967
3968 /**
3969 * Get the cookie as a string, suitable for sending as a "Cookie" header in an
3970 * HTTP request
3971 *
3972 * @return string
3973 */
3974 public function __toString() {
3975 if ($this->encodeValue) {
3976 return $this->name . '=' . urlencode($this->value) . ';';
3977 }
3978 return $this->name . '=' . $this->value . ';';
3979 }
3980
3981 /**
3982 * Generate a new Cookie object from a cookie string
3983 * (for example the value of the Set-Cookie HTTP header)
3984 *
3985 * @param string $cookieStr
3986 * @param Zend_Uri_Http|string $refUri Reference URI for default values (domain, path)
3987 * @param boolean $encodeValue Weither or not the cookie's value should be
3988 * passed through urlencode/urldecode
3989 * @return Zend_Http_Cookie A new Zend_Http_Cookie object or false on failure.
3990 */
3991 public static function fromString($cookieStr, $refUri = null, $encodeValue = true) {
3992 // Set default values
3993 if (is_string($refUri)) {
3994 $refUri = Zend_Uri_Http::factory($refUri);
3995 }
3996
3997 $name = '';
3998 $value = '';
3999 $domain = '';
4000 $path = '';
4001 $expires = null;
4002 $secure = false;
4003 $parts = explode(';', $cookieStr);
4004
4005 // If first part does not include '=', fail
4006 if (strpos($parts[0], '=') === false) return false;
4007
4008 // Get the name and value of the cookie
4009 list($name, $value) = explode('=', trim(array_shift($parts)), 2);
4010 $name = trim($name);
4011 if ($encodeValue) {
4012 $value = urldecode(trim($value));
4013 }
4014
4015 // Set default domain and path
4016 if ($refUri instanceof Zend_Uri_Http) {
4017 $domain = $refUri->getHost();
4018 $path = $refUri->getPath();
4019 $path = substr($path, 0, strrpos($path, '/'));
4020 }
4021
4022 // Set other cookie parameters
4023 foreach ($parts as $part) {
4024 $part = trim($part);
4025 if (strtolower($part) == 'secure') {
4026 $secure = true;
4027 continue;
4028 }
4029
4030 $keyValue = explode('=', $part, 2);
4031 if (count($keyValue) == 2) {
4032 list($k, $v) = $keyValue;
4033 switch (strtolower($k)) {
4034 case 'expires':
4035 if(($expires = strtotime($v)) === false) {
4036 /**
4037 * The expiration is past Tue, 19 Jan 2038 03:14:07 UTC
4038 * the maximum for 32-bit signed integer. Zend_Date
4039 * can get around that limit.
4040 *
4041 * @see Zend_Date
4042 */
4043 require_once 'Zend/Date.php';
4044
4045 $expireDate = new Zend_Date($v);
4046 $expires = $expireDate->getTimestamp();
4047 }
4048 break;
4049
4050 case 'path':
4051 $path = $v;
4052 break;
4053
4054 case 'domain':
4055 $domain = $v;
4056 break;
4057
4058 default:
4059 break;
4060 }
4061 }
4062 }
4063
4064 if ($name !== '') {
4065 $ret = new self($name, $value, $domain, $expires, $path, $secure);
4066 $ret->encodeValue = ($encodeValue) ? true : false;
4067 return $ret;
4068 } else {
4069 return false;
4070 }
4071 }
4072
4073 /**
4074 * Check if a cookie's domain matches a host name.
4075 *
4076 * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching
4077 *
4078 * @param string $cookieDomain
4079 * @param string $host
4080 *
4081 * @return boolean
4082 */
4083 public static function matchCookieDomain($cookieDomain, $host) {
4084 if (! $cookieDomain) {
4085 if (!class_exists('Zend_Exception')) require 'exception.php';
4086 throw new Zend_Http_Exception("\$cookieDomain is expected to be a cookie domain");
4087 }
4088
4089 if (! $host) {
4090 if (!class_exists('Zend_Exception')) require 'exception.php';
4091 throw new Zend_Http_Exception("\$host is expected to be a host name");
4092 }
4093
4094 $cookieDomain = strtolower($cookieDomain);
4095 $host = strtolower($host);
4096
4097 if ($cookieDomain[0] == '.') {
4098 $cookieDomain = substr($cookieDomain, 1);
4099 }
4100
4101 // Check for either exact match or suffix match
4102 return ($cookieDomain == $host ||
4103 preg_match("/\.$cookieDomain$/", $host));
4104 }
4105
4106 /**
4107 * Check if a cookie's path matches a URL path
4108 *
4109 * Used by Zend_Http_Cookie and Zend_Http_CookieJar for cookie matching
4110 *
4111 * @param string $cookiePath
4112 * @param string $path
4113 * @return boolean
4114 */
4115 public static function matchCookiePath($cookiePath, $path) {
4116 if (! $cookiePath) {
4117 if (!class_exists('Zend_Exception')) require 'exception.php';
4118 throw new Zend_Http_Exception("\$cookiePath is expected to be a cookie path");
4119 }
4120
4121 if (! $path) {
4122 if (!class_exists('Zend_Exception')) require 'exception.php';
4123 throw new Zend_Http_Exception("\$path is expected to be a host name");
4124 }
4125
4126 return (strpos($path, $cookiePath) === 0);
4127 }
4128 }
4129
4130 /**
4131 * Zend_Http_Response represents an HTTP 1.0 / 1.1 response message. It
4132 * includes easy access to all the response's different elemts, as well as some
4133 * convenience methods for parsing and validating HTTP responses.
4134 *
4135 * @package Zend_Http
4136 * @subpackage Response
4137 * @version $Id: Response.php 17131 2009-07-26 10:03:39Z shahar $
4138 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
4139 * @license http://framework.zend.com/license/new-bsd New BSD License
4140 */
4141 class Zend_Http_Response_Stream extends Zend_Http_Response {
4142 /**
4143 * Response as stream
4144 *
4145 * @var resource
4146 */
4147 protected $stream;
4148
4149 /**
4150 * The name of the file containing the stream
4151 *
4152 * Will be empty if stream is not file-based.
4153 *
4154 * @var string
4155 */
4156 protected $stream_name;
4157
4158 /**
4159 * Should we clean up the stream file when this response is closed?
4160 *
4161 * @var boolean
4162 */
4163 protected $_cleanup;
4164
4165 /**
4166 * Get the response as stream
4167 *
4168 * @return resourse
4169 */
4170 public function getStream() {
4171 return $this->stream;
4172 }
4173
4174 /**
4175 * Set the response stream
4176 *
4177 * @param resourse $stream
4178 * @return Zend_Http_Response_Stream
4179 */
4180 public function setStream($stream) {
4181 $this->stream = $stream;
4182 return $this;
4183 }
4184
4185 /**
4186 * Get the cleanup trigger
4187 *
4188 * @return boolean
4189 */
4190 public function getCleanup() {
4191 return $this->_cleanup;
4192 }
4193
4194 /**
4195 * Set the cleanup trigger
4196 *
4197 * @param $cleanup Set cleanup trigger
4198 */
4199 public function setCleanup($cleanup = true) {
4200 $this->_cleanup = $cleanup;
4201 }
4202
4203 /**
4204 * Get file name associated with the stream
4205 *
4206 * @return string
4207 */
4208 public function getStreamName() {
4209 return $this->stream_name;
4210 }
4211
4212 /**
4213 * Set file name associated with the stream
4214 *
4215 * @param string $stream_name Name to set
4216 * @return Zend_Http_Response_Stream
4217 */
4218 public function setStreamName($stream_name) {
4219 $this->stream_name = $stream_name;
4220 return $this;
4221 }
4222
4223
4224 /**
4225 * HTTP response constructor
4226 *
4227 * In most cases, you would use Zend_Http_Response::fromString to parse an HTTP
4228 * response string and create a new Zend_Http_Response object.
4229 *
4230 * NOTE: The constructor no longer accepts nulls or empty values for the code and
4231 * headers and will throw an exception if the passed values do not form a valid HTTP
4232 * responses.
4233 *
4234 * If no message is passed, the message will be guessed according to the response code.
4235 *
4236 * @param int $code Response code (200, 404, ...)
4237 * @param array $headers Headers array
4238 * @param string $body Response body
4239 * @param string $version HTTP version
4240 * @param string $message Response code as text
4241 * @throws Zend_Http_Exception
4242 */
4243 public function __construct($code, $headers, $body = null, $version = '1.1', $message = null) {
4244
4245 if(is_resource($body)) {
4246 $this->setStream($body);
4247 $body = '';
4248 }
4249 parent::__construct($code, $headers, $body, $version, $message);
4250 }
4251
4252 /**
4253 * Create a new Zend_Http_Response_Stream object from a string
4254 *
4255 * @param string $response_str
4256 * @param resource $stream
4257 * @return Zend_Http_Response_Stream
4258 */
4259 public static function fromStream($response_str, $stream) {
4260 $code = self::extractCode($response_str);
4261 $headers = self::extractHeaders($response_str);
4262 $version = self::extractVersion($response_str);
4263 $message = self::extractMessage($response_str);
4264
4265 return new self($code, $headers, $stream, $version, $message);
4266 }
4267
4268 /**
4269 * Get the response body as string
4270 *
4271 * This method returns the body of the HTTP response (the content), as it
4272 * should be in it's readable version - that is, after decoding it (if it
4273 * was decoded), deflating it (if it was gzip compressed), etc.
4274 *
4275 * If you want to get the raw body (as transfered on wire) use
4276 * $this->getRawBody() instead.
4277 *
4278 * @return string
4279 */
4280 public function getBody() {
4281 if($this->stream != null) {
4282 $this->readStream();
4283 }
4284 return parent::getBody();
4285 }
4286
4287 /**
4288 * Get the raw response body (as transfered "on wire") as string
4289 *
4290 * If the body is encoded (with Transfer-Encoding, not content-encoding -
4291 * IE "chunked" body), gzip compressed, etc. it will not be decoded.
4292 *
4293 * @return string
4294 */
4295 public function getRawBody() {
4296 if($this->stream) {
4297 $this->readStream();
4298 }
4299 return $this->body;
4300 }
4301
4302 /**
4303 * Read stream content and return it as string
4304 *
4305 * Function reads the remainder of the body from the stream and closes the stream.
4306 *
4307 * @return string
4308 */
4309 protected function readStream() {
4310 if(!is_resource($this->stream)) {
4311 return '';
4312 }
4313
4314 if(isset($headers['content-length'])) {
4315 $this->body = stream_get_contents($this->stream, $headers['content-length']);
4316 } else {
4317 $this->body = stream_get_contents($this->stream);
4318 }
4319 fclose($this->stream);
4320 $this->stream = null;
4321 }
4322
4323 public function __destruct() {
4324 if(is_resource($this->stream)) {
4325 fclose($this->stream);
4326 $this->stream = null;
4327 }
4328 if($this->_cleanup) {
4329 @unlink($this->stream_name);
4330 }
4331 }
4332 }