~chroot64bit/zivios/gentoo-experimental

« back to all changes in this revision

Viewing changes to application/library/Zend/Http/Client.php

  • Committer: Mustafa A. Hashmi
  • Date: 2008-12-04 13:32:21 UTC
  • Revision ID: mhashmi@zivios.org-20081204133221-0nd1trunwevijj38
Inclusion of new installation framework with ties to zend layout and dojo layout

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
19
 * @version    $Id: Client.php 12504 2008-11-10 16:28:46Z matthew $
 
20
 * @copyright  Copyright (c) 2005-2008 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_Loader
 
26
 */
 
27
require_once 'Zend/Loader.php';
 
28
 
 
29
 
 
30
/**
 
31
 * @see Zend_Uri
 
32
 */
 
33
require_once 'Zend/Uri.php';
 
34
 
 
35
 
 
36
/**
 
37
 * @see Zend_Http_Client_Adapter_Interface
 
38
 */
 
39
require_once 'Zend/Http/Client/Adapter/Interface.php';
 
40
 
 
41
 
 
42
/**
 
43
 * @see Zend_Http_Response
 
44
 */
 
45
require_once 'Zend/Http/Response.php';
 
46
 
 
47
/**
 
48
 * Zend_Http_Client is an implemetation of an HTTP client in PHP. The client
 
49
 * supports basic features like sending different HTTP requests and handling
 
50
 * redirections, as well as more advanced features like proxy settings, HTTP
 
51
 * authentication and cookie persistance (using a Zend_Http_CookieJar object)
 
52
 *
 
53
 * @todo Implement proxy settings
 
54
 * @category   Zend
 
55
 * @package    Zend_Http
 
56
 * @subpackage Client
 
57
 * @throws     Zend_Http_Client_Exception
 
58
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 
59
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 
60
 */
 
61
class Zend_Http_Client
 
62
{
 
63
    /**
 
64
     * HTTP request methods
 
65
     */
 
66
    const GET     = 'GET';
 
67
    const POST    = 'POST';
 
68
    const PUT     = 'PUT';
 
69
    const HEAD    = 'HEAD';
 
70
    const DELETE  = 'DELETE';
 
71
    const TRACE   = 'TRACE';
 
72
    const OPTIONS = 'OPTIONS';
 
73
    const CONNECT = 'CONNECT';
 
74
 
 
75
    /**
 
76
     * Supported HTTP Authentication methods
 
77
     */
 
78
    const AUTH_BASIC = 'basic';
 
79
    //const AUTH_DIGEST = 'digest'; <-- not implemented yet
 
80
 
 
81
    /**
 
82
     * HTTP protocol versions
 
83
     */
 
84
    const HTTP_1 = '1.1';
 
85
    const HTTP_0 = '1.0';
 
86
 
 
87
    /**
 
88
     * Content attributes
 
89
     */
 
90
    const CONTENT_TYPE   = 'Content-Type';
 
91
    const CONTENT_LENGTH = 'Content-Length';
 
92
 
 
93
    /**
 
94
     * POST data encoding methods
 
95
     */
 
96
    const ENC_URLENCODED = 'application/x-www-form-urlencoded';
 
97
    const ENC_FORMDATA   = 'multipart/form-data';
 
98
 
 
99
    /**
 
100
     * Configuration array, set using the constructor or using ::setConfig()
 
101
     *
 
102
     * @var array
 
103
     */
 
104
    protected $config = array(
 
105
        'maxredirects'    => 5,
 
106
        'strictredirects' => false,
 
107
        'useragent'       => 'Zend_Http_Client',
 
108
        'timeout'         => 10,
 
109
        'adapter'         => 'Zend_Http_Client_Adapter_Socket',
 
110
        'httpversion'     => self::HTTP_1,
 
111
        'keepalive'       => false,
 
112
        'storeresponse'   => true,
 
113
        'strict'          => true
 
114
    );
 
115
 
 
116
    /**
 
117
     * The adapter used to preform the actual connection to the server
 
118
     *
 
119
     * @var Zend_Http_Client_Adapter_Interface
 
120
     */
 
121
    protected $adapter = null;
 
122
 
 
123
    /**
 
124
     * Request URI
 
125
     *
 
126
     * @var Zend_Uri_Http
 
127
     */
 
128
    protected $uri;
 
129
 
 
130
    /**
 
131
     * Associative array of request headers
 
132
     *
 
133
     * @var array
 
134
     */
 
135
    protected $headers = array();
 
136
 
 
137
    /**
 
138
     * HTTP request method
 
139
     *
 
140
     * @var string
 
141
     */
 
142
    protected $method = self::GET;
 
143
 
 
144
    /**
 
145
     * Associative array of GET parameters
 
146
     *
 
147
     * @var array
 
148
     */
 
149
    protected $paramsGet = array();
 
150
 
 
151
    /**
 
152
     * Assiciative array of POST parameters
 
153
     *
 
154
     * @var array
 
155
     */
 
156
    protected $paramsPost = array();
 
157
 
 
158
    /**
 
159
     * Request body content type (for POST requests)
 
160
     *
 
161
     * @var string
 
162
     */
 
163
    protected $enctype = null;
 
164
 
 
165
    /**
 
166
     * The raw post data to send. Could be set by setRawData($data, $enctype).
 
167
     *
 
168
     * @var string
 
169
     */
 
170
    protected $raw_post_data = null;
 
171
 
 
172
    /**
 
173
     * HTTP Authentication settings
 
174
     *
 
175
     * Expected to be an associative array with this structure:
 
176
     * $this->auth = array('user' => 'username', 'password' => 'password', 'type' => 'basic')
 
177
     * Where 'type' should be one of the supported authentication types (see the AUTH_*
 
178
     * constants), for example 'basic' or 'digest'.
 
179
     *
 
180
     * If null, no authentication will be used.
 
181
     *
 
182
     * @var array|null
 
183
     */
 
184
    protected $auth;
 
185
 
 
186
    /**
 
187
     * File upload arrays (used in POST requests)
 
188
     *
 
189
     * An associative array, where each element is of the format:
 
190
     *   'name' => array('filename.txt', 'text/plain', 'This is the actual file contents')
 
191
     *
 
192
     * @var array
 
193
     */
 
194
    protected $files = array();
 
195
 
 
196
    /**
 
197
     * The client's cookie jar
 
198
     *
 
199
     * @var Zend_Http_CookieJar
 
200
     */
 
201
    protected $cookiejar = null;
 
202
 
 
203
    /**
 
204
     * The last HTTP request sent by the client, as string
 
205
     *
 
206
     * @var string
 
207
     */
 
208
    protected $last_request = null;
 
209
 
 
210
    /**
 
211
     * The last HTTP response received by the client
 
212
     *
 
213
     * @var Zend_Http_Response
 
214
     */
 
215
    protected $last_response = null;
 
216
 
 
217
    /**
 
218
     * Redirection counter
 
219
     *
 
220
     * @var int
 
221
     */
 
222
    protected $redirectCounter = 0;
 
223
 
 
224
    /**
 
225
     * Fileinfo magic database resource
 
226
     * 
 
227
     * This varaiable is populated the first time _detectFileMimeType is called
 
228
     * and is then reused on every call to this method
 
229
     *
 
230
     * @var resource
 
231
     */
 
232
    static protected $_fileInfoDb = null;
 
233
    
 
234
    /**
 
235
     * Contructor method. Will create a new HTTP client. Accepts the target
 
236
     * URL and optionally configuration array.
 
237
     *
 
238
     * @param Zend_Uri_Http|string $uri
 
239
     * @param array $config Configuration key-value pairs.
 
240
     */
 
241
    public function __construct($uri = null, $config = null)
 
242
    {
 
243
        if ($uri !== null) $this->setUri($uri);
 
244
        if ($config !== null) $this->setConfig($config);
 
245
    }
 
246
 
 
247
    /**
 
248
     * Set the URI for the next request
 
249
     *
 
250
     * @param  Zend_Uri_Http|string $uri
 
251
     * @return Zend_Http_Client
 
252
     * @throws Zend_Http_Client_Exception
 
253
     */
 
254
    public function setUri($uri)
 
255
    {
 
256
        if (is_string($uri)) {
 
257
            $uri = Zend_Uri::factory($uri);
 
258
        }
 
259
 
 
260
        if (!$uri instanceof Zend_Uri_Http) {
 
261
            /** @see Zend_Http_Client_Exception */
 
262
            require_once 'Zend/Http/Client/Exception.php';
 
263
            throw new Zend_Http_Client_Exception('Passed parameter is not a valid HTTP URI.');
 
264
        }
 
265
 
 
266
        // We have no ports, set the defaults
 
267
        if (! $uri->getPort()) {
 
268
            $uri->setPort(($uri->getScheme() == 'https' ? 443 : 80));
 
269
        }
 
270
 
 
271
        $this->uri = $uri;
 
272
 
 
273
        return $this;
 
274
    }
 
275
 
 
276
    /**
 
277
     * Get the URI for the next request
 
278
     *
 
279
     * @param boolean $as_string If true, will return the URI as a string
 
280
     * @return Zend_Uri_Http|string
 
281
     */
 
282
    public function getUri($as_string = false)
 
283
    {
 
284
        if ($as_string && $this->uri instanceof Zend_Uri_Http) {
 
285
            return $this->uri->__toString();
 
286
        } else {
 
287
            return $this->uri;
 
288
        }
 
289
    }
 
290
 
 
291
    /**
 
292
     * Set configuration parameters for this HTTP client
 
293
     *
 
294
     * @param array $config
 
295
     * @return Zend_Http_Client
 
296
     * @throws Zend_Http_Client_Exception
 
297
     */
 
298
    public function setConfig($config = array())
 
299
    {
 
300
        if (! is_array($config)) {
 
301
            /** @see Zend_Http_Client_Exception */
 
302
            require_once 'Zend/Http/Client/Exception.php';
 
303
            throw new Zend_Http_Client_Exception('Expected array parameter, given ' . gettype($config));
 
304
        }
 
305
 
 
306
        foreach ($config as $k => $v)
 
307
            $this->config[strtolower($k)] = $v;
 
308
 
 
309
        // Pass configuration options to the adapter if it exists
 
310
        if ($this->adapter instanceof Zend_Http_Client_Adapter_Interface) {
 
311
            $this->adapter->setConfig($config);
 
312
        }
 
313
        
 
314
        return $this;
 
315
    }
 
316
 
 
317
    /**
 
318
     * Set the next request's method
 
319
     *
 
320
     * Validated the passed method and sets it. If we have files set for
 
321
     * POST requests, and the new method is not POST, the files are silently
 
322
     * dropped.
 
323
     *
 
324
     * @param string $method
 
325
     * @return Zend_Http_Client
 
326
     * @throws Zend_Http_Client_Exception
 
327
     */
 
328
    public function setMethod($method = self::GET)
 
329
    {
 
330
        $regex = '/^[^\x00-\x1f\x7f-\xff\(\)<>@,;:\\\\"\/\[\]\?={}\s]+$/';
 
331
        if (! preg_match($regex, $method)) {
 
332
            /** @see Zend_Http_Client_Exception */
 
333
            require_once 'Zend/Http/Client/Exception.php';
 
334
            throw new Zend_Http_Client_Exception("'{$method}' is not a valid HTTP request method.");
 
335
        }
 
336
 
 
337
        if ($method == self::POST && $this->enctype === null)
 
338
            $this->setEncType(self::ENC_URLENCODED);
 
339
 
 
340
        $this->method = $method;
 
341
 
 
342
        return $this;
 
343
    }
 
344
 
 
345
    /**
 
346
     * Set one or more request headers
 
347
     *
 
348
     * This function can be used in several ways to set the client's request
 
349
     * headers:
 
350
     * 1. By providing two parameters: $name as the header to set (eg. 'Host')
 
351
     *    and $value as it's value (eg. 'www.example.com').
 
352
     * 2. By providing a single header string as the only parameter
 
353
     *    eg. 'Host: www.example.com'
 
354
     * 3. By providing an array of headers as the first parameter
 
355
     *    eg. array('host' => 'www.example.com', 'x-foo: bar'). In This case
 
356
     *    the function will call itself recursively for each array item.
 
357
     *
 
358
     * @param string|array $name Header name, full header string ('Header: value')
 
359
     *     or an array of headers
 
360
     * @param mixed $value Header value or null
 
361
     * @return Zend_Http_Client
 
362
     * @throws Zend_Http_Client_Exception
 
363
     */
 
364
    public function setHeaders($name, $value = null)
 
365
    {
 
366
        // If we got an array, go recusive!
 
367
        if (is_array($name)) {
 
368
            foreach ($name as $k => $v) {
 
369
                if (is_string($k)) {
 
370
                    $this->setHeaders($k, $v);
 
371
                } else {
 
372
                    $this->setHeaders($v, null);
 
373
                }
 
374
            }
 
375
        } else {
 
376
            // Check if $name needs to be split
 
377
            if ($value === null && (strpos($name, ':') > 0))
 
378
                list($name, $value) = explode(':', $name, 2);
 
379
 
 
380
            // Make sure the name is valid if we are in strict mode
 
381
            if ($this->config['strict'] && (! preg_match('/^[a-zA-Z0-9-]+$/', $name))) {
 
382
                /** @see Zend_Http_Client_Exception */
 
383
                require_once 'Zend/Http/Client/Exception.php';
 
384
                throw new Zend_Http_Client_Exception("{$name} is not a valid HTTP header name");
 
385
            }
 
386
            
 
387
            $normalized_name = strtolower($name);
 
388
 
 
389
            // If $value is null or false, unset the header
 
390
            if ($value === null || $value === false) {
 
391
                unset($this->headers[$normalized_name]);
 
392
 
 
393
            // Else, set the header
 
394
            } else {
 
395
                // Header names are storred lowercase internally.
 
396
                if (is_string($value)) $value = trim($value);
 
397
                $this->headers[$normalized_name] = array($name, $value);
 
398
            }
 
399
        }
 
400
 
 
401
        return $this;
 
402
    }
 
403
 
 
404
    /**
 
405
     * Get the value of a specific header
 
406
     *
 
407
     * Note that if the header has more than one value, an array
 
408
     * will be returned.
 
409
     *
 
410
     * @param string $key
 
411
     * @return string|array|null The header value or null if it is not set
 
412
     */
 
413
    public function getHeader($key)
 
414
    {
 
415
        $key = strtolower($key);
 
416
        if (isset($this->headers[$key])) {
 
417
            return $this->headers[$key][1];
 
418
        } else {
 
419
            return null;
 
420
        }
 
421
    }
 
422
 
 
423
    /**
 
424
     * Set a GET parameter for the request. Wrapper around _setParameter
 
425
     *
 
426
     * @param string|array $name
 
427
     * @param string $value
 
428
     * @return Zend_Http_Client
 
429
     */
 
430
    public function setParameterGet($name, $value = null)
 
431
    {
 
432
        if (is_array($name)) {
 
433
            foreach ($name as $k => $v)
 
434
                $this->_setParameter('GET', $k, $v);
 
435
        } else {
 
436
            $this->_setParameter('GET', $name, $value);
 
437
        }
 
438
 
 
439
        return $this;
 
440
    }
 
441
 
 
442
    /**
 
443
     * Set a POST parameter for the request. Wrapper around _setParameter
 
444
     *
 
445
     * @param string|array $name
 
446
     * @param string $value
 
447
     * @return Zend_Http_Client
 
448
     */
 
449
    public function setParameterPost($name, $value = null)
 
450
    {
 
451
        if (is_array($name)) {
 
452
            foreach ($name as $k => $v)
 
453
                $this->_setParameter('POST', $k, $v);
 
454
        } else {
 
455
            $this->_setParameter('POST', $name, $value);
 
456
        }
 
457
 
 
458
        return $this;
 
459
    }
 
460
 
 
461
    /**
 
462
     * Set a GET or POST parameter - used by SetParameterGet and SetParameterPost
 
463
     *
 
464
     * @param string $type GET or POST
 
465
     * @param string $name
 
466
     * @param string $value
 
467
     * @return null
 
468
     */
 
469
    protected function _setParameter($type, $name, $value)
 
470
    {
 
471
        $parray = array();
 
472
        $type = strtolower($type);
 
473
        switch ($type) {
 
474
            case 'get':
 
475
                $parray = &$this->paramsGet;
 
476
                break;
 
477
            case 'post':
 
478
                $parray = &$this->paramsPost;
 
479
                break;
 
480
        }
 
481
 
 
482
        if ($value === null) {
 
483
            if (isset($parray[$name])) unset($parray[$name]);
 
484
        } else {
 
485
            $parray[$name] = $value;
 
486
        }
 
487
    }
 
488
 
 
489
    /**
 
490
     * Get the number of redirections done on the last request
 
491
     *
 
492
     * @return int
 
493
     */
 
494
    public function getRedirectionsCount()
 
495
    {
 
496
        return $this->redirectCounter;
 
497
    }
 
498
 
 
499
    /**
 
500
     * Set HTTP authentication parameters
 
501
     *
 
502
     * $type should be one of the supported types - see the self::AUTH_*
 
503
     * constants.
 
504
     *
 
505
     * To enable authentication:
 
506
     * <code>
 
507
     * $this->setAuth('shahar', 'secret', Zend_Http_Client::AUTH_BASIC);
 
508
     * </code>
 
509
     *
 
510
     * To disable authentication:
 
511
     * <code>
 
512
     * $this->setAuth(false);
 
513
     * </code>
 
514
     *
 
515
     * @see http://www.faqs.org/rfcs/rfc2617.html
 
516
     * @param string|false $user User name or false disable authentication
 
517
     * @param string $password Password
 
518
     * @param string $type Authentication type
 
519
     * @return Zend_Http_Client
 
520
     * @throws Zend_Http_Client_Exception
 
521
     */
 
522
    public function setAuth($user, $password = '', $type = self::AUTH_BASIC)
 
523
    {
 
524
        // If we got false or null, disable authentication
 
525
        if ($user === false || $user === null) {
 
526
            $this->auth = null;
 
527
 
 
528
        // Else, set up authentication
 
529
        } else {
 
530
            // Check we got a proper authentication type
 
531
            if (! defined('self::AUTH_' . strtoupper($type))) {
 
532
                /** @see Zend_Http_Client_Exception */
 
533
                require_once 'Zend/Http/Client/Exception.php';
 
534
                throw new Zend_Http_Client_Exception("Invalid or not supported authentication type: '$type'");
 
535
            }
 
536
 
 
537
            $this->auth = array(
 
538
                'user' => (string) $user,
 
539
                'password' => (string) $password,
 
540
                'type' => $type
 
541
            );
 
542
        }
 
543
 
 
544
        return $this;
 
545
    }
 
546
 
 
547
    /**
 
548
     * Set the HTTP client's cookie jar.
 
549
     *
 
550
     * A cookie jar is an object that holds and maintains cookies across HTTP requests
 
551
     * and responses.
 
552
     *
 
553
     * @param Zend_Http_CookieJar|boolean $cookiejar Existing cookiejar object, true to create a new one, false to disable
 
554
     * @return Zend_Http_Client
 
555
     * @throws Zend_Http_Client_Exception
 
556
     */
 
557
    public function setCookieJar($cookiejar = true)
 
558
    {
 
559
        if (! class_exists('Zend_Http_CookieJar'))
 
560
            require_once 'Zend/Http/CookieJar.php';
 
561
 
 
562
        if ($cookiejar instanceof Zend_Http_CookieJar) {
 
563
            $this->cookiejar = $cookiejar;
 
564
        } elseif ($cookiejar === true) {
 
565
            $this->cookiejar = new Zend_Http_CookieJar();
 
566
        } elseif (! $cookiejar) {
 
567
            $this->cookiejar = null;
 
568
        } else {
 
569
            /** @see Zend_Http_Client_Exception */
 
570
            require_once 'Zend/Http/Client/Exception.php';
 
571
            throw new Zend_Http_Client_Exception('Invalid parameter type passed as CookieJar');
 
572
        }
 
573
 
 
574
        return $this;
 
575
    }
 
576
 
 
577
    /**
 
578
     * Return the current cookie jar or null if none.
 
579
     *
 
580
     * @return Zend_Http_CookieJar|null
 
581
     */
 
582
    public function getCookieJar()
 
583
    {
 
584
        return $this->cookiejar;
 
585
    }
 
586
 
 
587
    /**
 
588
     * Add a cookie to the request. If the client has no Cookie Jar, the cookies
 
589
     * will be added directly to the headers array as "Cookie" headers.
 
590
     *
 
591
     * @param Zend_Http_Cookie|string $cookie
 
592
     * @param string|null $value If "cookie" is a string, this is the cookie value.
 
593
     * @return Zend_Http_Client
 
594
     * @throws Zend_Http_Client_Exception
 
595
     */
 
596
    public function setCookie($cookie, $value = null)
 
597
    {
 
598
        if (! class_exists('Zend_Http_Cookie'))
 
599
            require_once 'Zend/Http/Cookie.php';
 
600
 
 
601
        if (is_array($cookie)) {
 
602
            foreach ($cookie as $c => $v) {
 
603
                if (is_string($c)) {
 
604
                    $this->setCookie($c, $v);
 
605
                } else {
 
606
                    $this->setCookie($v);
 
607
                }
 
608
            }
 
609
 
 
610
            return $this;
 
611
        }
 
612
 
 
613
        if ($value !== null) $value = urlencode($value);
 
614
 
 
615
        if (isset($this->cookiejar)) {
 
616
            if ($cookie instanceof Zend_Http_Cookie) {
 
617
                $this->cookiejar->addCookie($cookie);
 
618
            } elseif (is_string($cookie) && $value !== null) {
 
619
                $cookie = Zend_Http_Cookie::fromString("{$cookie}={$value}", $this->uri);
 
620
                $this->cookiejar->addCookie($cookie);
 
621
            }
 
622
        } else {
 
623
            if ($cookie instanceof Zend_Http_Cookie) {
 
624
                $name = $cookie->getName();
 
625
                $value = $cookie->getValue();
 
626
                $cookie = $name;
 
627
            }
 
628
 
 
629
            if (preg_match("/[=,; \t\r\n\013\014]/", $cookie)) {
 
630
                /** @see Zend_Http_Client_Exception */
 
631
                require_once 'Zend/Http/Client/Exception.php';
 
632
                throw new Zend_Http_Client_Exception("Cookie name cannot contain these characters: =,; \t\r\n\013\014 ({$cookie})");
 
633
            }
 
634
 
 
635
            $value = addslashes($value);
 
636
 
 
637
            if (! isset($this->headers['cookie'])) $this->headers['cookie'] = array('Cookie', '');
 
638
            $this->headers['cookie'][1] .= $cookie . '=' . $value . '; ';
 
639
        }
 
640
 
 
641
        return $this;
 
642
    }
 
643
 
 
644
    /**
 
645
     * Set a file to upload (using a POST request)
 
646
     *
 
647
     * Can be used in two ways:
 
648
     *
 
649
     * 1. $data is null (default): $filename is treated as the name if a local file which
 
650
     *    will be read and sent. Will try to guess the content type using mime_content_type().
 
651
     * 2. $data is set - $filename is sent as the file name, but $data is sent as the file
 
652
     *    contents and no file is read from the file system. In this case, you need to
 
653
     *    manually set the Content-Type ($ctype) or it will default to
 
654
     *    application/octet-stream.
 
655
     *
 
656
     * @param string $filename Name of file to upload, or name to save as
 
657
     * @param string $formname Name of form element to send as
 
658
     * @param string $data Data to send (if null, $filename is read and sent)
 
659
     * @param string $ctype Content type to use (if $data is set and $ctype is
 
660
     *     null, will be application/octet-stream)
 
661
     * @return Zend_Http_Client
 
662
     * @throws Zend_Http_Client_Exception
 
663
     */
 
664
    public function setFileUpload($filename, $formname, $data = null, $ctype = null)
 
665
    {
 
666
        if ($data === null) {
 
667
            if (($data = @file_get_contents($filename)) === false) {
 
668
                /** @see Zend_Http_Client_Exception */
 
669
                require_once 'Zend/Http/Client/Exception.php';
 
670
                throw new Zend_Http_Client_Exception("Unable to read file '{$filename}' for upload");
 
671
            }
 
672
 
 
673
            if (! $ctype) $ctype = $this->_detectFileMimeType($filename);
 
674
        }
 
675
 
 
676
        // Force enctype to multipart/form-data
 
677
        $this->setEncType(self::ENC_FORMDATA);
 
678
 
 
679
        $this->files[$formname] = array(basename($filename), $ctype, $data);
 
680
 
 
681
        return $this;
 
682
    }
 
683
 
 
684
    /**
 
685
     * Set the encoding type for POST data
 
686
     *
 
687
     * @param string $enctype
 
688
     * @return Zend_Http_Client
 
689
     */
 
690
    public function setEncType($enctype = self::ENC_URLENCODED)
 
691
    {
 
692
        $this->enctype = $enctype;
 
693
 
 
694
        return $this;
 
695
    }
 
696
 
 
697
    /**
 
698
     * Set the raw (already encoded) POST data.
 
699
     *
 
700
     * This function is here for two reasons:
 
701
     * 1. For advanced user who would like to set their own data, already encoded
 
702
     * 2. For backwards compatibilty: If someone uses the old post($data) method.
 
703
     *    this method will be used to set the encoded data.
 
704
     *
 
705
     * @param string $data
 
706
     * @param string $enctype
 
707
     * @return Zend_Http_Client
 
708
     */
 
709
    public function setRawData($data, $enctype = null)
 
710
    {
 
711
        $this->raw_post_data = $data;
 
712
        $this->setEncType($enctype);
 
713
 
 
714
        return $this;
 
715
    }
 
716
 
 
717
    /**
 
718
     * Clear all GET and POST parameters
 
719
     *
 
720
     * Should be used to reset the request parameters if the client is
 
721
     * used for several concurrent requests.
 
722
     *
 
723
     * @return Zend_Http_Client
 
724
     */
 
725
    public function resetParameters()
 
726
    {
 
727
        // Reset parameter data
 
728
        $this->paramsGet     = array();
 
729
        $this->paramsPost    = array();
 
730
        $this->files         = array();
 
731
        $this->raw_post_data = null;
 
732
 
 
733
        // Clear outdated headers
 
734
        if (isset($this->headers[strtolower(self::CONTENT_TYPE)]))
 
735
            unset($this->headers[strtolower(self::CONTENT_TYPE)]);
 
736
        if (isset($this->headers[strtolower(self::CONTENT_LENGTH)]))
 
737
            unset($this->headers[strtolower(self::CONTENT_LENGTH)]);
 
738
 
 
739
        return $this;
 
740
    }
 
741
 
 
742
    /**
 
743
     * Get the last HTTP request as string
 
744
     *
 
745
     * @return string
 
746
     */
 
747
    public function getLastRequest()
 
748
    {
 
749
        return $this->last_request;
 
750
    }
 
751
 
 
752
    /**
 
753
     * Get the last HTTP response received by this client
 
754
     *
 
755
     * If $config['storeresponse'] is set to false, or no response was
 
756
     * stored yet, will return null
 
757
     *
 
758
     * @return Zend_Http_Response or null if none
 
759
     */
 
760
    public function getLastResponse()
 
761
    {
 
762
        return $this->last_response;
 
763
    }
 
764
 
 
765
    /**
 
766
     * Load the connection adapter
 
767
     *
 
768
     * While this method is not called more than one for a client, it is
 
769
     * seperated from ->request() to preserve logic and readability
 
770
     *
 
771
     * @param Zend_Http_Client_Adapter_Interface|string $adapter
 
772
     * @return null
 
773
     * @throws Zend_Http_Client_Exception
 
774
     */
 
775
    public function setAdapter($adapter)
 
776
    {
 
777
        if (is_string($adapter)) {
 
778
            try {
 
779
                Zend_Loader::loadClass($adapter);
 
780
            } catch (Zend_Exception $e) {
 
781
                /** @see Zend_Http_Client_Exception */
 
782
                require_once 'Zend/Http/Client/Exception.php';
 
783
                throw new Zend_Http_Client_Exception("Unable to load adapter '$adapter': {$e->getMessage()}");
 
784
            }
 
785
 
 
786
            $adapter = new $adapter;
 
787
        }
 
788
 
 
789
        if (! $adapter instanceof Zend_Http_Client_Adapter_Interface) {
 
790
            /** @see Zend_Http_Client_Exception */
 
791
            require_once 'Zend/Http/Client/Exception.php';
 
792
            throw new Zend_Http_Client_Exception('Passed adapter is not a HTTP connection adapter');
 
793
        }
 
794
 
 
795
        $this->adapter = $adapter;
 
796
        $config = $this->config;
 
797
        unset($config['adapter']);
 
798
        $this->adapter->setConfig($config);
 
799
    }
 
800
 
 
801
    /**
 
802
     * Send the HTTP request and return an HTTP response object
 
803
     *
 
804
     * @param string $method
 
805
     * @return Zend_Http_Response
 
806
     * @throws Zend_Http_Client_Exception
 
807
     */
 
808
    public function request($method = null)
 
809
    {
 
810
        if (! $this->uri instanceof Zend_Uri_Http) {
 
811
            /** @see Zend_Http_Client_Exception */
 
812
            require_once 'Zend/Http/Client/Exception.php';
 
813
            throw new Zend_Http_Client_Exception('No valid URI has been passed to the client');
 
814
        }
 
815
 
 
816
        if ($method) $this->setMethod($method);
 
817
        $this->redirectCounter = 0;
 
818
        $response = null;
 
819
 
 
820
        // Make sure the adapter is loaded
 
821
        if ($this->adapter == null) $this->setAdapter($this->config['adapter']);
 
822
 
 
823
        // Send the first request. If redirected, continue.
 
824
        do {
 
825
            // Clone the URI and add the additional GET parameters to it
 
826
            $uri = clone $this->uri;
 
827
            if (! empty($this->paramsGet)) {
 
828
                $query = $uri->getQuery();
 
829
                   if (! empty($query)) $query .= '&';
 
830
                $query .= http_build_query($this->paramsGet, null, '&');
 
831
 
 
832
                $uri->setQuery($query);
 
833
            }
 
834
 
 
835
            $body = $this->_prepareBody();
 
836
            $headers = $this->_prepareHeaders();
 
837
 
 
838
            // Open the connection, send the request and read the response
 
839
            $this->adapter->connect($uri->getHost(), $uri->getPort(),
 
840
                ($uri->getScheme() == 'https' ? true : false));
 
841
 
 
842
            $this->last_request = $this->adapter->write($this->method,
 
843
                $uri, $this->config['httpversion'], $headers, $body);
 
844
 
 
845
            $response = $this->adapter->read();
 
846
            if (! $response) {
 
847
                /** @see Zend_Http_Client_Exception */
 
848
                require_once 'Zend/Http/Client/Exception.php';
 
849
                throw new Zend_Http_Client_Exception('Unable to read response, or response is empty');
 
850
            }
 
851
 
 
852
            $response = Zend_Http_Response::fromString($response);
 
853
            if ($this->config['storeresponse']) $this->last_response = $response;
 
854
 
 
855
            // Load cookies into cookie jar
 
856
            if (isset($this->cookiejar)) $this->cookiejar->addCookiesFromResponse($response, $uri);
 
857
 
 
858
            // If we got redirected, look for the Location header
 
859
            if ($response->isRedirect() && ($location = $response->getHeader('location'))) {
 
860
 
 
861
                // Check whether we send the exact same request again, or drop the parameters
 
862
                // and send a GET request
 
863
                if ($response->getStatus() == 303 ||
 
864
                   ((! $this->config['strictredirects']) && ($response->getStatus() == 302 ||
 
865
                       $response->getStatus() == 301))) {
 
866
 
 
867
                    $this->resetParameters();
 
868
                    $this->setMethod(self::GET);
 
869
                }
 
870
 
 
871
                // If we got a well formed absolute URI
 
872
                if (Zend_Uri_Http::check($location)) {
 
873
                    $this->setHeaders('host', null);
 
874
                    $this->setUri($location);
 
875
 
 
876
                } else {
 
877
 
 
878
                    // Split into path and query and set the query
 
879
                    if (strpos($location, '?') !== false) {
 
880
                        list($location, $query) = explode('?', $location, 2);
 
881
                    } else {
 
882
                        $query = '';
 
883
                    }
 
884
                    $this->uri->setQuery($query);
 
885
 
 
886
                    // Else, if we got just an absolute path, set it
 
887
                    if(strpos($location, '/') === 0) {
 
888
                        $this->uri->setPath($location);
 
889
 
 
890
                        // Else, assume we have a relative path
 
891
                    } else {
 
892
                        // Get the current path directory, removing any trailing slashes
 
893
                        $path = $this->uri->getPath();
 
894
                        $path = rtrim(substr($path, 0, strrpos($path, '/')), "/");
 
895
                        $this->uri->setPath($path . '/' . $location);
 
896
                    }
 
897
                }
 
898
                ++$this->redirectCounter;
 
899
 
 
900
            } else {
 
901
                // If we didn't get any location, stop redirecting
 
902
                break;
 
903
            }
 
904
 
 
905
        } while ($this->redirectCounter < $this->config['maxredirects']);
 
906
 
 
907
        return $response;
 
908
    }
 
909
 
 
910
    /**
 
911
     * Prepare the request headers
 
912
     *
 
913
     * @return array
 
914
     */
 
915
    protected function _prepareHeaders()
 
916
    {
 
917
        $headers = array();
 
918
 
 
919
        // Set the host header
 
920
        if (! isset($this->headers['host'])) {
 
921
            $host = $this->uri->getHost();
 
922
 
 
923
            // If the port is not default, add it
 
924
            if (! (($this->uri->getScheme() == 'http' && $this->uri->getPort() == 80) ||
 
925
                  ($this->uri->getScheme() == 'https' && $this->uri->getPort() == 443))) {
 
926
                $host .= ':' . $this->uri->getPort();
 
927
            }
 
928
 
 
929
            $headers[] = "Host: {$host}";
 
930
        }
 
931
 
 
932
        // Set the connection header
 
933
        if (! isset($this->headers['connection'])) {
 
934
            if (! $this->config['keepalive']) $headers[] = "Connection: close";
 
935
        }
 
936
 
 
937
        // Set the Accept-encoding header if not set - depending on whether
 
938
        // zlib is available or not.
 
939
        if (! isset($this->headers['accept-encoding'])) {
 
940
            if (function_exists('gzinflate')) {
 
941
                $headers[] = 'Accept-encoding: gzip, deflate';
 
942
            } else {
 
943
                $headers[] = 'Accept-encoding: identity';
 
944
            }
 
945
        }
 
946
        
 
947
        // Set the Content-Type header
 
948
        if ($this->method == self::POST &&
 
949
           (! isset($this->headers[strtolower(self::CONTENT_TYPE)]) && isset($this->enctype))) {
 
950
 
 
951
            $headers[] = self::CONTENT_TYPE . ': ' . $this->enctype;
 
952
        }
 
953
        
 
954
        // Set the user agent header
 
955
        if (! isset($this->headers['user-agent']) && isset($this->config['useragent'])) {
 
956
            $headers[] = "User-Agent: {$this->config['useragent']}";
 
957
        }
 
958
 
 
959
        // Set HTTP authentication if needed
 
960
        if (is_array($this->auth)) {
 
961
            $auth = self::encodeAuthHeader($this->auth['user'], $this->auth['password'], $this->auth['type']);
 
962
            $headers[] = "Authorization: {$auth}";
 
963
        }
 
964
 
 
965
        // Load cookies from cookie jar
 
966
        if (isset($this->cookiejar)) {
 
967
            $cookstr = $this->cookiejar->getMatchingCookies($this->uri,
 
968
                true, Zend_Http_CookieJar::COOKIE_STRING_CONCAT);
 
969
 
 
970
            if ($cookstr) $headers[] = "Cookie: {$cookstr}";
 
971
        }
 
972
 
 
973
        // Add all other user defined headers
 
974
        foreach ($this->headers as $header) {
 
975
            list($name, $value) = $header;
 
976
            if (is_array($value))
 
977
                $value = implode(', ', $value);
 
978
 
 
979
            $headers[] = "$name: $value";
 
980
        }
 
981
 
 
982
        return $headers;
 
983
    }
 
984
 
 
985
    /**
 
986
     * Prepare the request body (for POST and PUT requests)
 
987
     *
 
988
     * @return string
 
989
     * @throws Zend_Http_Client_Exception
 
990
     */
 
991
    protected function _prepareBody()
 
992
    {
 
993
        // According to RFC2616, a TRACE request should not have a body.
 
994
        if ($this->method == self::TRACE) {
 
995
            return '';
 
996
        }
 
997
 
 
998
        // If we have raw_post_data set, just use it as the body.
 
999
        if (isset($this->raw_post_data)) {
 
1000
            $this->setHeaders(self::CONTENT_LENGTH, strlen($this->raw_post_data));
 
1001
            return $this->raw_post_data;
 
1002
        }
 
1003
 
 
1004
        $body = '';
 
1005
 
 
1006
        // If we have files to upload, force enctype to multipart/form-data
 
1007
        if (count ($this->files) > 0) $this->setEncType(self::ENC_FORMDATA);
 
1008
 
 
1009
        // If we have POST parameters or files, encode and add them to the body
 
1010
        if (count($this->paramsPost) > 0 || count($this->files) > 0) {
 
1011
            switch($this->enctype) {
 
1012
                case self::ENC_FORMDATA:
 
1013
                    // Encode body as multipart/form-data
 
1014
                    $boundary = '---ZENDHTTPCLIENT-' . md5(microtime());
 
1015
                    $this->setHeaders(self::CONTENT_TYPE, self::ENC_FORMDATA . "; boundary={$boundary}");
 
1016
 
 
1017
                    // Get POST parameters and encode them
 
1018
                    $params = $this->_getParametersRecursive($this->paramsPost);
 
1019
                    foreach ($params as $pp) {
 
1020
                        $body .= self::encodeFormData($boundary, $pp[0], $pp[1]);
 
1021
                    }
 
1022
 
 
1023
                    // Encode files
 
1024
                    foreach ($this->files as $name => $file) {
 
1025
                        $fhead = array(self::CONTENT_TYPE => $file[1]);
 
1026
                        $body .= self::encodeFormData($boundary, $name, $file[2], $file[0], $fhead);
 
1027
                    }
 
1028
 
 
1029
                    $body .= "--{$boundary}--\r\n";
 
1030
                    break;
 
1031
 
 
1032
                case self::ENC_URLENCODED:
 
1033
                    // Encode body as application/x-www-form-urlencoded
 
1034
                    $this->setHeaders(self::CONTENT_TYPE, self::ENC_URLENCODED);
 
1035
                    $body = http_build_query($this->paramsPost, '', '&');
 
1036
                    break;
 
1037
 
 
1038
                default:
 
1039
                    /** @see Zend_Http_Client_Exception */
 
1040
                    require_once 'Zend/Http/Client/Exception.php';
 
1041
                    throw new Zend_Http_Client_Exception("Cannot handle content type '{$this->enctype}' automatically." .
 
1042
                        " Please use Zend_Http_Client::setRawData to send this kind of content.");
 
1043
                    break;
 
1044
            }
 
1045
        }
 
1046
        
 
1047
        // Set the Content-Length if we have a body or if request is POST/PUT
 
1048
        if ($body || $this->method == self::POST || $this->method == self::PUT) {
 
1049
            $this->setHeaders(self::CONTENT_LENGTH, strlen($body));
 
1050
        }
 
1051
 
 
1052
        return $body;
 
1053
    }
 
1054
 
 
1055
    /**
 
1056
     * Helper method that gets a possibly multi-level parameters array (get or
 
1057
     * post) and flattens it.
 
1058
     *
 
1059
     * The method returns an array of (key, value) pairs (because keys are not
 
1060
     * necessarily unique. If one of the parameters in as array, it will also
 
1061
     * add a [] suffix to the key.
 
1062
     *
 
1063
     * @param array $parray The parameters array
 
1064
     * @param bool $urlencode Whether to urlencode the name and value
 
1065
     * @return array
 
1066
     */
 
1067
    protected function _getParametersRecursive($parray, $urlencode = false)
 
1068
    {
 
1069
        if (! is_array($parray)) return $parray;
 
1070
        $parameters = array();
 
1071
 
 
1072
        foreach ($parray as $name => $value) {
 
1073
            if ($urlencode) $name = urlencode($name);
 
1074
 
 
1075
            // If $value is an array, iterate over it
 
1076
            if (is_array($value)) {
 
1077
                $name .= ($urlencode ? '%5B%5D' : '[]');
 
1078
                foreach ($value as $subval) {
 
1079
                    if ($urlencode) $subval = urlencode($subval);
 
1080
                    $parameters[] = array($name, $subval);
 
1081
                }
 
1082
            } else {
 
1083
                if ($urlencode) $value = urlencode($value);
 
1084
                $parameters[] = array($name, $value);
 
1085
            }
 
1086
        }
 
1087
 
 
1088
        return $parameters;
 
1089
    }
 
1090
    
 
1091
    /**
 
1092
     * Attempt to detect the MIME type of a file using available extensions
 
1093
     * 
 
1094
     * This method will try to detect the MIME type of a file. If the fileinfo
 
1095
     * extension is available, it will be used. If not, the mime_magic 
 
1096
     * extension which is deprected but is still available in many PHP setups
 
1097
     * will be tried. 
 
1098
     * 
 
1099
     * If neither extension is available, the default application/octet-stream
 
1100
     * MIME type will be returned
 
1101
     *
 
1102
     * @param  string $file File path
 
1103
     * @return string       MIME type
 
1104
     */
 
1105
    protected function _detectFileMimeType($file)
 
1106
    {
 
1107
        $type = null;
 
1108
        
 
1109
        // First try with fileinfo functions
 
1110
        if (function_exists('finfo_open')) {
 
1111
            if (self::$_fileInfoDb === null) {
 
1112
                self::$_fileInfoDb = @finfo_open(FILEINFO_MIME);
 
1113
            }
 
1114
            
 
1115
            if (self::$_fileInfoDb) { 
 
1116
                $type = finfo_file(self::$_fileInfoDb, $file);
 
1117
            }
 
1118
            
 
1119
        } elseif (function_exists('mime_content_type')) {
 
1120
            $type = mime_content_type($file);
 
1121
        }
 
1122
        
 
1123
        // Fallback to the default application/octet-stream
 
1124
        if (! $type) {
 
1125
            $type = 'application/octet-stream';
 
1126
        }
 
1127
        
 
1128
        return $type;
 
1129
    }
 
1130
 
 
1131
    /**
 
1132
     * Encode data to a multipart/form-data part suitable for a POST request.
 
1133
     *
 
1134
     * @param string $boundary
 
1135
     * @param string $name
 
1136
     * @param mixed $value
 
1137
     * @param string $filename
 
1138
     * @param array $headers Associative array of optional headers @example ("Content-Transfer-Encoding" => "binary")
 
1139
     * @return string
 
1140
     */
 
1141
    public static function encodeFormData($boundary, $name, $value, $filename = null, $headers = array()) {
 
1142
        $ret = "--{$boundary}\r\n" .
 
1143
            'Content-Disposition: form-data; name="' . $name .'"';
 
1144
 
 
1145
        if ($filename) $ret .= '; filename="' . $filename . '"';
 
1146
        $ret .= "\r\n";
 
1147
 
 
1148
        foreach ($headers as $hname => $hvalue) {
 
1149
            $ret .= "{$hname}: {$hvalue}\r\n";
 
1150
        }
 
1151
        $ret .= "\r\n";
 
1152
 
 
1153
        $ret .= "{$value}\r\n";
 
1154
 
 
1155
        return $ret;
 
1156
    }
 
1157
 
 
1158
    /**
 
1159
     * Create a HTTP authentication "Authorization:" header according to the
 
1160
     * specified user, password and authentication method.
 
1161
     *
 
1162
     * @see http://www.faqs.org/rfcs/rfc2617.html
 
1163
     * @param string $user
 
1164
     * @param string $password
 
1165
     * @param string $type
 
1166
     * @return string
 
1167
     * @throws Zend_Http_Client_Exception
 
1168
     */
 
1169
    public static function encodeAuthHeader($user, $password, $type = self::AUTH_BASIC)
 
1170
    {
 
1171
        $authHeader = null;
 
1172
 
 
1173
        switch ($type) {
 
1174
            case self::AUTH_BASIC:
 
1175
                // In basic authentication, the user name cannot contain ":"
 
1176
                if (strpos($user, ':') !== false) {
 
1177
                    /** @see Zend_Http_Client_Exception */
 
1178
                    require_once 'Zend/Http/Client/Exception.php';
 
1179
                    throw new Zend_Http_Client_Exception("The user name cannot contain ':' in 'Basic' HTTP authentication");
 
1180
                }
 
1181
 
 
1182
                $authHeader = 'Basic ' . base64_encode($user . ':' . $password);
 
1183
                break;
 
1184
 
 
1185
            //case self::AUTH_DIGEST:
 
1186
                /**
 
1187
                 * @todo Implement digest authentication
 
1188
                 */
 
1189
            //    break;
 
1190
 
 
1191
            default:
 
1192
                /** @see Zend_Http_Client_Exception */
 
1193
                require_once 'Zend/Http/Client/Exception.php';
 
1194
                throw new Zend_Http_Client_Exception("Not a supported HTTP authentication type: '$type'");
 
1195
        }
 
1196
 
 
1197
        return $authHeader;
 
1198
    }
 
1199
}