]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/HTTP/Request2/SocketWrapper.php
Merge commit 'refs/merge-requests/199' of git://gitorious.org/statusnet/mainline...
[quix0rs-gnu-social.git] / extlib / HTTP / Request2 / SocketWrapper.php
1 <?php\r
2 /**\r
3  * Socket wrapper class used by Socket Adapter\r
4  *\r
5  * PHP version 5\r
6  *\r
7  * LICENSE:\r
8  *\r
9  * Copyright (c) 2008-2012, Alexey Borzov <avb@php.net>\r
10  * All rights reserved.\r
11  *\r
12  * Redistribution and use in source and binary forms, with or without\r
13  * modification, are permitted provided that the following conditions\r
14  * are met:\r
15  *\r
16  *    * Redistributions of source code must retain the above copyright\r
17  *      notice, this list of conditions and the following disclaimer.\r
18  *    * Redistributions in binary form must reproduce the above copyright\r
19  *      notice, this list of conditions and the following disclaimer in the\r
20  *      documentation and/or other materials provided with the distribution.\r
21  *    * The names of the authors may not be used to endorse or promote products\r
22  *      derived from this software without specific prior written permission.\r
23  *\r
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\r
25  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r
26  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
27  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r
28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r
29  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r
30  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r
31  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\r
32  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r
33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r
34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
35  *\r
36  * @category HTTP\r
37  * @package  HTTP_Request2\r
38  * @author   Alexey Borzov <avb@php.net>\r
39  * @license  http://opensource.org/licenses/bsd-license.php New BSD License\r
40  * @version  SVN: $Id: SocketWrapper.php 324935 2012-04-07 07:10:50Z avb $\r
41  * @link     http://pear.php.net/package/HTTP_Request2\r
42  */\r
43 \r
44 /** Exception classes for HTTP_Request2 package */\r
45 require_once 'HTTP/Request2/Exception.php';\r
46 \r
47 /**\r
48  * Socket wrapper class used by Socket Adapter\r
49  *\r
50  * Needed to properly handle connection errors, global timeout support and\r
51  * similar things. Loosely based on Net_Socket used by older HTTP_Request.\r
52  *\r
53  * @category HTTP\r
54  * @package  HTTP_Request2\r
55  * @author   Alexey Borzov <avb@php.net>\r
56  * @license  http://opensource.org/licenses/bsd-license.php New BSD License\r
57  * @version  Release: 2.1.1\r
58  * @link     http://pear.php.net/package/HTTP_Request2\r
59  * @link     http://pear.php.net/bugs/bug.php?id=19332\r
60  * @link     http://tools.ietf.org/html/rfc1928\r
61  */\r
62 class HTTP_Request2_SocketWrapper\r
63 {\r
64     /**\r
65      * PHP warning messages raised during stream_socket_client() call\r
66      * @var array\r
67      */\r
68     protected $connectionWarnings = array();\r
69 \r
70     /**\r
71      * Connected socket\r
72      * @var resource\r
73      */\r
74     protected $socket;\r
75 \r
76     /**\r
77      * Sum of start time and global timeout, exception will be thrown if request continues past this time\r
78      * @var  integer\r
79      */\r
80     protected $deadline;\r
81 \r
82     /**\r
83      * Global timeout value, mostly for exception messages\r
84      * @var integer\r
85      */\r
86     protected $timeout;\r
87 \r
88     /**\r
89      * Class constructor, tries to establish connection\r
90      *\r
91      * @param string $address    Address for stream_socket_client() call,\r
92      *                           e.g. 'tcp://localhost:80'\r
93      * @param int    $timeout    Connection timeout (seconds)\r
94      * @param array  $sslOptions SSL context options\r
95      *\r
96      * @throws HTTP_Request2_LogicException\r
97      * @throws HTTP_Request2_ConnectionException\r
98      */\r
99     public function __construct($address, $timeout, array $sslOptions = array())\r
100     {\r
101         $context = stream_context_create();\r
102         foreach ($sslOptions as $name => $value) {\r
103             if (!stream_context_set_option($context, 'ssl', $name, $value)) {\r
104                 throw new HTTP_Request2_LogicException(\r
105                     "Error setting SSL context option '{$name}'"\r
106                 );\r
107             }\r
108         }\r
109         set_error_handler(array($this, 'connectionWarningsHandler'));\r
110         $this->socket = stream_socket_client(\r
111             $address, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context\r
112         );\r
113         restore_error_handler();\r
114         if (!$this->socket) {\r
115             $error = $errstr ? $errstr : implode("\n", $this->connectionWarnings);\r
116             throw new HTTP_Request2_ConnectionException(\r
117                 "Unable to connect to {$address}. Error: {$error}", 0, $errno\r
118             );\r
119         }\r
120     }\r
121 \r
122     /**\r
123      * Destructor, disconnects socket\r
124      */\r
125     public function __destruct()\r
126     {\r
127         fclose($this->socket);\r
128     }\r
129 \r
130     /**\r
131      * Wrapper around fread(), handles global request timeout\r
132      *\r
133      * @param int $length Reads up to this number of bytes\r
134      *\r
135      * @return   string Data read from socket\r
136      * @throws   HTTP_Request2_MessageException     In case of timeout\r
137      */\r
138     public function read($length)\r
139     {\r
140         if ($this->deadline) {\r
141             stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
142         }\r
143         $data = fread($this->socket, $length);\r
144         $this->checkTimeout();\r
145         return $data;\r
146     }\r
147 \r
148     /**\r
149      * Reads until either the end of the socket or a newline, whichever comes first\r
150      *\r
151      * Strips the trailing newline from the returned data, handles global\r
152      * request timeout. Method idea borrowed from Net_Socket PEAR package.\r
153      *\r
154      * @param int $bufferSize buffer size to use for reading\r
155      *\r
156      * @return   string Available data up to the newline (not including newline)\r
157      * @throws   HTTP_Request2_MessageException     In case of timeout\r
158      */\r
159     public function readLine($bufferSize)\r
160     {\r
161         $line = '';\r
162         while (!feof($this->socket)) {\r
163             if ($this->deadline) {\r
164                 stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
165             }\r
166             $line .= @fgets($this->socket, $bufferSize);\r
167             $this->checkTimeout();\r
168             if (substr($line, -1) == "\n") {\r
169                 return rtrim($line, "\r\n");\r
170             }\r
171         }\r
172         return $line;\r
173     }\r
174 \r
175     /**\r
176      * Wrapper around fwrite(), handles global request timeout\r
177      *\r
178      * @param string $data String to be written\r
179      *\r
180      * @return int\r
181      * @throws HTTP_Request2_MessageException\r
182      */\r
183     public function write($data)\r
184     {\r
185         if ($this->deadline) {\r
186             stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
187         }\r
188         $written = fwrite($this->socket, $data);\r
189         $this->checkTimeout();\r
190         // http://www.php.net/manual/en/function.fwrite.php#96951\r
191         if ($written < strlen($data)) {\r
192             throw new HTTP_Request2_MessageException('Error writing request');\r
193         }\r
194         return $written;\r
195     }\r
196 \r
197     /**\r
198      * Tests for end-of-file on a socket\r
199      *\r
200      * @return bool\r
201      */\r
202     public function eof()\r
203     {\r
204         return feof($this->socket);\r
205     }\r
206 \r
207     /**\r
208      * Sets request deadline\r
209      *\r
210      * @param int $deadline Exception will be thrown if request continues\r
211      *                      past this time\r
212      * @param int $timeout  Original request timeout value, to use in\r
213      *                      Exception message\r
214      */\r
215     public function setDeadline($deadline, $timeout)\r
216     {\r
217         $this->deadline = $deadline;\r
218         $this->timeout  = $timeout;\r
219     }\r
220 \r
221     /**\r
222      * Turns on encryption on a socket\r
223      *\r
224      * @throws HTTP_Request2_ConnectionException\r
225      */\r
226     public function enableCrypto()\r
227     {\r
228         $modes = array(\r
229             STREAM_CRYPTO_METHOD_TLS_CLIENT,\r
230             STREAM_CRYPTO_METHOD_SSLv3_CLIENT,\r
231             STREAM_CRYPTO_METHOD_SSLv23_CLIENT,\r
232             STREAM_CRYPTO_METHOD_SSLv2_CLIENT\r
233         );\r
234 \r
235         foreach ($modes as $mode) {\r
236             if (stream_socket_enable_crypto($this->socket, true, $mode)) {\r
237                 return;\r
238             }\r
239         }\r
240         throw new HTTP_Request2_ConnectionException(\r
241             'Failed to enable secure connection when connecting through proxy'\r
242         );\r
243     }\r
244 \r
245     /**\r
246      * Throws an Exception if stream timed out\r
247      *\r
248      * @throws HTTP_Request2_MessageException\r
249      */\r
250     protected function checkTimeout()\r
251     {\r
252         $info = stream_get_meta_data($this->socket);\r
253         if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {\r
254             $reason = $this->deadline\r
255                 ? "after {$this->timeout} second(s)"\r
256                 : 'due to default_socket_timeout php.ini setting';\r
257             throw new HTTP_Request2_MessageException(\r
258                 "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT\r
259             );\r
260         }\r
261     }\r
262 \r
263     /**\r
264      * Error handler to use during stream_socket_client() call\r
265      *\r
266      * One stream_socket_client() call may produce *multiple* PHP warnings\r
267      * (especially OpenSSL-related), we keep them in an array to later use for\r
268      * the message of HTTP_Request2_ConnectionException\r
269      *\r
270      * @param int    $errno  error level\r
271      * @param string $errstr error message\r
272      *\r
273      * @return bool\r
274      */\r
275     protected function connectionWarningsHandler($errno, $errstr)\r
276     {\r
277         if ($errno & E_WARNING) {\r
278             array_unshift($this->connectionWarnings, $errstr);\r
279         }\r
280         return true;\r
281     }\r
282 }\r
283 ?>\r