source: trunk/www.guidonia.net/wp/wp-content/plugins/tubepress/classes/net/php/pear/HTTP/Request2/Adapter/Socket.class.php@ 44

Last change on this file since 44 was 44, checked in by luciano, 14 years ago
File size: 16.4 KB
Line 
1<?php
2/**
3 * Socket-based adapter for HTTP_Request2
4 *
5 * PHP version 5
6 *
7 * LICENSE:
8 *
9 * Copyright (c) 2008, Alexey Borzov <avb@php.net>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * * Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * * Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * * The names of the authors may not be used to endorse or promote products
22 * derived from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
25 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
26 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
32 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 *
36 * @category HTTP
37 * @package HTTP_Request2
38 * @author Alexey Borzov <avb@php.net>
39 * @license http://opensource.org/licenses/bsd-license.php New BSD License
40 * @version CVS: $Id: Socket.php,v 1.2 2008/11/17 12:30:12 avb Exp $
41 * @link http://pear.php.net/package/HTTP_Request2
42 */
43
44/**
45 * Base class for HTTP_Request2 adapters
46 */
47
48function_exists('tubepress_load_classes')
49 || require(dirname(__FILE__) . '/../../../../../../tubepress_classloader.php');
50tubepress_load_classes(array('net_php_pear_HTTP_Request2_Adapter',
51 'net_php_pear_HTTP_Request2',
52 'net_php_pear_HTTP_Request2_Exception',
53 'net_php_pear_HTTP_Request2_Response'));
54
55/**
56 * Socket-based adapter for HTTP_Request2
57 *
58 * This adapter uses only PHP sockets and will work on almost any PHP
59 * environment. Code is based on original HTTP_Request PEAR package.
60 *
61 * @category HTTP
62 * @package HTTP_Request2
63 * @author Alexey Borzov <avb@php.net>
64 * @version Release: 0.1.0
65 * @todo Implement HTTPS proxy support via stream_socket_enable_crypto()
66 * @todo Implement Digest authentication support
67 * @todo Proper read timeout handling
68 * @todo Support various SSL options
69 */
70class net_php_pear_HTTP_Request2_Adapter_Socket extends net_php_pear_HTTP_Request2_Adapter
71{
72 /**
73 * Connected sockets, needed for Keep-Alive support
74 * @var array
75 * @see connect()
76 */
77 protected static $sockets;
78
79 /**
80 * Connected socket
81 * @var resource
82 * @see connect()
83 */
84 protected $socket;
85
86 /**
87 * Remaining length of the current chunk, when reading chunked response
88 * @var integer
89 * @see readChunked()
90 */
91 protected $chunkLength = 0;
92
93 /**
94 * Sends request to the remote server and returns its response
95 *
96 * @param HTTP_Request2
97 * @return net_php_pear_HTTP_Request2_Response
98 * @throws net_php_pear_HTTP_Request2_Exception
99 */
100 public function sendRequest(net_php_pear_HTTP_Request2 $request)
101 {
102 $this->request = $request;
103 $keepAlive = $this->connect();
104 $headers = $this->prepareHeaders();
105
106 try {
107 if (false === @fwrite($this->socket, $headers, strlen($headers))) {
108 throw new net_php_pear_HTTP_Request2_Exception('Error writing request');
109 }
110 // provide request headers to the observer, see request #7633
111 $this->request->setLastEvent('sentHeaders', $headers);
112 $this->writeBody();
113
114 $response = $this->readResponse();
115
116 // check whether we should keep the connection open
117 $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding')) ||
118 null !== $response->getHeader('content-length');
119 $persistent = 'keep-alive' == strtolower($response->getHeader('connection')) ||
120 (null === $response->getHeader('connection') &&
121 '1.1' == $response->getVersion());
122 if (!$keepAlive || !$lengthKnown || !$persistent) {
123 $this->disconnect();
124 }
125
126 } catch (Exception $e) {
127 throw $e;
128 }
129 return $response;
130 }
131
132 /**
133 * Connects to the remote server
134 *
135 * @return bool whether the connection can be persistent
136 * @throws net_php_pear_HTTP_Request2_Exception
137 */
138 protected function connect()
139 {
140 if ($host = $this->request->getConfigValue('proxy_host')) {
141 if (!($port = $this->request->getConfigValue('proxy_port'))) {
142 throw new net_php_pear_HTTP_Request2_Exception('Proxy port not provided');
143 }
144 $proxy = true;
145 } else {
146 $host = $this->request->getUrl()->getHost();
147 if (!($port = $this->request->getUrl()->getPort())) {
148 $port = 0 == strcasecmp(
149 $this->request->getUrl()->getScheme(), 'https'
150 )? 443: 80;
151 }
152 $proxy = false;
153 }
154
155 if (0 == strcasecmp($this->request->getUrl()->getScheme(), 'https')) {
156 if ($proxy) {
157 throw new net_php_pear_HTTP_Request2_Exception('HTTPS proxy support not yet implemented');
158 } elseif (!in_array('ssl', stream_get_transports())) {
159 throw new net_php_pear_HTTP_Request2_Exception('Need OpenSSL support for https:// requests');
160 }
161 $host = 'ssl://' . $host;
162 } else {
163 $host = 'tcp://' . $host;
164 }
165
166 $headers = $this->request->getHeaders();
167
168 // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
169 // connection token to a proxy server...
170 if ($proxy && !empty($headers['connection']) && 'Keep-Alive' == $headers['connection'])
171 {
172 $this->request->setHeader('connection');
173 }
174
175 $keepAlive = ('1.1' == $this->request->getConfigValue('protocol_version') &&
176 empty($headers['connection'])) ||
177 (!empty($headers['connection']) &&
178 'Keep-Alive' == $headers['connection']);
179 $socketKey = $host . ':' . $port;
180 unset($this->socket);
181
182 // We use persistent connections and have a connected socket?
183 if ($keepAlive && !empty(self::$sockets[$socketKey])) {
184 $this->socket =& self::$sockets[$socketKey];
185 } else {
186 $this->socket = stream_socket_client(
187 $socketKey, $errno, $errstr,
188 $this->request->getConfigValue('connect_timeout'),
189 STREAM_CLIENT_CONNECT
190 );
191 if (!$this->socket) {
192 throw new net_php_pear_HTTP_Request2_Exception(
193 'Unable to connect to ' . $socketKey . '. Error #' . $errno .
194 ': ' . $errstr
195 );
196 }
197 $this->request->setLastEvent('connect', $socketKey);
198 self::$sockets[$socketKey] =& $this->socket;
199 }
200 return $keepAlive;
201 }
202
203 /**
204 * Disconnects from the remote server
205 */
206 protected function disconnect()
207 {
208 if (is_resource($this->socket)) {
209 fclose($this->socket);
210 $this->socket = null;
211 $this->request->setLastEvent('disconnect');
212 }
213 }
214
215 /**
216 * Creates the value for '[Proxy-]Authorization:' header
217 *
218 * @param string user name
219 * @param string password
220 * @param string authentication scheme, one of the HTTP_Request2::AUTH_* constants
221 * @return string header value
222 * @throws net_php_pear_HTTP_Request2_Exception
223 */
224 protected function createAuthHeader($user, $pass, $scheme)
225 {
226 switch ($scheme) {
227 case net_php_pear_HTTP_Request2::AUTH_BASIC:
228 return 'Basic ' . base64_encode($user . ':' . $pass);
229
230 case net_php_pear_HTTP_Request2::AUTH_DIGEST:
231 throw new net_php_pear_HTTP_Request2_Exception('Digest authentication is not implemented yet.');
232
233 default:
234 throw new net_php_pear_HTTP_Request2_Exception("Unknown HTTP authentication scheme '{$scheme}'");
235 }
236 }
237
238 /**
239 * Creates the string with the Request-Line and request headers
240 *
241 * @return string
242 * @throws net_php_pear_HTTP_Request2_Exception
243 */
244 protected function prepareHeaders()
245 {
246 $headers = $this->request->getHeaders();
247 $url = $this->request->getUrl();
248
249 $host = $url->getHost();
250 if ($port = $url->getPort()) {
251 $scheme = $url->getScheme();
252 if ((0 == strcasecmp($scheme, 'http') && 80 != $port) ||
253 (0 == strcasecmp($scheme, 'https') && 443 != $port)
254 ) {
255 $host .= ':' . $port;
256 }
257 }
258 $headers['host'] = $host;
259
260 if (!$this->request->getConfigValue('proxy_host')) {
261 $requestUrl = '';
262 } else {
263 if ($user = $this->request->getConfigValue('proxy_user')) {
264 $headers['proxy-authorization'] = $this->createAuthHeader(
265 $user, $this->request->getConfigValue('proxy_password'),
266 $this->request->getConfigValue('proxy_auth_scheme')
267 );
268 }
269 $requestUrl = $url->getScheme() . '://' . $host;
270 }
271 $path = $url->getPath();
272 $query = $url->getQuery();
273 $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);
274
275 if ($auth = $this->request->getAuth()) {
276 $headers['authorization'] = $this->createAuthHeader(
277 $auth['user'], $auth['password'], $auth['scheme']
278 );
279 }
280 if ('1.1' == $this->request->getConfigValue('protocol_version') &&
281 extension_loaded('zlib') && !isset($headers['accept-encoding'])
282 ) {
283 $headers['accept-encoding'] = 'gzip, deflate';
284 }
285
286 $this->calculateRequestLength($headers);
287
288 $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' .
289 $this->request->getConfigValue('protocol_version') . "\r\n";
290 foreach ($headers as $name => $value) {
291 $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
292 $headersStr .= $canonicalName . ': ' . $value . "\r\n";
293 }
294 return $headersStr . "\r\n";
295 }
296
297 /**
298 * Sends the request body
299 *
300 * @throws net_php_pear_HTTP_Request2_Exception
301 */
302 protected function writeBody()
303 {
304 if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
305 0 == $this->contentLength
306 ) {
307 return;
308 }
309
310 $position = 0;
311 $bufferSize = $this->request->getConfigValue('buffer_size');
312 while ($position < $this->contentLength) {
313 if (is_string($this->requestBody)) {
314 $str = substr($this->requestBody, $position, $bufferSize);
315 } elseif (is_resource($this->requestBody)) {
316 $str = fread($this->requestBody, $bufferSize);
317 } else {
318 $str = $this->requestBody->read($bufferSize);
319 }
320 if (false === @fwrite($this->socket, $str, strlen($str))) {
321 throw new net_php_pear_HTTP_Request2_Exception('Error writing request');
322 }
323 // Provide the length of written string to the observer, request #7630
324 $this->request->setLastEvent('sentBodyPart', strlen($str));
325 $position += strlen($str);
326 }
327 }
328
329 /**
330 * Reads the remote server's response
331 *
332 * @return net_php_pear_HTTP_Request2_Response
333 * @throws net_php_pear_HTTP_Request2_Exception
334 */
335 protected function readResponse()
336 {
337 $bufferSize = $this->request->getConfigValue('buffer_size');
338
339 do {
340 $response = new net_php_pear_HTTP_Request2_Response($this->readLine($bufferSize), true);
341 do {
342 $headerLine = $this->readLine($bufferSize);
343 $response->parseHeaderLine($headerLine);
344 } while ('' != $headerLine);
345 } while (in_array($response->getStatus(), array(100, 101)));
346
347 $this->request->setLastEvent('receivedHeaders', $response);
348
349 // No body possible in such responses
350 if (net_php_pear_HTTP_Request2::METHOD_HEAD == $this->request->getMethod() ||
351 in_array($response->getStatus(), array(204, 304))
352 ) {
353 return $response;
354 }
355
356 $chunked = 'chunked' == $response->getHeader('transfer-encoding');
357 $length = $response->getHeader('content-length');
358 $hasBody = false;
359 if ($chunked || null === $length || 0 < intval($length)) {
360 // RFC 2616, section 4.4:
361 // 3. ... If a message is received with both a
362 // Transfer-Encoding header field and a Content-Length header field,
363 // the latter MUST be ignored.
364 $toRead = ($chunked || null === $length)? null: $length;
365 $this->chunkLength = 0;
366
367 while (!feof($this->socket) && (is_null($toRead) || 0 < $toRead)) {
368 if ($chunked) {
369 $data = $this->readChunked($bufferSize);
370 } elseif (is_null($toRead)) {
371 $data = fread($this->socket, $bufferSize);
372 } else {
373 $data = fread($this->socket, min($toRead, $bufferSize));
374 $toRead -= strlen($data);
375 }
376 if ('' == $data && (!$this->chunkLength || feof($this->socket))) {
377 break;
378 }
379
380 $hasBody = true;
381 $response->appendBody($data);
382 if (!in_array($response->getHeader('content-encoding'), array('identity', null))) {
383 $this->request->setLastEvent('receivedEncodedBodyPart', $data);
384 } else {
385 $this->request->setLastEvent('receivedBodyPart', $data);
386 }
387 }
388 }
389
390 if ($hasBody) {
391 $this->request->setLastEvent('receivedBody', $response);
392 }
393 return $response;
394 }
395
396 /**
397 * Reads until either the end of the socket or a newline, whichever comes first
398 *
399 * Strips the trailing newline from the returned data. Method borrowed from
400 * Net_Socket PEAR package.
401 *
402 * @param int buffer size to use for reading
403 * @return Available data up to the newline (not including newline)
404 */
405 protected function readLine($bufferSize)
406 {
407 $line = '';
408 while (!feof($this->socket)) {
409 $line .= @fgets($this->socket, $bufferSize);
410 if (substr($line, -1) == "\n") {
411 return rtrim($line, "\r\n");
412 }
413 }
414 return $line;
415 }
416
417 /**
418 * Reads a part of response body encoded with chunked Transfer-Encoding
419 *
420 * @param int buffer size to use for reading
421 * @return string
422 * @throws net_php_pear_HTTP_Request2_Exception
423 */
424 protected function readChunked($bufferSize)
425 {
426 // at start of the next chunk?
427 if (0 == $this->chunkLength) {
428 $line = $this->readLine($bufferSize);
429 if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
430 throw new net_php_pear_HTTP_Request2_Exception(
431 "Cannot decode chunked response, invalid chunk length '{$line}'"
432 );
433 } else {
434 $this->chunkLength = hexdec($matches[1]);
435 // Chunk with zero length indicates the end
436 if (0 == $this->chunkLength) {
437 $this->readLine($bufferSize);
438 return '';
439 }
440 }
441 }
442 $data = fread($this->socket, min($this->chunkLength, $bufferSize));
443 $this->chunkLength -= strlen($data);
444 if (0 == $this->chunkLength) {
445 $this->readLine($bufferSize); // Trailing CRLF
446 }
447 return $data;
448 }
449}
450
451?>
Note: See TracBrowser for help on using the repository browser.