Subversion Repositories Applications.gtt

Rev

Rev 94 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
94 jpm 1
<?php
2
/**
3
 * PEAR_REST
4
 *
5
 * PHP versions 4 and 5
6
 *
7
 * @category   pear
8
 * @package    PEAR
9
 * @author     Greg Beaver <cellog@php.net>
187 mathias 10
 * @copyright  1997-2009 The Authors
11
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
94 jpm 12
 * @link       http://pear.php.net/package/PEAR
13
 * @since      File available since Release 1.4.0a1
14
 */
15
 
16
/**
17
 * For downloading xml files
18
 */
19
require_once 'PEAR.php';
20
require_once 'PEAR/XMLParser.php';
21
 
22
/**
23
 * Intelligently retrieve data, following hyperlinks if necessary, and re-directing
24
 * as well
25
 * @category   pear
26
 * @package    PEAR
27
 * @author     Greg Beaver <cellog@php.net>
187 mathias 28
 * @copyright  1997-2009 The Authors
29
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
30
 * @version    Release: 1.10.1
94 jpm 31
 * @link       http://pear.php.net/package/PEAR
32
 * @since      Class available since Release 1.4.0a1
33
 */
34
class PEAR_REST
35
{
36
    var $config;
37
    var $_options;
187 mathias 38
 
39
    function __construct(&$config, $options = array())
94 jpm 40
    {
187 mathias 41
        $this->config   = &$config;
94 jpm 42
        $this->_options = $options;
43
    }
44
 
45
    /**
46
     * Retrieve REST data, but always retrieve the local cache if it is available.
47
     *
48
     * This is useful for elements that should never change, such as information on a particular
49
     * release
50
     * @param string full URL to this resource
51
     * @param array|false contents of the accept-encoding header
52
     * @param boolean     if true, xml will be returned as a string, otherwise, xml will be
53
     *                    parsed using PEAR_XMLParser
54
     * @return string|array
55
     */
187 mathias 56
    function retrieveCacheFirst($url, $accept = false, $forcestring = false, $channel = false)
94 jpm 57
    {
58
        $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
59
            md5($url) . 'rest.cachefile';
187 mathias 60
 
94 jpm 61
        if (file_exists($cachefile)) {
62
            return unserialize(implode('', file($cachefile)));
63
        }
187 mathias 64
 
65
        return $this->retrieveData($url, $accept, $forcestring, $channel);
94 jpm 66
    }
67
 
68
    /**
69
     * Retrieve a remote REST resource
70
     * @param string full URL to this resource
71
     * @param array|false contents of the accept-encoding header
72
     * @param boolean     if true, xml will be returned as a string, otherwise, xml will be
73
     *                    parsed using PEAR_XMLParser
74
     * @return string|array
75
     */
187 mathias 76
    function retrieveData($url, $accept = false, $forcestring = false, $channel = false)
94 jpm 77
    {
78
        $cacheId = $this->getCacheId($url);
79
        if ($ret = $this->useLocalCache($url, $cacheId)) {
80
            return $ret;
81
        }
187 mathias 82
 
83
        $file = $trieddownload = false;
94 jpm 84
        if (!isset($this->_options['offline'])) {
85
            $trieddownload = true;
187 mathias 86
            $file = $this->downloadHttp($url, $cacheId ? $cacheId['lastChange'] : false, $accept, $channel);
94 jpm 87
        }
187 mathias 88
 
94 jpm 89
        if (PEAR::isError($file)) {
187 mathias 90
            if ($file->getCode() !== -9276) {
94 jpm 91
                return $file;
92
            }
187 mathias 93
 
94
            $trieddownload = false;
95
            $file = false; // use local copy if available on socket connect error
94 jpm 96
        }
187 mathias 97
 
94 jpm 98
        if (!$file) {
99
            $ret = $this->getCache($url);
100
            if (!PEAR::isError($ret) && $trieddownload) {
101
                // reset the age of the cache if the server says it was unmodified
187 mathias 102
                $result = $this->saveCache($url, $ret, null, true, $cacheId);
103
                if (PEAR::isError($result)) {
104
                    return PEAR::raiseError($result->getMessage());
105
                }
94 jpm 106
            }
187 mathias 107
 
94 jpm 108
            return $ret;
109
        }
187 mathias 110
 
94 jpm 111
        if (is_array($file)) {
187 mathias 112
            $headers      = $file[2];
94 jpm 113
            $lastmodified = $file[1];
187 mathias 114
            $content      = $file[0];
94 jpm 115
        } else {
187 mathias 116
            $headers      = array();
94 jpm 117
            $lastmodified = false;
187 mathias 118
            $content      = $file;
94 jpm 119
        }
187 mathias 120
 
94 jpm 121
        if ($forcestring) {
187 mathias 122
            $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId);
123
            if (PEAR::isError($result)) {
124
                return PEAR::raiseError($result->getMessage());
125
            }
126
 
94 jpm 127
            return $content;
128
        }
187 mathias 129
 
94 jpm 130
        if (isset($headers['content-type'])) {
187 mathias 131
            $content_type = explode(";", $headers['content-type']);
132
            $content_type = $content_type[0];
133
            switch ($content_type) {
94 jpm 134
                case 'text/xml' :
135
                case 'application/xml' :
187 mathias 136
                case 'text/plain' :
137
                    if ($content_type === 'text/plain') {
138
                        $check = substr($content, 0, 5);
139
                        if ($check !== '<?xml') {
140
                            break;
141
                        }
142
                    }
143
 
94 jpm 144
                    $parser = new PEAR_XMLParser;
145
                    PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
146
                    $err = $parser->parse($content);
147
                    PEAR::popErrorHandling();
148
                    if (PEAR::isError($err)) {
149
                        return PEAR::raiseError('Invalid xml downloaded from "' . $url . '": ' .
150
                            $err->getMessage());
151
                    }
152
                    $content = $parser->getData();
153
                case 'text/html' :
154
                default :
155
                    // use it as a string
156
            }
157
        } else {
158
            // assume XML
159
            $parser = new PEAR_XMLParser;
160
            $parser->parse($content);
161
            $content = $parser->getData();
162
        }
187 mathias 163
 
164
        $result = $this->saveCache($url, $content, $lastmodified, false, $cacheId);
165
        if (PEAR::isError($result)) {
166
            return PEAR::raiseError($result->getMessage());
167
        }
168
 
94 jpm 169
        return $content;
170
    }
171
 
172
    function useLocalCache($url, $cacheid = null)
173
    {
174
        if ($cacheid === null) {
175
            $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
176
                md5($url) . 'rest.cacheid';
187 mathias 177
            if (!file_exists($cacheidfile)) {
94 jpm 178
                return false;
179
            }
187 mathias 180
 
181
            $cacheid = unserialize(implode('', file($cacheidfile)));
94 jpm 182
        }
187 mathias 183
 
94 jpm 184
        $cachettl = $this->config->get('cache_ttl');
185
        // If cache is newer than $cachettl seconds, we use the cache!
186
        if (time() - $cacheid['age'] < $cachettl) {
187
            return $this->getCache($url);
188
        }
187 mathias 189
 
94 jpm 190
        return false;
191
    }
192
 
193
    function getCacheId($url)
194
    {
195
        $cacheidfile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
196
            md5($url) . 'rest.cacheid';
187 mathias 197
 
198
        if (!file_exists($cacheidfile)) {
94 jpm 199
            return false;
200
        }
187 mathias 201
 
202
        $ret = unserialize(implode('', file($cacheidfile)));
203
        return $ret;
94 jpm 204
    }
205
 
206
    function getCache($url)
207
    {
208
        $cachefile = $this->config->get('cache_dir') . DIRECTORY_SEPARATOR .
209
            md5($url) . 'rest.cachefile';
187 mathias 210
 
211
        if (!file_exists($cachefile)) {
94 jpm 212
            return PEAR::raiseError('No cached content available for "' . $url . '"');
213
        }
187 mathias 214
 
215
        return unserialize(implode('', file($cachefile)));
94 jpm 216
    }
217
 
218
    /**
219
     * @param string full URL to REST resource
220
     * @param string original contents of the REST resource
221
     * @param array  HTTP Last-Modified and ETag headers
222
     * @param bool   if true, then the cache id file should be regenerated to
223
     *               trigger a new time-to-live value
224
     */
225
    function saveCache($url, $contents, $lastmodified, $nochange = false, $cacheid = null)
226
    {
187 mathias 227
        $cache_dir   = $this->config->get('cache_dir');
228
        $d           = $cache_dir . DIRECTORY_SEPARATOR . md5($url);
229
        $cacheidfile = $d . 'rest.cacheid';
230
        $cachefile   = $d . 'rest.cachefile';
231
 
232
        if (!is_dir($cache_dir)) {
233
            if (System::mkdir(array('-p', $cache_dir)) === false) {
234
              return PEAR::raiseError("The value of config option cache_dir ($cache_dir) is not a directory and attempts to create the directory failed.");
235
            }
236
        }
237
 
238
        if (!is_writeable($cache_dir)) {
239
            // If writing to the cache dir is not going to work, silently do nothing.
240
            // An ugly hack, but retains compat with PEAR 1.9.1 where many commands
241
            // work fine as non-root user (w/out write access to default cache dir).
242
            return true;
243
        }
244
 
94 jpm 245
        if ($cacheid === null && $nochange) {
246
            $cacheid = unserialize(implode('', file($cacheidfile)));
247
        }
248
 
187 mathias 249
        $idData = serialize(array(
250
            'age'        => time(),
251
            'lastChange' => ($nochange ? $cacheid['lastChange'] : $lastmodified),
252
        ));
94 jpm 253
 
187 mathias 254
        $result = $this->saveCacheFile($cacheidfile, $idData);
255
        if (PEAR::isError($result)) {
256
            return $result;
257
        } elseif ($nochange) {
94 jpm 258
            return true;
259
        }
187 mathias 260
 
261
        $result = $this->saveCacheFile($cachefile, serialize($contents));
262
        if (PEAR::isError($result)) {
94 jpm 263
            if (file_exists($cacheidfile)) {
187 mathias 264
              @unlink($cacheidfile);
94 jpm 265
            }
187 mathias 266
 
267
            return $result;
94 jpm 268
        }
187 mathias 269
 
94 jpm 270
        return true;
271
    }
272
 
187 mathias 273
    function saveCacheFile($file, $contents)
274
    {
275
        $len = strlen($contents);
276
 
277
        $cachefile_fp = @fopen($file, 'xb'); // x is the O_CREAT|O_EXCL mode
278
        if ($cachefile_fp !== false) { // create file
279
            if (fwrite($cachefile_fp, $contents, $len) < $len) {
280
                fclose($cachefile_fp);
281
                return PEAR::raiseError("Could not write $file.");
282
            }
283
        } else { // update file
284
            $cachefile_fp = @fopen($file, 'r+b'); // do not truncate file
285
            if (!$cachefile_fp) {
286
                return PEAR::raiseError("Could not open $file for writing.");
287
            }
288
 
289
            if (OS_WINDOWS) {
290
                $not_symlink     = !is_link($file); // see bug #18834
291
            } else {
292
                $cachefile_lstat = lstat($file);
293
                $cachefile_fstat = fstat($cachefile_fp);
294
                $not_symlink     = $cachefile_lstat['mode'] == $cachefile_fstat['mode']
295
                                   && $cachefile_lstat['ino']  == $cachefile_fstat['ino']
296
                                   && $cachefile_lstat['dev']  == $cachefile_fstat['dev']
297
                                   && $cachefile_fstat['nlink'] === 1;
298
            }
299
 
300
            if ($not_symlink) {
301
                ftruncate($cachefile_fp, 0); // NOW truncate
302
                if (fwrite($cachefile_fp, $contents, $len) < $len) {
303
                    fclose($cachefile_fp);
304
                    return PEAR::raiseError("Could not write $file.");
305
                }
306
            } else {
307
                fclose($cachefile_fp);
308
                $link = function_exists('readlink') ? readlink($file) : $file;
309
                return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $file . ' as it is symlinked to ' . $link . ' - Possible symlink attack');
310
            }
311
        }
312
 
313
        fclose($cachefile_fp);
314
        return true;
315
    }
316
 
94 jpm 317
    /**
318
     * Efficiently Download a file through HTTP.  Returns downloaded file as a string in-memory
319
     * This is best used for small files
320
     *
321
     * If an HTTP proxy has been configured (http_proxy PEAR_Config
322
     * setting), the proxy will be used.
323
     *
324
     * @param string  $url       the URL to download
325
     * @param string  $save_dir  directory to save file in
326
     * @param false|string|array $lastmodified header values to check against for caching
327
     *                           use false to return the header values from this download
328
     * @param false|array $accept Accept headers to send
329
     * @return string|array  Returns the contents of the downloaded file or a PEAR
330
     *                       error on failure.  If the error is caused by
331
     *                       socket-related errors, the error object will
332
     *                       have the fsockopen error code available through
333
     *                       getCode().  If caching is requested, then return the header
334
     *                       values.
335
     *
336
     * @access public
337
     */
187 mathias 338
    function downloadHttp($url, $lastmodified = null, $accept = false, $channel = false)
94 jpm 339
    {
187 mathias 340
        static $redirect = 0;
341
        // always reset , so we are clean case of error
342
        $wasredirect = $redirect;
343
        $redirect = 0;
344
 
94 jpm 345
        $info = parse_url($url);
346
        if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
347
            return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
348
        }
187 mathias 349
 
94 jpm 350
        if (!isset($info['host'])) {
351
            return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
352
        }
187 mathias 353
 
354
        $host   = isset($info['host']) ? $info['host'] : null;
355
        $port   = isset($info['port']) ? $info['port'] : null;
356
        $path   = isset($info['path']) ? $info['path'] : null;
357
        $schema = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
358
 
94 jpm 359
        $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
187 mathias 360
        if ($this->config->get('http_proxy')&&
361
              $proxy = parse_url($this->config->get('http_proxy'))
362
        ) {
94 jpm 363
            $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
187 mathias 364
            if ($schema === 'https') {
94 jpm 365
                $proxy_host = 'ssl://' . $proxy_host;
366
            }
187 mathias 367
 
368
            $proxy_port   = isset($proxy['port']) ? $proxy['port'] : 8080;
369
            $proxy_user   = isset($proxy['user']) ? urldecode($proxy['user']) : null;
370
            $proxy_pass   = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
371
            $proxy_schema = (isset($proxy['scheme']) && $proxy['scheme'] == 'https') ? 'https' : 'http';
94 jpm 372
        }
187 mathias 373
 
94 jpm 374
        if (empty($port)) {
187 mathias 375
            $port = (isset($info['scheme']) && $info['scheme'] == 'https')  ? 443 : 80;
94 jpm 376
        }
187 mathias 377
 
378
        if (isset($proxy['host'])) {
94 jpm 379
            $request = "GET $url HTTP/1.1\r\n";
380
        } else {
381
            $request = "GET $path HTTP/1.1\r\n";
382
        }
383
 
187 mathias 384
        $request .= "Host: $host\r\n";
94 jpm 385
        $ifmodifiedsince = '';
386
        if (is_array($lastmodified)) {
387
            if (isset($lastmodified['Last-Modified'])) {
388
                $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
389
            }
187 mathias 390
 
94 jpm 391
            if (isset($lastmodified['ETag'])) {
392
                $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
393
            }
394
        } else {
395
            $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
396
        }
187 mathias 397
 
398
        $request .= $ifmodifiedsince .
399
            "User-Agent: PEAR/1.10.1/PHP/" . PHP_VERSION . "\r\n";
400
 
401
        $username = $this->config->get('username', null, $channel);
402
        $password = $this->config->get('password', null, $channel);
403
 
94 jpm 404
        if ($username && $password) {
405
            $tmp = base64_encode("$username:$password");
406
            $request .= "Authorization: Basic $tmp\r\n";
407
        }
187 mathias 408
 
94 jpm 409
        if ($proxy_host != '' && $proxy_user != '') {
410
            $request .= 'Proxy-Authorization: Basic ' .
411
                base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
412
        }
187 mathias 413
 
94 jpm 414
        if ($accept) {
415
            $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
416
        }
187 mathias 417
 
418
        $request .= "Accept-Encoding:\r\n";
94 jpm 419
        $request .= "Connection: close\r\n";
420
        $request .= "\r\n";
187 mathias 421
 
94 jpm 422
        if ($proxy_host != '') {
423
            $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr, 15);
424
            if (!$fp) {
187 mathias 425
                return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", -9276);
94 jpm 426
            }
427
        } else {
187 mathias 428
            if ($schema === 'https') {
94 jpm 429
                $host = 'ssl://' . $host;
430
            }
187 mathias 431
 
94 jpm 432
            $fp = @fsockopen($host, $port, $errno, $errstr);
433
            if (!$fp) {
434
                return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
435
            }
436
        }
187 mathias 437
 
94 jpm 438
        fwrite($fp, $request);
187 mathias 439
 
94 jpm 440
        $headers = array();
187 mathias 441
        $reply   = 0;
442
        while ($line = trim(fgets($fp, 1024))) {
443
            if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
94 jpm 444
                $headers[strtolower($matches[1])] = trim($matches[2]);
445
            } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
187 mathias 446
                $reply = (int)$matches[1];
447
                if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
94 jpm 448
                    return false;
449
                }
187 mathias 450
 
451
                if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
452
                    return PEAR::raiseError("File $schema://$host:$port$path not valid (received: $line)");
94 jpm 453
                }
454
            }
455
        }
187 mathias 456
 
457
        if ($reply != 200) {
458
            if (!isset($headers['location'])) {
459
                return PEAR::raiseError("File $schema://$host:$port$path not valid (redirected but no location)");
460
            }
461
 
462
            if ($wasredirect > 4) {
463
                return PEAR::raiseError("File $schema://$host:$port$path not valid (redirection looped more than 5 times)");
464
            }
465
 
466
            $redirect = $wasredirect + 1;
467
            return $this->downloadHttp($headers['location'], $lastmodified, $accept, $channel);
94 jpm 468
        }
187 mathias 469
 
470
        $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
471
 
94 jpm 472
        $data = '';
473
        while ($chunk = @fread($fp, 8192)) {
474
            $data .= $chunk;
475
        }
476
        fclose($fp);
187 mathias 477
 
94 jpm 478
        if ($lastmodified === false || $lastmodified) {
479
            if (isset($headers['etag'])) {
480
                $lastmodified = array('ETag' => $headers['etag']);
481
            }
187 mathias 482
 
94 jpm 483
            if (isset($headers['last-modified'])) {
484
                if (is_array($lastmodified)) {
485
                    $lastmodified['Last-Modified'] = $headers['last-modified'];
486
                } else {
487
                    $lastmodified = $headers['last-modified'];
488
                }
489
            }
187 mathias 490
 
94 jpm 491
            return array($data, $lastmodified, $headers);
492
        }
187 mathias 493
 
94 jpm 494
        return $data;
495
    }
496
}