Completed
Push — 3.7 ( 81b2d8...ef0909 )
by
unknown
09:42
created

SimpleProxyRoute::createConnection()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
nc 3
nop 2
dl 0
loc 19
rs 9.0111
c 0
b 0
f 0
1
<?php
2
/**
3
 *  base include file for SimpleTest
4
 *  @package    SimpleTest
5
 *  @subpackage WebTester
6
 *  @version    $Id: http.php 1722 2008-04-07 19:30:56Z lastcraft $
7
 */
8
9
/**#@+
10
 *  include other SimpleTest class files
11
 */
12
require_once(dirname(__FILE__) . '/socket.php');
13
require_once(dirname(__FILE__) . '/cookies.php');
14
require_once(dirname(__FILE__) . '/url.php');
15
/**#@-*/
16
17
/**
18
 *    Creates HTTP headers for the end point of
19
 *    a HTTP request.
20
 *    @package SimpleTest
21
 *    @subpackage WebTester
22
 */
23
class SimpleRoute {
24
    var $_url;
25
    
26
    /**
27
     *    Sets the target URL.
28
     *    @param SimpleUrl $url   URL as object.
29
     *    @access public
30
     */
31
    function __construct($url) {
32
        $this->_url = $url;
33
    }
34
    
35
    /**
36
     *    Resource name.
37
     *    @return SimpleUrl        Current url.
38
     *    @access protected
39
     */
40
    function getUrl() {
41
        return $this->_url;
42
    }
43
    
44
    /**
45
     *    Creates the first line which is the actual request.
46
     *    @param string $method   HTTP request method, usually GET.
47
     *    @return string          Request line content.
48
     *    @access protected
49
     */
50
    function _getRequestLine($method) {
51
        return $method . ' ' . $this->_url->getPath() .
52
                $this->_url->getEncodedRequest() . ' HTTP/1.0';
53
    }
54
    
55
    /**
56
     *    Creates the host part of the request.
57
     *    @return string          Host line content.
58
     *    @access protected
59
     */
60
    function _getHostLine() {
61
        $line = 'Host: ' . $this->_url->getHost();
62
        if ($this->_url->getPort()) {
63
            $line .= ':' . $this->_url->getPort();
64
        }
65
        return $line;
66
    }
67
    
68
    /**
69
     *    Opens a socket to the route.
70
     *    @param string $method      HTTP request method, usually GET.
71
     *    @param integer $timeout    Connection timeout.
72
     *    @return SimpleSocket       New socket.
73
     *    @access public
74
     */
75
    function &createConnection($method, $timeout) {
76
        $default_port = ('https' == $this->_url->getScheme()) ? 443 : 80;
77
        $socket = &$this->_createSocket(
78
                $this->_url->getScheme() ? $this->_url->getScheme() : 'http',
79
                $this->_url->getHost(),
80
                $this->_url->getPort() ? $this->_url->getPort() : $default_port,
81
                $timeout);
82
        if (! $socket->isError()) {
83
            $socket->write($this->_getRequestLine($method) . "\r\n");
84
            $socket->write($this->_getHostLine() . "\r\n");
85
            $socket->write("Connection: close\r\n");
86
        }
87
        return $socket;
88
    }
89
    
90
    /**
91
     *    Factory for socket.
92
     *    @param string $scheme                   Protocol to use.
93
     *    @param string $host                     Hostname to connect to.
94
     *    @param integer $port                    Remote port.
95
     *    @param integer $timeout                 Connection timeout.
96
     *    @return SimpleSocket/SimpleSecureSocket New socket.
97
     *    @access protected
98
     */
99
    function &_createSocket($scheme, $host, $port, $timeout) {
100
        if (in_array($scheme, array('https'))) {
101
            $socket = new SimpleSecureSocket($host, $port, $timeout);
102
        } else {
103
            $socket = new SimpleSocket($host, $port, $timeout);
104
        }
105
        return $socket;
106
    }
107
}
108
109
/**
110
 *    Creates HTTP headers for the end point of
111
 *    a HTTP request via a proxy server.
112
 *    @package SimpleTest
113
 *    @subpackage WebTester
114
 */
115
class SimpleProxyRoute extends SimpleRoute {
116
    var $_proxy;
117
    var $_username;
118
    var $_password;
119
    
120
    /**
121
     *    Stashes the proxy address.
122
     *    @param SimpleUrl $url     URL as object.
123
     *    @param string $proxy      Proxy URL.
124
     *    @param string $username   Username for autentication.
125
     *    @param string $password   Password for autentication.
126
     *    @access public
127
     */
128
    function __construct($url, $proxy, $username = false, $password = false) {
129
        parent::__construct($url);
130
        $this->_proxy = $proxy;
131
        $this->_username = $username;
132
        $this->_password = $password;
133
    }
134
    
135
    /**
136
     *    Creates the first line which is the actual request.
137
     *    @param string $method   HTTP request method, usually GET.
138
     *    @param SimpleUrl $url   URL as object.
139
     *    @return string          Request line content.
140
     *    @access protected
141
     */
142
    function _getRequestLine($method) {
143
        $url = $this->getUrl();
144
        $scheme = $url->getScheme() ? $url->getScheme() : 'http';
145
        $port = $url->getPort() ? ':' . $url->getPort() : '';
146
        return $method . ' ' . $scheme . '://' . $url->getHost() . $port .
147
                $url->getPath() . $url->getEncodedRequest() . ' HTTP/1.0';
148
    }
149
    
150
    /**
151
     *    Creates the host part of the request.
152
     *    @param SimpleUrl $url   URL as object.
153
     *    @return string          Host line content.
154
     *    @access protected
155
     */
156
    function _getHostLine() {
157
        $host = 'Host: ' . $this->_proxy->getHost();
158
        $port = $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080;
159
        return "$host:$port";
160
    }
161
    
162
    /**
163
     *    Opens a socket to the route.
164
     *    @param string $method       HTTP request method, usually GET.
165
     *    @param integer $timeout     Connection timeout.
166
     *    @return SimpleSocket        New socket.
167
     *    @access public
168
     */
169
    function &createConnection($method, $timeout) {
170
        $socket = &$this->_createSocket(
171
                $this->_proxy->getScheme() ? $this->_proxy->getScheme() : 'http',
172
                $this->_proxy->getHost(),
173
                $this->_proxy->getPort() ? $this->_proxy->getPort() : 8080,
174
                $timeout);
175
        if ($socket->isError()) {
176
            return $socket;
177
        }
178
        $socket->write($this->_getRequestLine($method) . "\r\n");
179
        $socket->write($this->_getHostLine() . "\r\n");
180
        if ($this->_username && $this->_password) {
181
            $socket->write('Proxy-Authorization: Basic ' .
182
                    base64_encode($this->_username . ':' . $this->_password) .
183
                    "\r\n");
184
        }
185
        $socket->write("Connection: close\r\n");
186
        return $socket;
187
    }
188
}
189
190
/**
191
 *    HTTP request for a web page. Factory for
192
 *    HttpResponse object.
193
 *    @package SimpleTest
194
 *    @subpackage WebTester
195
 */
196
class SimpleHttpRequest {
197
    var $_route;
198
    var $_encoding;
199
    var $_headers;
200
    var $_cookies;
201
    
202
    /**
203
     *    Builds the socket request from the different pieces.
204
     *    These include proxy information, URL, cookies, headers,
205
     *    request method and choice of encoding.
206
     *    @param SimpleRoute $route              Request route.
207
     *    @param SimpleFormEncoding $encoding    Content to send with
208
     *                                           request.
209
     *    @access public
210
     */
211
    function __construct(&$route, $encoding) {
212
        $this->_route = &$route;
213
        $this->_encoding = $encoding;
214
        $this->_headers = array();
215
        $this->_cookies = array();
216
    }
217
    
218
    /**
219
     *    Dispatches the content to the route's socket.
220
     *    @param integer $timeout      Connection timeout.
221
     *    @return SimpleHttpResponse   A response which may only have
222
     *                                 an error, but hopefully has a
223
     *                                 complete web page.
224
     *    @access public
225
     */
226
    function &fetch($timeout) {
227
        $socket = &$this->_route->createConnection($this->_encoding->getMethod(), $timeout);
228
        if (! $socket->isError()) {
229
            $this->_dispatchRequest($socket, $this->_encoding);
230
        }
231
        $response = &$this->_createResponse($socket);
232
        return $response;
233
    }
234
    
235
    /**
236
     *    Sends the headers.
237
     *    @param SimpleSocket $socket           Open socket.
238
     *    @param string $method                 HTTP request method,
239
     *                                          usually GET.
240
     *    @param SimpleFormEncoding $encoding   Content to send with request.
241
     *    @access private
242
     */
243
    function _dispatchRequest(&$socket, $encoding) {
244
        foreach ($this->_headers as $header_line) {
245
            $socket->write($header_line . "\r\n");
246
        }
247
        if (count($this->_cookies) > 0) {
248
            $socket->write("Cookie: " . implode(";", $this->_cookies) . "\r\n");
249
        }
250
        $encoding->writeHeadersTo($socket);
251
        $socket->write("\r\n");
252
        $encoding->writeTo($socket);
253
    }
254
    
255
    /**
256
     *    Adds a header line to the request.
257
     *    @param string $header_line    Text of full header line.
258
     *    @access public
259
     */
260
    function addHeaderLine($header_line) {
261
        $this->_headers[] = $header_line;
262
    }
263
    
264
    /**
265
     *    Reads all the relevant cookies from the
266
     *    cookie jar.
267
     *    @param SimpleCookieJar $jar     Jar to read
268
     *    @param SimpleUrl $url           Url to use for scope.
269
     *    @access public
270
     */
271
    function readCookiesFromJar($jar, $url) {
272
        $this->_cookies = $jar->selectAsPairs($url);
273
    }
274
    
275
    /**
276
     *    Wraps the socket in a response parser.
277
     *    @param SimpleSocket $socket   Responding socket.
278
     *    @return SimpleHttpResponse    Parsed response object.
279
     *    @access protected
280
     */
281
    function &_createResponse(&$socket) {
282
        $response = new SimpleHttpResponse(
283
                $socket,
284
                $this->_route->getUrl(),
285
                $this->_encoding);
286
        return $response;
287
    }
288
}
289
290
/**
291
 *    Collection of header lines in the response.
292
 *    @package SimpleTest
293
 *    @subpackage WebTester
294
 */
295
class SimpleHttpHeaders {
296
    var $_raw_headers;
297
    var $_response_code;
298
    var $_http_version;
299
    var $_mime_type;
300
    var $_location;
301
    var $_cookies;
302
    var $_authentication;
303
    var $_realm;
304
    
305
    /**
306
     *    Parses the incoming header block.
307
     *    @param string $headers     Header block.
308
     *    @access public
309
     */
310
    function __construct($headers) {
311
        $this->_raw_headers = $headers;
312
        $this->_response_code = false;
313
        $this->_http_version = false;
314
        $this->_mime_type = '';
315
        $this->_location = false;
316
        $this->_cookies = array();
317
        $this->_authentication = false;
318
        $this->_realm = false;
319
        foreach (preg_split("/\r\n/", $headers) as $header_line) {
320
            $this->_parseHeaderLine($header_line);
321
        }
322
    }
323
    
324
    /**
325
     *    Accessor for parsed HTTP protocol version.
326
     *    @return integer           HTTP error code.
327
     *    @access public
328
     */
329
    function getHttpVersion() {
330
        return $this->_http_version;
331
    }
332
    
333
    /**
334
     *    Accessor for raw header block.
335
     *    @return string        All headers as raw string.
336
     *    @access public
337
     */
338
    function getRaw() {
339
        return $this->_raw_headers;
340
    }
341
    
342
    /**
343
     *    Accessor for parsed HTTP error code.
344
     *    @return integer           HTTP error code.
345
     *    @access public
346
     */
347
    function getResponseCode() {
348
        return (integer)$this->_response_code;
349
    }
350
    
351
    /**
352
     *    Returns the redirected URL or false if
353
     *    no redirection.
354
     *    @return string      URL or false for none.
355
     *    @access public
356
     */
357
    function getLocation() {
358
        return $this->_location;
359
    }
360
    
361
    /**
362
     *    Test to see if the response is a valid redirect.
363
     *    @return boolean       True if valid redirect.
364
     *    @access public
365
     */
366
    function isRedirect() {
367
        return in_array($this->_response_code, array(301, 302, 303, 307)) &&
368
                (boolean)$this->getLocation();
369
    }
370
    
371
    /**
372
     *    Test to see if the response is an authentication
373
     *    challenge.
374
     *    @return boolean       True if challenge.
375
     *    @access public
376
     */
377
    function isChallenge() {
378
        return ($this->_response_code == 401) &&
379
                (boolean)$this->_authentication &&
380
                (boolean)$this->_realm;
381
    }
382
    
383
    /**
384
     *    Accessor for MIME type header information.
385
     *    @return string           MIME type.
386
     *    @access public
387
     */
388
    function getMimeType() {
389
        return $this->_mime_type;
390
    }
391
    
392
    /**
393
     *    Accessor for authentication type.
394
     *    @return string        Type.
395
     *    @access public
396
     */
397
    function getAuthentication() {
398
        return $this->_authentication;
399
    }
400
    
401
    /**
402
     *    Accessor for security realm.
403
     *    @return string        Realm.
404
     *    @access public
405
     */
406
    function getRealm() {
407
        return $this->_realm;
408
    }
409
    
410
    /**
411
     *    Writes new cookies to the cookie jar.
412
     *    @param SimpleCookieJar $jar   Jar to write to.
413
     *    @param SimpleUrl $url         Host and path to write under.
414
     *    @access public
415
     */
416
    function writeCookiesToJar(&$jar, $url) {
417
        foreach ($this->_cookies as $cookie) {
418
            $jar->setCookie(
419
                    $cookie->getName(),
420
                    $cookie->getValue(),
421
                    $url->getHost(),
422
                    $cookie->getPath(),
423
                    $cookie->getExpiry());
424
        }
425
    }
426
427
    /**
428
     *    Called on each header line to accumulate the held
429
     *    data within the class.
430
     *    @param string $header_line        One line of header.
431
     *    @access protected
432
     */
433
    function _parseHeaderLine($header_line) {
434
        if (preg_match('/HTTP\/(\d+\.\d+)\s+(\d+)/i', $header_line, $matches)) {
435
            $this->_http_version = $matches[1];
436
            $this->_response_code = $matches[2];
437
        }
438
        if (preg_match('/Content-type:\s*(.*)/i', $header_line, $matches)) {
439
            $this->_mime_type = trim($matches[1]);
440
        }
441
        if (preg_match('/Location:\s*(.*)/i', $header_line, $matches)) {
442
            $this->_location = trim($matches[1]);
443
        }
444
        if (preg_match('/Set-cookie:(.*)/i', $header_line, $matches)) {
445
            $this->_cookies[] = $this->_parseCookie($matches[1]);
446
        }
447
        if (preg_match('/WWW-Authenticate:\s+(\S+)\s+realm=\"(.*?)\"/i', $header_line, $matches)) {
448
            $this->_authentication = $matches[1];
449
            $this->_realm = trim($matches[2]);
450
        }
451
    }
452
    
453
    /**
454
     *    Parse the Set-cookie content.
455
     *    @param string $cookie_line    Text after "Set-cookie:"
456
     *    @return SimpleCookie          New cookie object.
457
     *    @access private
458
     */
459
    function _parseCookie($cookie_line) {
460
        $parts = preg_split('/;/', $cookie_line);
461
        $cookie = array();
462
        preg_match('/\s*(.*?)\s*=(.*)/', array_shift($parts), $cookie);
463
        foreach ($parts as $part) {
464
            if (preg_match('/\s*(.*?)\s*=(.*)/', $part, $matches)) {
465
                $cookie[$matches[1]] = trim($matches[2]);
466
            }
467
        }
468
        return new SimpleCookie(
469
                $cookie[1],
470
                trim($cookie[2]),
471
                isset($cookie["path"]) ? $cookie["path"] : "",
472
                isset($cookie["expires"]) ? $cookie["expires"] : false);
473
    }
474
}
475
476
/**
477
 *    Basic HTTP response.
478
 *    @package SimpleTest
479
 *    @subpackage WebTester
480
 */
481
class SimpleHttpResponse extends SimpleStickyError {
482
    var $_url;
483
    var $_encoding;
484
    var $_sent;
485
    var $_content;
486
    var $_headers;
487
    
488
    /**
489
     *    Constructor. Reads and parses the incoming
490
     *    content and headers.
491
     *    @param SimpleSocket $socket   Network connection to fetch
492
     *                                  response text from.
493
     *    @param SimpleUrl $url         Resource name.
494
     *    @param mixed $encoding        Record of content sent.
495
     *    @access public
496
     */
497
    function __construct(&$socket, $url, $encoding) {
498
        parent::__construct();
499
        $this->_url = $url;
500
        $this->_encoding = $encoding;
501
        $this->_sent = $socket->getSent();
502
        $this->_content = false;
503
        $raw = $this->_readAll($socket);
504
        if ($socket->isError()) {
505
            $this->_setError('Error reading socket [' . $socket->getError() . ']');
506
            return;
507
        }
508
        $this->_parse($raw);
509
    }
510
    
511
    /**
512
     *    Splits up the headers and the rest of the content.
513
     *    @param string $raw    Content to parse.
514
     *    @access private
515
     */
516
    function _parse($raw) {
517
        if (! $raw) {
518
            $this->_setError('Nothing fetched');
519
            $this->_headers = new SimpleHttpHeaders('');
520
        } elseif (! strstr($raw, "\r\n\r\n")) {
521
            $this->_setError('Could not split headers from content');
522
            $this->_headers = new SimpleHttpHeaders($raw);
523
        } else {
524
            list($headers, $this->_content) = preg_split("/\r\n\r\n/", $raw, 2);
525
            $this->_headers = new SimpleHttpHeaders($headers);
526
        }
527
    }
528
    
529
    /**
530
     *    Original request method.
531
     *    @return string        GET, POST or HEAD.
532
     *    @access public
533
     */
534
    function getMethod() {
535
        return $this->_encoding->getMethod();
536
    }
537
    
538
    /**
539
     *    Resource name.
540
     *    @return SimpleUrl        Current url.
541
     *    @access public
542
     */
543
    function getUrl() {
544
        return $this->_url;
545
    }
546
    
547
    /**
548
     *    Original request data.
549
     *    @return mixed              Sent content.
550
     *    @access public
551
     */
552
    function getRequestData() {
553
        return $this->_encoding;
554
    }
555
    
556
    /**
557
     *    Raw request that was sent down the wire.
558
     *    @return string        Bytes actually sent.
559
     *    @access public
560
     */
561
    function getSent() {
562
        return $this->_sent;
563
    }
564
    
565
    /**
566
     *    Accessor for the content after the last
567
     *    header line.
568
     *    @return string           All content.
569
     *    @access public
570
     */
571
    function getContent() {
572
        return $this->_content;
573
    }
574
    
575
    /**
576
     *    Accessor for header block. The response is the
577
     *    combination of this and the content.
578
     *    @return SimpleHeaders        Wrapped header block.
579
     *    @access public
580
     */
581
    function getHeaders() {
582
        return $this->_headers;
583
    }
584
    
585
    /**
586
     *    Accessor for any new cookies.
587
     *    @return array       List of new cookies.
588
     *    @access public
589
     */
590
    function getNewCookies() {
591
        return $this->_headers->getNewCookies();
592
    }
593
    
594
    /**
595
     *    Reads the whole of the socket output into a
596
     *    single string.
597
     *    @param SimpleSocket $socket  Unread socket.
598
     *    @return string               Raw output if successful
599
     *                                 else false.
600
     *    @access private
601
     */
602
    function _readAll(&$socket) {
603
        $all = '';
604
        while (! $this->_isLastPacket($next = $socket->read())) {
605
            $all .= $next;
606
        }
607
        return $all;
608
    }
609
    
610
    /**
611
     *    Test to see if the packet from the socket is the
612
     *    last one.
613
     *    @param string $packet    Chunk to interpret.
614
     *    @return boolean          True if empty or EOF.
615
     *    @access private
616
     */
617
    function _isLastPacket($packet) {
618
        if (is_string($packet)) {
619
            return $packet === '';
620
        }
621
        return ! $packet;
622
    }
623
}
624
?>
625