source: extensions/Google2Piwigo/include/Zend/Http/Client/Adapter/Socket.php @ 17475

Last change on this file since 17475 was 17475, checked in by mistic100, 12 years ago

new extension: Google2Piwigo

File size: 18.0 KB
Line 
1<?php
2
3/**
4 * Zend Framework
5 *
6 * LICENSE
7 *
8 * This source file is subject to the new BSD license that is bundled
9 * with this package in the file LICENSE.txt.
10 * It is also available through the world-wide-web at this URL:
11 * http://framework.zend.com/license/new-bsd
12 * If you did not receive a copy of the license and are unable to
13 * obtain it through the world-wide-web, please send an email
14 * to license@zend.com so we can send you a copy immediately.
15 *
16 * @category   Zend
17 * @package    Zend_Http
18 * @subpackage Client_Adapter
19 * @version    $Id: Socket.php 24594 2012-01-05 21:27:01Z matthew $
20 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
21 * @license    http://framework.zend.com/license/new-bsd     New BSD License
22 */
23
24/**
25 * @see Zend_Uri_Http
26 */
27require_once 'Zend/Uri/Http.php';
28/**
29 * @see Zend_Http_Client_Adapter_Interface
30 */
31require_once 'Zend/Http/Client/Adapter/Interface.php';
32/**
33 * @see Zend_Http_Client_Adapter_Stream
34 */
35require_once 'Zend/Http/Client/Adapter/Stream.php';
36
37/**
38 * A sockets based (stream_socket_client) adapter class for Zend_Http_Client. Can be used
39 * on almost every PHP environment, and does not require any special extensions.
40 *
41 * @category   Zend
42 * @package    Zend_Http
43 * @subpackage Client_Adapter
44 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
45 * @license    http://framework.zend.com/license/new-bsd     New BSD License
46 */
47class Zend_Http_Client_Adapter_Socket implements Zend_Http_Client_Adapter_Interface, Zend_Http_Client_Adapter_Stream
48{
49    /**
50     * The socket for server connection
51     *
52     * @var resource|null
53     */
54    protected $socket = null;
55
56    /**
57     * What host/port are we connected to?
58     *
59     * @var array
60     */
61    protected $connected_to = array(null, null);
62
63    /**
64     * Stream for storing output
65     *
66     * @var resource
67     */
68    protected $out_stream = null;
69
70    /**
71     * Parameters array
72     *
73     * @var array
74     */
75    protected $config = array(
76        'persistent'    => false,
77        'ssltransport'  => 'ssl',
78        'sslcert'       => null,
79        'sslpassphrase' => null,
80        'sslusecontext' => false
81    );
82
83    /**
84     * Request method - will be set by write() and might be used by read()
85     *
86     * @var string
87     */
88    protected $method = null;
89
90    /**
91     * Stream context
92     *
93     * @var resource
94     */
95    protected $_context = null;
96
97    /**
98     * Adapter constructor, currently empty. Config is set using setConfig()
99     *
100     */
101    public function __construct()
102    {
103    }
104
105    /**
106     * Set the configuration array for the adapter
107     *
108     * @param Zend_Config | array $config
109     */
110    public function setConfig($config = array())
111    {
112        if ($config instanceof Zend_Config) {
113            $config = $config->toArray();
114
115        } elseif (! is_array($config)) {
116            require_once 'Zend/Http/Client/Adapter/Exception.php';
117            throw new Zend_Http_Client_Adapter_Exception(
118                'Array or Zend_Config object expected, got ' . gettype($config)
119            );
120        }
121
122        foreach ($config as $k => $v) {
123            $this->config[strtolower($k)] = $v;
124        }
125    }
126
127    /**
128      * Retrieve the array of all configuration options
129      *
130      * @return array
131      */
132     public function getConfig()
133     {
134         return $this->config;
135     }
136
137     /**
138     * Set the stream context for the TCP connection to the server
139     *
140     * Can accept either a pre-existing stream context resource, or an array
141     * of stream options, similar to the options array passed to the
142     * stream_context_create() PHP function. In such case a new stream context
143     * will be created using the passed options.
144     *
145     * @since  Zend Framework 1.9
146     *
147     * @param  mixed $context Stream context or array of context options
148     * @return Zend_Http_Client_Adapter_Socket
149     */
150    public function setStreamContext($context)
151    {
152        if (is_resource($context) && get_resource_type($context) == 'stream-context') {
153            $this->_context = $context;
154
155        } elseif (is_array($context)) {
156            $this->_context = stream_context_create($context);
157
158        } else {
159            // Invalid parameter
160            require_once 'Zend/Http/Client/Adapter/Exception.php';
161            throw new Zend_Http_Client_Adapter_Exception(
162                "Expecting either a stream context resource or array, got " . gettype($context)
163            );
164        }
165
166        return $this;
167    }
168
169    /**
170     * Get the stream context for the TCP connection to the server.
171     *
172     * If no stream context is set, will create a default one.
173     *
174     * @return resource
175     */
176    public function getStreamContext()
177    {
178        if (! $this->_context) {
179            $this->_context = stream_context_create();
180        }
181
182        return $this->_context;
183    }
184
185    /**
186     * Connect to the remote server
187     *
188     * @param string  $host
189     * @param int     $port
190     * @param boolean $secure
191     */
192    public function connect($host, $port = 80, $secure = false)
193    {
194        // If the URI should be accessed via SSL, prepend the Hostname with ssl://
195        $host = ($secure ? $this->config['ssltransport'] : 'tcp') . '://' . $host;
196
197        // If we are connected to the wrong host, disconnect first
198        if (($this->connected_to[0] != $host || $this->connected_to[1] != $port)) {
199            if (is_resource($this->socket)) $this->close();
200        }
201
202        // Now, if we are not connected, connect
203        if (! is_resource($this->socket) || ! $this->config['keepalive']) {
204            $context = $this->getStreamContext();
205            if ($secure || $this->config['sslusecontext']) {
206                if ($this->config['sslcert'] !== null) {
207                    if (! stream_context_set_option($context, 'ssl', 'local_cert',
208                                                    $this->config['sslcert'])) {
209                        require_once 'Zend/Http/Client/Adapter/Exception.php';
210                        throw new Zend_Http_Client_Adapter_Exception('Unable to set sslcert option');
211                    }
212                }
213                if ($this->config['sslpassphrase'] !== null) {
214                    if (! stream_context_set_option($context, 'ssl', 'passphrase',
215                                                    $this->config['sslpassphrase'])) {
216                        require_once 'Zend/Http/Client/Adapter/Exception.php';
217                        throw new Zend_Http_Client_Adapter_Exception('Unable to set sslpassphrase option');
218                    }
219                }
220            }
221
222            $flags = STREAM_CLIENT_CONNECT;
223            if ($this->config['persistent']) $flags |= STREAM_CLIENT_PERSISTENT;
224
225            $this->socket = @stream_socket_client($host . ':' . $port,
226                                                  $errno,
227                                                  $errstr,
228                                                  (int) $this->config['timeout'],
229                                                  $flags,
230                                                  $context);
231
232            if (! $this->socket) {
233                $this->close();
234                require_once 'Zend/Http/Client/Adapter/Exception.php';
235                throw new Zend_Http_Client_Adapter_Exception(
236                    'Unable to Connect to ' . $host . ':' . $port . '. Error #' . $errno . ': ' . $errstr);
237            }
238
239            // Set the stream timeout
240            if (! stream_set_timeout($this->socket, (int) $this->config['timeout'])) {
241                require_once 'Zend/Http/Client/Adapter/Exception.php';
242                throw new Zend_Http_Client_Adapter_Exception('Unable to set the connection timeout');
243            }
244
245            // Update connected_to
246            $this->connected_to = array($host, $port);
247        }
248    }
249
250    /**
251     * Send request to the remote server
252     *
253     * @param string        $method
254     * @param Zend_Uri_Http $uri
255     * @param string        $http_ver
256     * @param array         $headers
257     * @param string        $body
258     * @return string Request as string
259     */
260    public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
261    {
262        // Make sure we're properly connected
263        if (! $this->socket) {
264            require_once 'Zend/Http/Client/Adapter/Exception.php';
265            throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are not connected');
266        }
267
268        $host = $uri->getHost();
269        $host = (strtolower($uri->getScheme()) == 'https' ? $this->config['ssltransport'] : 'tcp') . '://' . $host;
270        if ($this->connected_to[0] != $host || $this->connected_to[1] != $uri->getPort()) {
271            require_once 'Zend/Http/Client/Adapter/Exception.php';
272            throw new Zend_Http_Client_Adapter_Exception('Trying to write but we are connected to the wrong host');
273        }
274
275        // Save request method for later
276        $this->method = $method;
277
278        // Build request headers
279        $path = $uri->getPath();
280        if ($uri->getQuery()) $path .= '?' . $uri->getQuery();
281        $request = "{$method} {$path} HTTP/{$http_ver}\r\n";
282        foreach ($headers as $k => $v) {
283            if (is_string($k)) $v = ucfirst($k) . ": $v";
284            $request .= "$v\r\n";
285        }
286
287        if(is_resource($body)) {
288            $request .= "\r\n";
289        } else {
290            // Add the request body
291            $request .= "\r\n" . $body;
292        }
293
294        // Send the request
295        if (! @fwrite($this->socket, $request)) {
296            require_once 'Zend/Http/Client/Adapter/Exception.php';
297            throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
298        }
299
300        if(is_resource($body)) {
301            if(stream_copy_to_stream($body, $this->socket) == 0) {
302                require_once 'Zend/Http/Client/Adapter/Exception.php';
303                throw new Zend_Http_Client_Adapter_Exception('Error writing request to server');
304            }
305        }
306
307        return $request;
308    }
309
310    /**
311     * Read response from server
312     *
313     * @return string
314     */
315    public function read()
316    {
317        // First, read headers only
318        $response = '';
319        $gotStatus = false;
320
321        while (($line = @fgets($this->socket)) !== false) {
322            $gotStatus = $gotStatus || (strpos($line, 'HTTP') !== false);
323            if ($gotStatus) {
324                $response .= $line;
325                if (rtrim($line) === '') break;
326            }
327        }
328
329        $this->_checkSocketReadTimeout();
330
331        $statusCode = Zend_Http_Response::extractCode($response);
332
333        // Handle 100 and 101 responses internally by restarting the read again
334        if ($statusCode == 100 || $statusCode == 101) return $this->read();
335
336        // Check headers to see what kind of connection / transfer encoding we have
337        $headers = Zend_Http_Response::extractHeaders($response);
338
339        /**
340         * Responses to HEAD requests and 204 or 304 responses are not expected
341         * to have a body - stop reading here
342         */
343        if ($statusCode == 304 || $statusCode == 204 ||
344            $this->method == Zend_Http_Client::HEAD) {
345
346            // Close the connection if requested to do so by the server
347            if (isset($headers['connection']) && $headers['connection'] == 'close') {
348                $this->close();
349            }
350            return $response;
351        }
352
353        // If we got a 'transfer-encoding: chunked' header
354        if (isset($headers['transfer-encoding'])) {
355
356            if (strtolower($headers['transfer-encoding']) == 'chunked') {
357
358                do {
359                    $line  = @fgets($this->socket);
360                    $this->_checkSocketReadTimeout();
361
362                    $chunk = $line;
363
364                    // Figure out the next chunk size
365                    $chunksize = trim($line);
366                    if (! ctype_xdigit($chunksize)) {
367                        $this->close();
368                        require_once 'Zend/Http/Client/Adapter/Exception.php';
369                        throw new Zend_Http_Client_Adapter_Exception('Invalid chunk size "' .
370                            $chunksize . '" unable to read chunked body');
371                    }
372
373                    // Convert the hexadecimal value to plain integer
374                    $chunksize = hexdec($chunksize);
375
376                    // Read next chunk
377                    $read_to = ftell($this->socket) + $chunksize;
378
379                    do {
380                        $current_pos = ftell($this->socket);
381                        if ($current_pos >= $read_to) break;
382
383                        if($this->out_stream) {
384                            if(stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
385                              $this->_checkSocketReadTimeout();
386                              break;
387                             }
388                        } else {
389                            $line = @fread($this->socket, $read_to - $current_pos);
390                            if ($line === false || strlen($line) === 0) {
391                                $this->_checkSocketReadTimeout();
392                                break;
393                            }
394                                    $chunk .= $line;
395                        }
396                    } while (! feof($this->socket));
397
398                    $chunk .= @fgets($this->socket);
399                    $this->_checkSocketReadTimeout();
400
401                    if(!$this->out_stream) {
402                        $response .= $chunk;
403                    }
404                } while ($chunksize > 0);
405            } else {
406                $this->close();
407        require_once 'Zend/Http/Client/Adapter/Exception.php';
408                throw new Zend_Http_Client_Adapter_Exception('Cannot handle "' .
409                    $headers['transfer-encoding'] . '" transfer encoding');
410            }
411
412            // We automatically decode chunked-messages when writing to a stream
413            // this means we have to disallow the Zend_Http_Response to do it again
414            if ($this->out_stream) {
415                $response = str_ireplace("Transfer-Encoding: chunked\r\n", '', $response);
416            }
417        // Else, if we got the content-length header, read this number of bytes
418        } elseif (isset($headers['content-length'])) {
419
420            // If we got more than one Content-Length header (see ZF-9404) use
421            // the last value sent
422            if (is_array($headers['content-length'])) {
423                $contentLength = $headers['content-length'][count($headers['content-length']) - 1];
424            } else {
425                $contentLength = $headers['content-length'];
426            }
427
428            $current_pos = ftell($this->socket);
429            $chunk = '';
430
431            for ($read_to = $current_pos + $contentLength;
432                 $read_to > $current_pos;
433                 $current_pos = ftell($this->socket)) {
434
435                 if($this->out_stream) {
436                     if(@stream_copy_to_stream($this->socket, $this->out_stream, $read_to - $current_pos) == 0) {
437                          $this->_checkSocketReadTimeout();
438                          break;
439                     }
440                 } else {
441                    $chunk = @fread($this->socket, $read_to - $current_pos);
442                    if ($chunk === false || strlen($chunk) === 0) {
443                        $this->_checkSocketReadTimeout();
444                        break;
445                    }
446
447                    $response .= $chunk;
448                }
449
450                // Break if the connection ended prematurely
451                if (feof($this->socket)) break;
452            }
453
454        // Fallback: just read the response until EOF
455        } else {
456
457            do {
458                if($this->out_stream) {
459                    if(@stream_copy_to_stream($this->socket, $this->out_stream) == 0) {
460                          $this->_checkSocketReadTimeout();
461                          break;
462                     }
463                }  else {
464                    $buff = @fread($this->socket, 8192);
465                    if ($buff === false || strlen($buff) === 0) {
466                        $this->_checkSocketReadTimeout();
467                        break;
468                    } else {
469                        $response .= $buff;
470                    }
471                }
472
473            } while (feof($this->socket) === false);
474
475            $this->close();
476        }
477
478        // Close the connection if requested to do so by the server
479        if (isset($headers['connection']) && $headers['connection'] == 'close') {
480            $this->close();
481        }
482
483        return $response;
484    }
485
486    /**
487     * Close the connection to the server
488     *
489     */
490    public function close()
491    {
492        if (is_resource($this->socket)) @fclose($this->socket);
493        $this->socket = null;
494        $this->connected_to = array(null, null);
495    }
496
497    /**
498     * Check if the socket has timed out - if so close connection and throw
499     * an exception
500     *
501     * @throws Zend_Http_Client_Adapter_Exception with READ_TIMEOUT code
502     */
503    protected function _checkSocketReadTimeout()
504    {
505        if ($this->socket) {
506            $info = stream_get_meta_data($this->socket);
507            $timedout = $info['timed_out'];
508            if ($timedout) {
509                $this->close();
510                require_once 'Zend/Http/Client/Adapter/Exception.php';
511                throw new Zend_Http_Client_Adapter_Exception(
512                    "Read timed out after {$this->config['timeout']} seconds",
513                    Zend_Http_Client_Adapter_Exception::READ_TIMEOUT
514                );
515            }
516        }
517    }
518
519    /**
520     * Set output stream for the response
521     *
522     * @param resource $stream
523     * @return Zend_Http_Client_Adapter_Socket
524     */
525    public function setOutputStream($stream)
526    {
527        $this->out_stream = $stream;
528        return $this;
529    }
530
531    /**
532     * Destructor: make sure the socket is disconnected
533     *
534     * If we are in persistent TCP mode, will not close the connection
535     *
536     */
537    public function __destruct()
538    {
539        if (! $this->config['persistent']) {
540            if ($this->socket) $this->close();
541        }
542    }
543}
Note: See TracBrowser for help on using the repository browser.