Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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