]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/HTTP/Request2/SocketWrapper.php
PEAR::HTTP_Request2 updated to 2.2.1
[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  * This source file is subject to BSD 3-Clause License that is bundled\r
10  * with this package in the file LICENSE and available at the URL\r
11  * https://raw.github.com/pear/HTTP_Request2/trunk/docs/LICENSE\r
12  *\r
13  * @category  HTTP\r
14  * @package   HTTP_Request2\r
15  * @author    Alexey Borzov <avb@php.net>\r
16  * @copyright 2008-2014 Alexey Borzov <avb@php.net>\r
17  * @license   http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License\r
18  * @link      http://pear.php.net/package/HTTP_Request2\r
19  */\r
20 \r
21 /** Exception classes for HTTP_Request2 package */\r
22 require_once 'HTTP/Request2/Exception.php';\r
23 \r
24 /**\r
25  * Socket wrapper class used by Socket Adapter\r
26  *\r
27  * Needed to properly handle connection errors, global timeout support and\r
28  * similar things. Loosely based on Net_Socket used by older HTTP_Request.\r
29  *\r
30  * @category HTTP\r
31  * @package  HTTP_Request2\r
32  * @author   Alexey Borzov <avb@php.net>\r
33  * @license  http://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License\r
34  * @version  Release: 2.2.1\r
35  * @link     http://pear.php.net/package/HTTP_Request2\r
36  * @link     http://pear.php.net/bugs/bug.php?id=19332\r
37  * @link     http://tools.ietf.org/html/rfc1928\r
38  */\r
39 class HTTP_Request2_SocketWrapper\r
40 {\r
41     /**\r
42      * PHP warning messages raised during stream_socket_client() call\r
43      * @var array\r
44      */\r
45     protected $connectionWarnings = array();\r
46 \r
47     /**\r
48      * Connected socket\r
49      * @var resource\r
50      */\r
51     protected $socket;\r
52 \r
53     /**\r
54      * Sum of start time and global timeout, exception will be thrown if request continues past this time\r
55      * @var  integer\r
56      */\r
57     protected $deadline;\r
58 \r
59     /**\r
60      * Global timeout value, mostly for exception messages\r
61      * @var integer\r
62      */\r
63     protected $timeout;\r
64 \r
65     /**\r
66      * Class constructor, tries to establish connection\r
67      *\r
68      * @param string $address        Address for stream_socket_client() call,\r
69      *                               e.g. 'tcp://localhost:80'\r
70      * @param int    $timeout        Connection timeout (seconds)\r
71      * @param array  $contextOptions Context options\r
72      *\r
73      * @throws HTTP_Request2_LogicException\r
74      * @throws HTTP_Request2_ConnectionException\r
75      */\r
76     public function __construct($address, $timeout, array $contextOptions = array())\r
77     {\r
78         if (!empty($contextOptions)\r
79             && !isset($contextOptions['socket']) && !isset($contextOptions['ssl'])\r
80         ) {\r
81             // Backwards compatibility with 2.1.0 and 2.1.1 releases\r
82             $contextOptions = array('ssl' => $contextOptions);\r
83         }\r
84         $context = stream_context_create();\r
85         foreach ($contextOptions as $wrapper => $options) {\r
86             foreach ($options as $name => $value) {\r
87                 if (!stream_context_set_option($context, $wrapper, $name, $value)) {\r
88                     throw new HTTP_Request2_LogicException(\r
89                         "Error setting '{$wrapper}' wrapper context option '{$name}'"\r
90                     );\r
91                 }\r
92             }\r
93         }\r
94         set_error_handler(array($this, 'connectionWarningsHandler'));\r
95         $this->socket = stream_socket_client(\r
96             $address, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context\r
97         );\r
98         restore_error_handler();\r
99         // if we fail to bind to a specified local address (see request #19515),\r
100         // connection still succeeds, albeit with a warning. Throw an Exception\r
101         // with the warning text in this case as that connection is unlikely\r
102         // to be what user wants and as Curl throws an error in similar case.\r
103         if ($this->connectionWarnings) {\r
104             if ($this->socket) {\r
105                 fclose($this->socket);\r
106             }\r
107             $error = $errstr ? $errstr : implode("\n", $this->connectionWarnings);\r
108             throw new HTTP_Request2_ConnectionException(\r
109                 "Unable to connect to {$address}. Error: {$error}", 0, $errno\r
110             );\r
111         }\r
112     }\r
113 \r
114     /**\r
115      * Destructor, disconnects socket\r
116      */\r
117     public function __destruct()\r
118     {\r
119         fclose($this->socket);\r
120     }\r
121 \r
122     /**\r
123      * Wrapper around fread(), handles global request timeout\r
124      *\r
125      * @param int $length Reads up to this number of bytes\r
126      *\r
127      * @return   string Data read from socket\r
128      * @throws   HTTP_Request2_MessageException     In case of timeout\r
129      */\r
130     public function read($length)\r
131     {\r
132         if ($this->deadline) {\r
133             stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
134         }\r
135         $data = fread($this->socket, $length);\r
136         $this->checkTimeout();\r
137         return $data;\r
138     }\r
139 \r
140     /**\r
141      * Reads until either the end of the socket or a newline, whichever comes first\r
142      *\r
143      * Strips the trailing newline from the returned data, handles global\r
144      * request timeout. Method idea borrowed from Net_Socket PEAR package.\r
145      *\r
146      * @param int $bufferSize   buffer size to use for reading\r
147      * @param int $localTimeout timeout value to use just for this call\r
148      *                          (used when waiting for "100 Continue" response)\r
149      *\r
150      * @return   string Available data up to the newline (not including newline)\r
151      * @throws   HTTP_Request2_MessageException     In case of timeout\r
152      */\r
153     public function readLine($bufferSize, $localTimeout = null)\r
154     {\r
155         $line = '';\r
156         while (!feof($this->socket)) {\r
157             if (null !== $localTimeout) {\r
158                 stream_set_timeout($this->socket, $localTimeout);\r
159             } elseif ($this->deadline) {\r
160                 stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
161             }\r
162 \r
163             $line .= @fgets($this->socket, $bufferSize);\r
164 \r
165             if (null === $localTimeout) {\r
166                 $this->checkTimeout();\r
167 \r
168             } else {\r
169                 $info = stream_get_meta_data($this->socket);\r
170                 // reset socket timeout if we don't have request timeout specified,\r
171                 // prevents further calls failing with a bogus Exception\r
172                 if (!$this->deadline) {\r
173                     $default = (int)@ini_get('default_socket_timeout');\r
174                     stream_set_timeout($this->socket, $default > 0 ? $default : PHP_INT_MAX);\r
175                 }\r
176                 if ($info['timed_out']) {\r
177                     throw new HTTP_Request2_MessageException(\r
178                         "readLine() call timed out", HTTP_Request2_Exception::TIMEOUT\r
179                     );\r
180                 }\r
181             }\r
182             if (substr($line, -1) == "\n") {\r
183                 return rtrim($line, "\r\n");\r
184             }\r
185         }\r
186         return $line;\r
187     }\r
188 \r
189     /**\r
190      * Wrapper around fwrite(), handles global request timeout\r
191      *\r
192      * @param string $data String to be written\r
193      *\r
194      * @return int\r
195      * @throws HTTP_Request2_MessageException\r
196      */\r
197     public function write($data)\r
198     {\r
199         if ($this->deadline) {\r
200             stream_set_timeout($this->socket, max($this->deadline - time(), 1));\r
201         }\r
202         $written = fwrite($this->socket, $data);\r
203         $this->checkTimeout();\r
204         // http://www.php.net/manual/en/function.fwrite.php#96951\r
205         if ($written < strlen($data)) {\r
206             throw new HTTP_Request2_MessageException('Error writing request');\r
207         }\r
208         return $written;\r
209     }\r
210 \r
211     /**\r
212      * Tests for end-of-file on a socket\r
213      *\r
214      * @return bool\r
215      */\r
216     public function eof()\r
217     {\r
218         return feof($this->socket);\r
219     }\r
220 \r
221     /**\r
222      * Sets request deadline\r
223      *\r
224      * @param int $deadline Exception will be thrown if request continues\r
225      *                      past this time\r
226      * @param int $timeout  Original request timeout value, to use in\r
227      *                      Exception message\r
228      */\r
229     public function setDeadline($deadline, $timeout)\r
230     {\r
231         $this->deadline = $deadline;\r
232         $this->timeout  = $timeout;\r
233     }\r
234 \r
235     /**\r
236      * Turns on encryption on a socket\r
237      *\r
238      * @throws HTTP_Request2_ConnectionException\r
239      */\r
240     public function enableCrypto()\r
241     {\r
242         $modes = array(\r
243             STREAM_CRYPTO_METHOD_TLS_CLIENT,\r
244             STREAM_CRYPTO_METHOD_SSLv3_CLIENT,\r
245             STREAM_CRYPTO_METHOD_SSLv23_CLIENT,\r
246             STREAM_CRYPTO_METHOD_SSLv2_CLIENT\r
247         );\r
248 \r
249         foreach ($modes as $mode) {\r
250             if (stream_socket_enable_crypto($this->socket, true, $mode)) {\r
251                 return;\r
252             }\r
253         }\r
254         throw new HTTP_Request2_ConnectionException(\r
255             'Failed to enable secure connection when connecting through proxy'\r
256         );\r
257     }\r
258 \r
259     /**\r
260      * Throws an Exception if stream timed out\r
261      *\r
262      * @throws HTTP_Request2_MessageException\r
263      */\r
264     protected function checkTimeout()\r
265     {\r
266         $info = stream_get_meta_data($this->socket);\r
267         if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {\r
268             $reason = $this->deadline\r
269                 ? "after {$this->timeout} second(s)"\r
270                 : 'due to default_socket_timeout php.ini setting';\r
271             throw new HTTP_Request2_MessageException(\r
272                 "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT\r
273             );\r
274         }\r
275     }\r
276 \r
277     /**\r
278      * Error handler to use during stream_socket_client() call\r
279      *\r
280      * One stream_socket_client() call may produce *multiple* PHP warnings\r
281      * (especially OpenSSL-related), we keep them in an array to later use for\r
282      * the message of HTTP_Request2_ConnectionException\r
283      *\r
284      * @param int    $errno  error level\r
285      * @param string $errstr error message\r
286      *\r
287      * @return bool\r
288      */\r
289     protected function connectionWarningsHandler($errno, $errstr)\r
290     {\r
291         if ($errno & E_WARNING) {\r
292             array_unshift($this->connectionWarnings, $errstr);\r
293         }\r
294         return true;\r
295     }\r
296 }\r
297 ?>\r