PEAR
[ class tree: PEAR ] [ index: PEAR ] [ all elements ]

Source for file REST.php

Documentation is available at REST.php

  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|falsecontents 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|falsecontents 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$retnulltrue$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$lastmodifiedfalse$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($content05);
  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$lastmodifiedfalse$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($cachefileserialize($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$errstr15);
  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($fp1024))) {
  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($replyarray(200301302303305307))) {
  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($fp8192)) {
  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. }

Documentation generated on Wed, 06 Jul 2011 23:31:17 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.