source: trunk/library/PEAR/PEAR/REST.php @ 5146

Revision 5146, 16.8 KB checked in by wmerlotto, 12 years ago (diff)

Ticket #2305 - Enviando alteracoes, desenvolvidas internamente na Prognus. Library: adicionando arquivos.

Line 
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 */
20require_once 'PEAR.php';
21require_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 */
35class 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}
Note: See TracBrowser for help on using the repository browser.