diff --git a/thirdparty/pear/HTTP/Client.php b/thirdparty/pear/HTTP/Client.php new file mode 100644 index 0000000..5708cc3 --- /dev/null +++ b/thirdparty/pear/HTTP/Client.php @@ -0,0 +1,461 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: Client.php,v 1.4 2004/03/23 13:35:37 avb Exp $ + +require_once 'HTTP/Request.php'; +require_once 'HTTP/Client/CookieManager.php'; + +/** + * A simple HTTP client class. + * + * The class wraps around HTTP_Request providing a higher-level + * API for performing multiple HTTP requests + * + * @package HTTP_Client + * @author Alexey Borzov + * @version $Revision: 1.4 $ + */ +class HTTP_Client +{ + /** + * An HTTP_Client_CookieManager instance + * @var object + */ + var $_cookieManager; + + /** + * Received HTTP responses + * @var array + */ + var $_responses; + + /** + * Default headers to send on every request + * @var array + */ + var $_defaultHeaders = array(); + + /** + * Default parameters for HTTP_Request's constructor + * @var array + */ + var $_defaultRequestParams = array(); + + /** + * How many redirects were done + * @var integer + */ + var $_redirectCount = 0; + + /** + * Maximum allowed redirects + * @var integer + */ + var $_maxRedirects = 5; + + /** + * Listeners attached to the client + * @var array + */ + var $_listeners = array(); + + /** + * Whether the listener should be propagated to Request objects + * @var array + */ + var $_propagate = array(); + + /** + * Whether to keep all the responses or just the most recent one + * @var boolean + */ + var $_isHistoryEnabled = true; + + /** + * Constructor + * + * @access public + * @param array Parameters to pass to HTTP_Request's constructor + * @param array Default headers to send on every request + */ + function HTTP_Client($defaultRequestParams = null, $defaultHeaders = null) + { + $this->_cookieManager =& new HTTP_Client_CookieManager(); + if (isset($defaultHeaders)) { + $this->setDefaultHeader($defaultHeaders); + } + if (isset($defaultRequestParams)) { + $this->setRequestParameter($defaultRequestParams); + } + } + + + /** + * Sets the maximum redirects that will be processed. + * + * Setting this to 0 disables redirect processing. If not 0 and the + * number of redirects in a request is bigger than this number, then an + * error will be raised. + * + * @access public + * @param int Max number of redirects to process + */ + function setMaxRedirects($value) + { + $this->_maxRedirects = $value; + } + + + /** + * Sets whether to keep all the responses or just the most recent one + * + * @access public + * @param bool Whether to enable history + */ + function enableHistory($enable) + { + $this->_isHistoryEnabled = (bool)$enable; + } + + /** + * Creates a HTTP_Request objects, applying all the necessary defaults + * + * @param string URL + * @param integer Method, constants are defined in HTTP_Request + * @access private + * @return object HTTP_Request object with all defaults applied + */ + function &_createRequest($url, $method = HTTP_REQUEST_METHOD_GET) + { + $req =& new HTTP_Request($url, $this->_defaultRequestParams); + $req->setMethod($method); + foreach ($this->_defaultHeaders as $name => $value) { + $req->addHeader($name, $value); + } + $this->_cookieManager->passCookies($req); + foreach ($this->_propagate as $id => $propagate) { + if ($propagate) { + $req->attach($this->_listeners[$id]); + } + } + return $req; + } + + + /** + * Sends a 'HEAD' HTTP request + * + * @param string URL + * @access public + * @return integer HTTP response code + * @throws PEAR_Error + */ + function head($url) + { + $request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_HEAD); + return $this->_performRequest($request); + } + + + /** + * Sends a 'GET' HTTP request + * + * @param string URL + * @param mixed additional data to send + * @param boolean Whether the data is already urlencoded + * @access public + * @return integer HTTP response code + * @throws PEAR_Error + */ + function get($url, $data = null, $preEncoded = false) + { + $request =& $this->_createRequest($url); + if (is_array($data)) { + foreach ($data as $name => $value) { + $request->addQueryString($name, $value, $preEncoded); + } + } elseif (isset($data)) { + $request->addRawQueryString($data, $preEncoded); + } + return $this->_performRequest($request); + } + + + /** + * Sends a 'POST' HTTP request + * + * @param string URL + * @param mixed Data to send + * @param boolean Whether the data is already urlencoded + * @param array Files to upload. Elements of the array should have the form: + * array(name, filename(s)[, content type]), see HTTP_Request::addFile() + * @access public + * @return integer HTTP response code + * @throws PEAR_Error + */ + function post($url, $data, $preEncoded = false, $files = array()) + { + $request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_POST); + if (is_array($data)) { + foreach ($data as $name => $value) { + $request->addPostData($name, $value, $preEncoded); + } + } else { + $request->addRawPostData($data, $preEncoded); + } + foreach ($files as $fileData) { + $res = call_user_func_array(array(&$request, 'addFile'), $fileData); + if (PEAR::isError($res)) { + return $res; + } + } + return $this->_performRequest($request); + } + + + /** + * Sets default header(s) for HTTP requests + * + * @param mixed header name or array ('header name' => 'header value') + * @param string header value if $name is not an array + * @access public + */ + function setDefaultHeader($name, $value = null) + { + if (is_array($name)) { + $this->_defaultHeaders = array_merge($this->_defaultHeaders, $name); + } else { + $this->_defaultHeaders[$name] = $value; + } + } + + + /** + * Sets parameter(s) for HTTP requests + * + * @param mixed parameter name or array ('parameter name' => 'parameter value') + * @param string parameter value if $name is not an array + * @access public + */ + function setRequestParameter($name, $value = null) + { + if (is_array($name)) { + $this->_defaultRequestParams = array_merge($this->_defaultRequestParams, $name); + } else { + $this->_defaultRequestParams[$name] = $value; + } + } + + + /** + * Performs a request, processes redirects + * + * @param object HTTP_Request object + * @access private + * @return integer HTTP response code + * @throws PEAR_Error + */ + function _performRequest(&$request) + { + // If this is not a redirect, notify the listeners of new request + if (0 == $this->_redirectCount) { + $this->_notify('request', $request->_url->getUrl()); + } + if (PEAR::isError($err = $request->sendRequest())) { + return $err; + } + $this->_pushResponse($request); + + $code = $request->getResponseCode(); + if ($this->_maxRedirects > 0 && in_array($code, array(300, 301, 302, 303, 307))) { + if (++$this->_redirectCount > $this->_maxRedirects) { + return PEAR::raiseError('Too many redirects'); + } + $location = $request->getResponseHeader('Location'); + if ('' == $location) { + return PEAR::raiseError("No 'Location' field on redirect"); + } + $url = $this->_redirectUrl($request->_url, $location); + // Notify of redirection + $this->_notify('httpRedirect', $url); + // we access the private properties directly, as there are no accessors for them + switch ($request->_method) { + case HTTP_REQUEST_METHOD_POST: + if (302 == $code || 303 == $code) { + return $this->get($url); + } else { + $postFiles = array(); + foreach ($request->_postFiles as $name => $data) { + $postFiles[] = array($name, $data['name'], $data['type']); + } + return $this->post($url, $request->_postData, true, $postFiles); + } + case HTTP_REQUEST_METHOD_HEAD: + return (303 == $code? $this->get($url): $this->head($url)); + case HTTP_REQUEST_METHOD_GET: + default: + return $this->get($url); + } // switch + + } else { + $this->_redirectCount = 0; + if (400 >= $code) { + $this->_notify('httpSuccess'); + $this->setDefaultHeader('Referer', $request->_url->getUrl()); + // some result processing should go here + } else { + $this->_notify('httpError'); + } + } + return $code; + } + + + /** + * Returns the most recent HTTP response + * + * @access public + * @return array + */ + function ¤tResponse() + { + return $this->_responses[count($this->_responses) - 1]; + } + + + /** + * Saves the server's response to responses list + * + * @param object HTTP_Request object, with request already sent + * @access private + */ + function _pushResponse(&$request) + { + $this->_cookieManager->updateCookies($request); + $idx = $this->_isHistoryEnabled? count($this->_responses): 0; + $this->_responses[$idx] = array( + 'code' => $request->getResponseCode(), + 'headers' => $request->getResponseHeader(), + 'body' => $request->getResponseBody() + ); + } + + + /** + * Clears object's internal properties + * + * @access public + */ + function reset() + { + $this->_cookieManager->reset(); + $this->_responses = array(); + $this->_defaultHeaders = array(); + $this->_defaultRequestParams = array(); + } + + + /** + * Adds a Listener to the list of listeners that are notified of + * the object's events + * + * @param object HTTP_Request_Listener instance to attach + * @param boolean Whether the listener should be attached to the + * created HTTP_Request objects + * @return boolean whether the listener was successfully attached + * @access public + */ + function attach(&$listener, $propagate = false) + { + if (!is_a($listener, 'HTTP_Request_Listener')) { + return false; + } + $this->_listeners[$listener->getId()] =& $listener; + $this->_propagate[$listener->getId()] = $propagate; + return true; + } + + + /** + * Removes a Listener from the list of listeners + * + * @param object HTTP_Request_Listener instance to detach + * @return boolean whether the listener was successfully detached + * @access public + */ + function detach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener') || + !isset($this->_listeners[$listener->getId()])) { + return false; + } + unset($this->_listeners[$listener->getId()], $this->_propagate[$listener->getId()]); + return true; + } + + + /** + * Notifies all registered listeners of an event. + * + * Currently available events are: + * 'request': sent on HTTP request that is not a redirect + * 'httpSuccess': sent when we receive a successfull 2xx response + * 'httpRedirect': sent when we receive a redirection response + * 'httpError': sent on 4xx, 5xx response + * + * @param string Event name + * @param mixed Additional data + * @access private + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } + + + /** + * Calculates the absolute URL of a redirect + * + * @param object Net_Url object containing the request URL + * @param string Value of the 'Location' response header + * @return string Absolute URL we are being redirected to + * @access private + */ + function _redirectUrl($url, $location) + { + if (preg_match('!^https?://!i', $location)) { + return $location; + } else { + if ('/' == $location{0}) { + $url->path = Net_URL::resolvePath($location); + } elseif('/' == substr($url->path, -1)) { + $url->path = Net_URL::resolvePath($url->path . $location); + } else { + $dirname = (DIRECTORY_SEPARATOR == dirname($url->path)? '/': dirname($url->path)); + $url->path = Net_URL::resolvePath($dirname . '/' . $location); + } + $url->querystring = array(); + $url->anchor = ''; + return $url->getUrl(); + } + } +} +?> diff --git a/thirdparty/pear/HTTP/Client/CookieManager.php b/thirdparty/pear/HTTP/Client/CookieManager.php new file mode 100644 index 0000000..3b9abb2 --- /dev/null +++ b/thirdparty/pear/HTTP/Client/CookieManager.php @@ -0,0 +1,183 @@ + | +// +----------------------------------------------------------------------+ +// +// $Id: CookieManager.php,v 1.3 2004/04/10 10:04:52 avb Exp $ + +/** + * This class is used to store cookies and pass them between HTTP requests. + * + * @package HTTP_Client + * @author Alexey Borzov + * @version $Revision: 1.3 $ + */ +class HTTP_Client_CookieManager +{ + /** + * An array containing cookie values + * @var array + */ + var $_cookies = array(); + + + /** + * Constructor + * + * @access public + */ + function HTTP_Client_CookieManager() + { + // abstract + } + + + /** + * Adds cookies to the request + * + * @access public + * @param object An HTTP_Request object + */ + function passCookies(&$request) + { + if (!empty($this->_cookies)) { + $url =& $request->_url; + // We do not check cookie's "expires" field, as we do not store deleted + // cookies in the array and our client does not work long enough for other + // cookies to expire. If some kind of persistence is added to this object, + // then expiration should be checked upon loading and session cookies should + // be cleared on saving. + $cookies = array(); + foreach ($this->_cookies as $cookie) { + if ($this->_domainMatch($url->host, $cookie['domain']) && (0 === strpos($url->path, $cookie['path'])) + && (empty($cookie['secure']) || $url->protocol == 'https')) { + $cookies[$cookie['name']][strlen($cookie['path'])] = $cookie['value']; + } + } + // cookies with longer paths go first + foreach ($cookies as $name => $values) { + krsort($values); + foreach ($values as $value) { + $request->addCookie($name, $value); + } + } + } + return true; + } + + + /** + * Explicitly adds cookie to the list + * + * @param array An array representing cookie, this function expects all of the array's + * fields to be set + * @access public + */ + function addCookie($cookie) + { + $hash = $this->_makeHash($cookie['name'], $cookie['domain'], $cookie['path']); + $this->_cookies[$hash] = $cookie; + } + + + /** + * Updates cookie list from HTTP server response + * + * @access public + * @param object An HTTP_Request object with sendRequest() already done + */ + function updateCookies(&$request) + { + if (false !== ($cookies = $request->getResponseCookies())) { + $url =& $request->_url; + foreach ($cookies as $cookie) { + // use the current domain by default + if (!isset($cookie['domain'])) { + $cookie['domain'] = $url->host; + } + // use the path to the current page by default + if (!isset($cookie['path'])) { + $cookie['path'] = DIRECTORY_SEPARATOR == dirname($url->path)? '/': dirname($url->path); + } + // check if the domains match + if ($this->_domainMatch($url->host, $cookie['domain'])) { + $hash = $this->_makeHash($cookie['name'], $cookie['domain'], $cookie['path']); + // if value is empty or the time is in the past the cookie is deleted, else added + if (strlen($cookie['value']) + && (!isset($cookie['expires']) || (strtotime($cookie['expires']) > time()))) { + $this->_cookies[$hash] = $cookie; + } elseif (isset($this->_cookies[$hash])) { + unset($this->_cookies[$hash]); + } + } + } + } + } + + + /** + * Generates a key for the $_cookies array. + * + * The cookies is uniquely identified by its name, domain and path. + * Thus we cannot make f.e. an associative array with name as a key, we should + * generate a key from these 3 values. + * + * @access private + * @param string Cookie name + * @param string Cookie domain + * @param string Cookie path + * @return string a key + */ + function _makeHash($name, $domain, $path) + { + return md5($name . "\r\n" . $domain . "\r\n" . $path); + } + + + /** + * Checks whether a cookie domain matches a request host. + * + * Cookie domain can begin with a dot, it also must contain at least + * two dots. + * + * @access private + * @param string request host + * @param string cookie domain + * @return bool match success + */ + function _domainMatch($requestHost, $cookieDomain) + { + if ('.' != $cookieDomain{0}) { + return $requestHost == $cookieDomain; + } elseif (substr_count($cookieDomain, '.') < 2) { + return false; + } else { + return substr('.'. $requestHost, - strlen($cookieDomain)) == $cookieDomain; + } + } + + + /** + * Clears the $_cookies array + * + * @access public + */ + function reset() + { + $this->_cookies = array(); + } +} +?> diff --git a/thirdparty/pear/HTTP/Download.php b/thirdparty/pear/HTTP/Download.php new file mode 100644 index 0000000..51d496a --- /dev/null +++ b/thirdparty/pear/HTTP/Download.php @@ -0,0 +1,1031 @@ + + * @copyright 2003-2005 Michael Wallner + * @license BSD, revised + * @version CVS: $Id: Download.php,v 1.75 2005/11/13 19:18:53 mike Exp $ + * @link http://pear.php.net/package/HTTP_Download + */ + +// {{{ includes +/** + * Requires PEAR + */ +require_once 'PEAR.php'; + +/** + * Requires HTTP_Header + */ +require_once 'HTTP/Header.php'; +// }}} + +// {{{ constants +/**#@+ Use with HTTP_Download::setContentDisposition() **/ +/** + * Send data as attachment + */ +define('HTTP_DOWNLOAD_ATTACHMENT', 'attachment'); +/** + * Send data inline + */ +define('HTTP_DOWNLOAD_INLINE', 'inline'); +/**#@-**/ + +/**#@+ Use with HTTP_Download::sendArchive() **/ +/** + * Send as uncompressed tar archive + */ +define('HTTP_DOWNLOAD_TAR', 'TAR'); +/** + * Send as gzipped tar archive + */ +define('HTTP_DOWNLOAD_TGZ', 'TGZ'); +/** + * Send as bzip2 compressed tar archive + */ +define('HTTP_DOWNLOAD_BZ2', 'BZ2'); +/** + * Send as zip archive + */ +define('HTTP_DOWNLOAD_ZIP', 'ZIP'); +/**#@-**/ + +/**#@+ + * Error constants + */ +define('HTTP_DOWNLOAD_E_HEADERS_SENT', -1); +define('HTTP_DOWNLOAD_E_NO_EXT_ZLIB', -2); +define('HTTP_DOWNLOAD_E_NO_EXT_MMAGIC', -3); +define('HTTP_DOWNLOAD_E_INVALID_FILE', -4); +define('HTTP_DOWNLOAD_E_INVALID_PARAM', -5); +define('HTTP_DOWNLOAD_E_INVALID_RESOURCE', -6); +define('HTTP_DOWNLOAD_E_INVALID_REQUEST', -7); +define('HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE', -8); +define('HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE', -9); +/**#@-**/ +// }}} + +/** + * Send HTTP Downloads/Responses. + * + * With this package you can handle (hidden) downloads. + * It supports partial downloads, resuming and sending + * raw data ie. from database BLOBs. + * + * ATTENTION: + * You shouldn't use this package together with ob_gzhandler or + * zlib.output_compression enabled in your php.ini, especially + * if you want to send already gzipped data! + * + * @access public + * @version $Revision: 1.75 $ + */ +class HTTP_Download +{ + // {{{ protected member variables + /** + * Path to file for download + * + * @see HTTP_Download::setFile() + * @access protected + * @var string + */ + var $file = ''; + + /** + * Data for download + * + * @see HTTP_Download::setData() + * @access protected + * @var string + */ + var $data = null; + + /** + * Resource handle for download + * + * @see HTTP_Download::setResource() + * @access protected + * @var int + */ + var $handle = null; + + /** + * Whether to gzip the download + * + * @access protected + * @var bool + */ + var $gzip = false; + + /** + * Whether to allow caching of the download on the clients side + * + * @access protected + * @var bool + */ + var $cache = true; + + /** + * Size of download + * + * @access protected + * @var int + */ + var $size = 0; + + /** + * Last modified + * + * @access protected + * @var int + */ + var $lastModified = 0; + + /** + * HTTP headers + * + * @access protected + * @var array + */ + var $headers = array( + 'Content-Type' => 'application/x-octetstream', + 'Pragma' => 'cache', + 'Cache-Control' => 'public, must-revalidate, max-age=0', + 'Accept-Ranges' => 'bytes', + 'X-Sent-By' => 'PEAR::HTTP::Download' + ); + + /** + * HTTP_Header + * + * @access protected + * @var object + */ + var $HTTP = null; + + /** + * ETag + * + * @access protected + * @var string + */ + var $etag = ''; + + /** + * Buffer Size + * + * @access protected + * @var int + */ + var $bufferSize = 2097152; + + /** + * Throttle Delay + * + * @access protected + * @var float + */ + var $throttleDelay = 0; + + /** + * Sent Bytes + * + * @access public + * @var int + */ + var $sentBytes = 0; + // }}} + + // {{{ constructor + /** + * Constructor + * + * Set supplied parameters. + * + * @access public + * @param array $params associative array of parameters + * + * one of: + * o 'file' => path to file for download + * o 'data' => raw data for download + * o 'resource' => resource handle for download + *
+ * and any of: + * o 'cache' => whether to allow cs caching + * o 'gzip' => whether to gzip the download + * o 'lastmodified' => unix timestamp + * o 'contenttype' => content type of download + * o 'contentdisposition' => content disposition + * o 'buffersize' => amount of bytes to buffer + * o 'throttledelay' => amount of secs to sleep + * o 'cachecontrol' => cache privacy and validity + * + *
+ * 'Content-Disposition' is not HTTP compliant, but most browsers + * follow this header, so it was borrowed from MIME standard. + * + * It looks like this:
+ * "Content-Disposition: attachment; filename=example.tgz". + * + * @see HTTP_Download::setContentDisposition() + */ + function HTTP_Download($params = array()) + { + $this->HTTP = &new HTTP_Header; + $this->setParams($params); + } + // }}} + + // {{{ public methods + /** + * Set parameters + * + * Set supplied parameters through its accessor methods. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $params associative array of parameters + * + * @see HTTP_Download::HTTP_Download() + */ + function setParams($params) + { + foreach((array) $params as $param => $value){ + $method = 'set'. $param; + + if (!method_exists($this, $method)) { + return PEAR::raiseError( + "Method '$method' doesn't exist.", + HTTP_DOWNLOAD_E_INVALID_PARAM + ); + } + + $e = call_user_func_array(array(&$this, $method), (array) $value); + + if (PEAR::isError($e)) { + return $e; + } + } + return true; + } + + /** + * Set path to file for download + * + * The Last-Modified header will be set to files filemtime(), actually. + * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_FILE) if file doesn't exist. + * Sends HTTP 404 status if $send_404 is set to true. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $file path to file for download + * @param bool $send_404 whether to send HTTP/404 if + * the file wasn't found + */ + function setFile($file, $send_404 = true) + { + $file = realpath($file); + if (!is_file($file)) { + if ($send_404) { + $this->HTTP->sendStatusCode(404); + } + return PEAR::raiseError( + "File '$file' not found.", + HTTP_DOWNLOAD_E_INVALID_FILE + ); + } + $this->setLastModified(filemtime($file)); + $this->file = $file; + $this->size = filesize($file); + return true; + } + + /** + * Set data for download + * + * Set $data to null if you want to unset this. + * + * @access public + * @return void + * @param $data raw data to send + */ + function setData($data = null) + { + $this->data = $data; + $this->size = strlen($data); + } + + /** + * Set resource for download + * + * The resource handle supplied will be closed after sending the download. + * Returns a PEAR_Error (HTTP_DOWNLOAD_E_INVALID_RESOURCE) if $handle + * is no valid resource. Set $handle to null if you want to unset this. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param int $handle resource handle + */ + function setResource($handle = null) + { + if (!isset($handle)) { + $this->handle = null; + $this->size = 0; + return true; + } + + if (is_resource($handle)) { + $this->handle = $handle; + $filestats = fstat($handle); + $this->size = $filestats['size']; + return true; + } + + return PEAR::raiseError( + "Handle '$handle' is no valid resource.", + HTTP_DOWNLOAD_E_INVALID_RESOURCE + ); + } + + /** + * Whether to gzip the download + * + * Returns a PEAR_Error (HTTP_DOWNLOAD_E_NO_EXT_ZLIB) + * if ext/zlib is not available/loadable. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param bool $gzip whether to gzip the download + */ + function setGzip($gzip = false) + { + if ($gzip && !PEAR::loadExtension('zlib')){ + return PEAR::raiseError( + 'GZIP compression (ext/zlib) not available.', + HTTP_DOWNLOAD_E_NO_EXT_ZLIB + ); + } + $this->gzip = (bool) $gzip; + return true; + } + + /** + * Whether to allow caching + * + * If set to true (default) we'll send some headers that are commonly + * used for caching purposes like ETag, Cache-Control and Last-Modified. + * + * If caching is disabled, we'll send the download no matter if it + * would actually be cached at the client side. + * + * @access public + * @return void + * @param bool $cache whether to allow caching + */ + function setCache($cache = true) + { + $this->cache = (bool) $cache; + } + + /** + * Whether to allow proxies to cache + * + * If set to 'private' proxies shouldn't cache the response. + * This setting defaults to 'public' and affects only cached responses. + * + * @access public + * @return bool + * @param string $cache private or public + * @param int $maxage maximum age of the client cache entry + */ + function setCacheControl($cache = 'public', $maxage = 0) + { + switch ($cache = strToLower($cache)) + { + case 'private': + case 'public': + $this->headers['Cache-Control'] = + $cache .', must-revalidate, max-age='. abs($maxage); + return true; + break; + } + return false; + } + + /** + * Set ETag + * + * Sets a user-defined ETag for cache-validation. The ETag is usually + * generated by HTTP_Download through its payload information. + * + * @access public + * @return void + * @param string $etag Entity tag used for strong cache validation. + */ + function setETag($etag = null) + { + $this->etag = (string) $etag; + } + + /** + * Set Size of Buffer + * + * The amount of bytes specified as buffer size is the maximum amount + * of data read at once from resources or files. The default size is 2M + * (2097152 bytes). Be aware that if you enable gzip compression and + * you set a very low buffer size that the actual file size may grow + * due to added gzip headers for each sent chunk of the specified size. + * + * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_PARAM) if $size is not + * greater than 0 bytes. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param int $bytes Amount of bytes to use as buffer. + */ + function setBufferSize($bytes = 2097152) + { + if (0 >= $bytes) { + return PEAR::raiseError( + 'Buffer size must be greater than 0 bytes ('. $bytes .' given)', + HTTP_DOWNLOAD_E_INVALID_PARAM); + } + $this->bufferSize = abs($bytes); + return true; + } + + /** + * Set Throttle Delay + * + * Set the amount of seconds to sleep after each chunck that has been + * sent. One can implement some sort of throttle through adjusting the + * buffer size and the throttle delay. With the following settings + * HTTP_Download will sleep a second after each 25 K of data sent. + * + * + * Array( + * 'throttledelay' => 1, + * 'buffersize' => 1024 * 25, + * ) + * + * + * Just be aware that if gzipp'ing is enabled, decreasing the chunk size + * too much leads to proportionally increased network traffic due to added + * gzip header and bottom bytes around each chunk. + * + * @access public + * @return void + * @param float $seconds Amount of seconds to sleep after each + * chunk that has been sent. + */ + function setThrottleDelay($seconds = 0) + { + $this->throttleDelay = abs($seconds) * 1000; + } + + /** + * Set "Last-Modified" + * + * This is usually determined by filemtime() in HTTP_Download::setFile() + * If you set raw data for download with HTTP_Download::setData() and you + * want do send an appropiate "Last-Modified" header, you should call this + * method. + * + * @access public + * @return void + * @param int unix timestamp + */ + function setLastModified($last_modified) + { + $this->lastModified = $this->headers['Last-Modified'] = (int) $last_modified; + } + + /** + * Set Content-Disposition header + * + * @see HTTP_Download::HTTP_Download + * + * @access public + * @return void + * @param string $disposition whether to send the download + * inline or as attachment + * @param string $file_name the filename to display in + * the browser's download window + * + * Example: + * + * $HTTP_Download->setContentDisposition( + * HTTP_DOWNLOAD_ATTACHMENT, + * 'download.tgz' + * ); + * + */ + function setContentDisposition( $disposition = HTTP_DOWNLOAD_ATTACHMENT, + $file_name = null) + { + $cd = $disposition; + if (isset($file_name)) { + $cd .= '; filename="' . $file_name . '"'; + } elseif ($this->file) { + $cd .= '; filename="' . basename($this->file) . '"'; + } + $this->headers['Content-Disposition'] = $cd; + } + + /** + * Set content type of the download + * + * Default content type of the download will be 'application/x-octetstream'. + * Returns PEAR_Error (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) if + * $content_type doesn't seem to be valid. + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $content_type content type of file for download + */ + function setContentType($content_type = 'application/x-octetstream') + { + if (!preg_match('/^[a-z]+\w*\/[a-z]+[\w.;= -]*$/', $content_type)) { + return PEAR::raiseError( + "Invalid content type '$content_type' supplied.", + HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE + ); + } + $this->headers['Content-Type'] = $content_type; + return true; + } + + /** + * Guess content type of file + * + * First we try to use PEAR::MIME_Type, if installed, to detect the content + * type, else we check if ext/mime_magic is loaded and properly configured. + * + * Returns PEAR_Error if: + * o if PEAR::MIME_Type failed to detect a proper content type + * (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) + * o ext/magic.mime is not installed, or not properly configured + * (HTTP_DOWNLOAD_E_NO_EXT_MMAGIC) + * o mime_content_type() couldn't guess content type or returned + * a content type considered to be bogus by setContentType() + * (HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE) + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + */ + function guessContentType() + { + if (class_exists('MIME_Type') || @include_once 'MIME/Type.php') { + if (PEAR::isError($mime_type = MIME_Type::autoDetect($this->file))) { + return PEAR::raiseError($mime_type->getMessage(), + HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE); + } + return $this->setContentType($mime_type); + } + if (!function_exists('mime_content_type')) { + return PEAR::raiseError( + 'This feature requires ext/mime_magic!', + HTTP_DOWNLOAD_E_NO_EXT_MMAGIC + ); + } + if (!is_file(ini_get('mime_magic.magicfile'))) { + return PEAR::raiseError( + 'ext/mime_magic is loaded but not properly configured!', + HTTP_DOWNLOAD_E_NO_EXT_MMAGIC + ); + } + if (!$content_type = @mime_content_type($this->file)) { + return PEAR::raiseError( + 'Couldn\'t guess content type with mime_content_type().', + HTTP_DOWNLOAD_E_INVALID_CONTENT_TYPE + ); + } + return $this->setContentType($content_type); + } + + /** + * Send + * + * Returns PEAR_Error if: + * o HTTP headers were already sent (HTTP_DOWNLOAD_E_HEADERS_SENT) + * o HTTP Range was invalid (HTTP_DOWNLOAD_E_INVALID_REQUEST) + * + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param bool $autoSetContentDisposition Whether to set the + * Content-Disposition header if it isn't already. + */ + function send($autoSetContentDisposition = true) + { + if (headers_sent()) { + return PEAR::raiseError( + 'Headers already sent.', + HTTP_DOWNLOAD_E_HEADERS_SENT + ); + } + + if (!ini_get('safe_mode')) { + @set_time_limit(0); + } + + if ($autoSetContentDisposition && + !isset($this->headers['Content-Disposition'])) { + $this->setContentDisposition(); + } + + if ($this->cache) { + $this->headers['ETag'] = $this->generateETag(); + if ($this->isCached()) { + $this->HTTP->sendStatusCode(304); + $this->sendHeaders(); + return true; + } + } else { + unset($this->headers['Last-Modified']); + } + + while (@ob_end_clean()); + + if ($this->gzip) { + @ob_start('ob_gzhandler'); + } else { + ob_start(); + } + + $this->sentBytes = 0; + + if ($this->isRangeRequest()) { + $this->HTTP->sendStatusCode(206); + $chunks = $this->getChunks(); + } else { + $this->HTTP->sendStatusCode(200); + $chunks = array(array(0, $this->size)); + if (!$this->gzip && count(ob_list_handlers()) < 2) { + $this->headers['Content-Length'] = $this->size; + } + } + + if (PEAR::isError($e = $this->sendChunks($chunks))) { + ob_end_clean(); + $this->HTTP->sendStatusCode(416); + return $e; + } + + ob_end_flush(); + flush(); + return true; + } + + /** + * Static send + * + * @see HTTP_Download::HTTP_Download() + * @see HTTP_Download::send() + * + * @static + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $params associative array of parameters + * @param bool $guess whether HTTP_Download::guessContentType() + * should be called + */ + function staticSend($params, $guess = false) + { + $d = &new HTTP_Download(); + $e = $d->setParams($params); + if (PEAR::isError($e)) { + return $e; + } + if ($guess) { + $e = $d->guessContentType(); + if (PEAR::isError($e)) { + return $e; + } + } + return $d->send(); + } + + /** + * Send a bunch of files or directories as an archive + * + * Example: + * + * require_once 'HTTP/Download.php'; + * HTTP_Download::sendArchive( + * 'myArchive.tgz', + * '/var/ftp/pub/mike', + * HTTP_DOWNLOAD_TGZ, + * '', + * '/var/ftp/pub' + * ); + * + * + * @see Archive_Tar::createModify() + * @deprecated use HTTP_Download_Archive::send() + * @static + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $name name the sent archive should have + * @param mixed $files files/directories + * @param string $type archive type + * @param string $add_path path that should be prepended to the files + * @param string $strip_path path that should be stripped from the files + */ + function sendArchive( $name, + $files, + $type = HTTP_DOWNLOAD_TGZ, + $add_path = '', + $strip_path = '') + { + require_once 'HTTP/Download/Archive.php'; + return HTTP_Download_Archive::send($name, $files, $type, + $add_path, $strip_path); + } + // }}} + + // {{{ protected methods + /** + * Generate ETag + * + * @access protected + * @return string + */ + function generateETag() + { + if (!$this->etag) { + if ($this->data) { + $md5 = md5($this->data); + } else { + $fst = is_resource($this->handle) ? + fstat($this->handle) : stat($this->file); + $md5 = md5($fst['mtime'] .'='. $fst['ino'] .'='. $fst['size']); + } + $this->etag = '"' . $md5 . '-' . crc32($md5) . '"'; + } + return $this->etag; + } + + /** + * Send multiple chunks + * + * @access protected + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $chunks + */ + function sendChunks($chunks) + { + if (count($chunks) == 1) { + return $this->sendChunk(array_shift($chunks)); + } + + $bound = uniqid('HTTP_DOWNLOAD-', true); + $cType = $this->headers['Content-Type']; + $this->headers['Content-Type'] = + 'multipart/byteranges; boundary=' . $bound; + $this->sendHeaders(); + foreach ($chunks as $chunk){ + if (PEAR::isError($e = $this->sendChunk($chunk, $cType, $bound))) { + return $e; + } + } + #echo "\r\n--$bound--\r\n"; + return true; + } + + /** + * Send chunk of data + * + * @access protected + * @return mixed Returns true on success or PEAR_Error on failure. + * @param array $chunk start and end offset of the chunk to send + * @param string $cType actual content type + * @param string $bound boundary for multipart/byteranges + */ + function sendChunk($chunk, $cType = null, $bound = null) + { + list($offset, $lastbyte) = $chunk; + $length = ($lastbyte - $offset) + 1; + + if ($length < 1) { + return PEAR::raiseError( + "Error processing range request: $offset-$lastbyte/$length", + HTTP_DOWNLOAD_E_INVALID_REQUEST + ); + } + + $range = $offset . '-' . $lastbyte . '/' . $this->size; + + if (isset($cType, $bound)) { + echo "\r\n--$bound\r\n", + "Content-Type: $cType\r\n", + "Content-Range: bytes $range\r\n\r\n"; + } else { + if ($this->isRangeRequest()) { + $this->headers['Content-Range'] = 'bytes '. $range; + } + $this->sendHeaders(); + } + + if ($this->data) { + while (($length -= $this->bufferSize) > 0) { + $this->flush(substr($this->data, $offset, $this->bufferSize)); + $this->throttleDelay and $this->sleep(); + $offset += $this->bufferSize; + } + if ($length) { + $this->flush(substr($this->data, $offset, $this->bufferSize + $length)); + } + } else { + if (!is_resource($this->handle)) { + $this->handle = fopen($this->file, 'rb'); + } + fseek($this->handle, $offset); + while (($length -= $this->bufferSize) > 0) { + $this->flush(fread($this->handle, $this->bufferSize)); + $this->throttleDelay and $this->sleep(); + } + if ($length) { + $this->flush(fread($this->handle, $this->bufferSize + $length)); + } + } + return true; + } + + /** + * Get chunks to send + * + * @access protected + * @return array + */ + function getChunks() + { + $parts = array(); + foreach (explode(',', $this->getRanges()) as $chunk){ + list($o, $e) = explode('-', $chunk); + if ($e >= $this->size || (empty($e) && $e !== 0 && $e !== '0')) { + $e = $this->size - 1; + } + if (empty($o) && $o !== 0 && $o !== '0') { + $o = $this->size - $e; + $e = $this->size - 1; + } + $parts[] = array($o, $e); + } + return $parts; + } + + /** + * Check if range is requested + * + * @access protected + * @return bool + */ + function isRangeRequest() + { + if (!isset($_SERVER['HTTP_RANGE'])) { + return false; + } + return $this->isValidRange(); + } + + /** + * Get range request + * + * @access protected + * @return array + */ + function getRanges() + { + return preg_match('/^bytes=((\d*-\d*,? ?)+)$/', + @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : array(); + } + + /** + * Check if entity is cached + * + * @access protected + * @return bool + */ + function isCached() + { + return ( + (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && + $this->lastModified == strtotime(current($a = explode( + ';', $_SERVER['HTTP_IF_MODIFIED_SINCE'])))) || + (isset($_SERVER['HTTP_IF_NONE_MATCH']) && + $this->compareAsterisk('HTTP_IF_NONE_MATCH', $this->etag)) + ); + } + + /** + * Check if entity hasn't changed + * + * @access protected + * @return bool + */ + function isValidRange() + { + if (isset($_SERVER['HTTP_IF_MATCH']) && + !$this->compareAsterisk('HTTP_IF_MATCH', $this->etag)) { + return false; + } + if (isset($_SERVER['HTTP_IF_RANGE']) && + $_SERVER['HTTP_IF_RANGE'] !== $this->etag && + strtotime($_SERVER['HTTP_IF_RANGE']) !== $this->lastModified) { + return false; + } + if (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) { + $lm = array_shift(explode(';', $_SERVER['HTTP_IF_UNMODIFIED_SINCE'])); + if (strtotime($lm) !== $this->lastModified) { + return false; + } + } + if (isset($_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])) { + $lm = array_shift(explode(';', $_SERVER['HTTP_UNLESS_MODIFIED_SINCE'])); + if (strtotime($lm) !== $this->lastModified) { + return false; + } + } + return true; + } + + /** + * Compare against an asterisk or check for equality + * + * @access protected + * @return bool + * @param string key for the $_SERVER array + * @param string string to compare + */ + function compareAsterisk($svar, $compare) + { + foreach (array_map('trim', explode(',', $_SERVER[$svar])) as $request) { + if ($request === '*' || $request === $compare) { + return true; + } + } + return false; + } + + /** + * Send HTTP headers + * + * @access protected + * @return void + */ + function sendHeaders() + { + foreach ($this->headers as $header => $value) { + $this->HTTP->setHeader($header, $value); + } + $this->HTTP->sendHeaders(); + /* NSAPI won't output anything if we did this */ + if (strncasecmp(PHP_SAPI, 'nsapi', 5)) { + ob_flush(); + flush(); + } + } + + /** + * Flush + * + * @access protected + * @return void + * @param string $data + */ + function flush($data = '') + { + if ($dlen = strlen($data)) { + $this->sentBytes += $dlen; + echo $data; + } + ob_flush(); + flush(); + } + + /** + * Sleep + * + * @access protected + * @return void + */ + function sleep() + { + if (OS_WINDOWS) { + com_message_pump($this->throttleDelay); + } else { + usleep($this->throttleDelay * 1000); + } + } + // }}} +} +?> diff --git a/thirdparty/pear/HTTP/Download/Archive.php b/thirdparty/pear/HTTP/Download/Archive.php new file mode 100644 index 0000000..2aa5b8b --- /dev/null +++ b/thirdparty/pear/HTTP/Download/Archive.php @@ -0,0 +1,122 @@ + + * @copyright 2003-2005 Michael Wallner + * @license BSD, revisewd + * @version CVS: $Id: Archive.php,v 1.4 2005/11/13 19:18:55 mike Exp $ + * @link http://pear.php.net/package/HTTP_Download + */ + +/** + * Requires HTTP_Download + */ +require_once 'HTTP/Download.php'; + +/** + * Requires System + */ +require_once 'System.php'; + +/** + * HTTP_Download_Archive + * + * Helper class for sending Archives. + * + * @access public + * @version $Revision: 1.4 $ + */ +class HTTP_Download_Archive +{ + /** + * Send a bunch of files or directories as an archive + * + * Example: + * + * require_once 'HTTP/Download/Archive.php'; + * HTTP_Download_Archive::send( + * 'myArchive.tgz', + * '/var/ftp/pub/mike', + * HTTP_DOWNLOAD_BZ2, + * '', + * '/var/ftp/pub' + * ); + * + * + * @see Archive_Tar::createModify() + * @static + * @access public + * @return mixed Returns true on success or PEAR_Error on failure. + * @param string $name name the sent archive should have + * @param mixed $files files/directories + * @param string $type archive type + * @param string $add_path path that should be prepended to the files + * @param string $strip_path path that should be stripped from the files + */ + function send($name, $files, $type = HTTP_DOWNLOAD_TGZ, $add_path = '', $strip_path = '') + { + $tmp = System::mktemp(); + + switch ($type = strToUpper($type)) + { + case HTTP_DOWNLOAD_TAR: + include_once 'Archive/Tar.php'; + $arc = &new Archive_Tar($tmp); + $content_type = 'x-tar'; + break; + + case HTTP_DOWNLOAD_TGZ: + include_once 'Archive/Tar.php'; + $arc = &new Archive_Tar($tmp, 'gz'); + $content_type = 'x-gzip'; + break; + + case HTTP_DOWNLOAD_BZ2: + include_once 'Archive/Tar.php'; + $arc = &new Archive_Tar($tmp, 'bz2'); + $content_type = 'x-bzip2'; + break; + + case HTTP_DOWNLOAD_ZIP: + include_once 'Archive/Zip.php'; + $arc = &new Archive_Zip($tmp); + $content_type = 'x-zip'; + break; + + default: + return PEAR::raiseError( + 'Archive type not supported: ' . $type, + HTTP_DOWNLOAD_E_INVALID_ARCHIVE_TYPE + ); + } + + if ($type == HTTP_DOWNLOAD_ZIP) { + $options = array( 'add_path' => $add_path, + 'remove_path' => $strip_path); + if (!$arc->create($files, $options)) { + return PEAR::raiseError('Archive creation failed.'); + } + } else { + if (!$e = $arc->createModify($files, $add_path, $strip_path)) { + return PEAR::raiseError('Archive creation failed.'); + } + if (PEAR::isError($e)) { + return $e; + } + } + unset($arc); + + $dl = &new HTTP_Download(array('file' => $tmp)); + $dl->setContentType('application/' . $content_type); + $dl->setContentDisposition(HTTP_DOWNLOAD_ATTACHMENT, $name); + return $dl->send(); + } +} +?> diff --git a/thirdparty/pear/HTTP/Download/PgLOB.php b/thirdparty/pear/HTTP/Download/PgLOB.php new file mode 100644 index 0000000..19de2bc --- /dev/null +++ b/thirdparty/pear/HTTP/Download/PgLOB.php @@ -0,0 +1,177 @@ + + * @copyright 2003-2005 Michael Wallner + * @license BSD, revised + * @version CVS: $Id: PgLOB.php,v 1.14 2005/11/13 19:18:55 mike Exp $ + * @link http://pear.php.net/package/HTTP_Download + */ + +$GLOBALS['_HTTP_Download_PgLOB_Connection'] = null; +stream_register_wrapper('pglob', 'HTTP_Download_PgLOB'); + +/** + * PgSQL large object stream interface for HTTP_Download + * + * Usage: + * + * require_once 'HTTP/Download.php'; + * require_once 'HTTP/Download/PgLOB.php'; + * $db = &DB::connect('pgsql://user:pass@host/db'); + * // or $db = pg_connect(...); + * $lo = HTTP_Download_PgLOB::open($db, 12345); + * $dl = &new HTTP_Download; + * $dl->setResource($lo); + * $dl->send() + * + * + * @access public + * @version $Revision: 1.14 $ + */ +class HTTP_Download_PgLOB +{ + /** + * Set Connection + * + * @static + * @access public + * @return bool + * @param mixed $conn + */ + function setConnection($conn) + { + if (is_a($conn, 'DB_Common')) { + $conn = $conn->dbh; + } elseif ( is_a($conn, 'MDB_Common') || + is_a($conn, 'MDB2_Driver_Common')) { + $conn = $conn->connection; + } + if ($isResource = is_resource($conn)) { + $GLOBALS['_HTTP_Download_PgLOB_Connection'] = $conn; + } + return $isResource; + } + + /** + * Get Connection + * + * @static + * @access public + * @return resource + */ + function getConnection() + { + if (is_resource($GLOBALS['_HTTP_Download_PgLOB_Connection'])) { + return $GLOBALS['_HTTP_Download_PgLOB_Connection']; + } + return null; + } + + /** + * Open + * + * @static + * @access public + * @return resource + * @param mixed $conn + * @param int $loid + * @param string $mode + */ + function open($conn, $loid, $mode = 'rb') + { + HTTP_Download_PgLOB::setConnection($conn); + return fopen('pglob:///'. $loid, $mode); + } + + /**#@+ + * Stream Interface Implementation + * @internal + */ + var $ID = 0; + var $size = 0; + var $conn = null; + var $handle = null; + + function stream_open($path, $mode) + { + if (!$this->conn = HTTP_Download_PgLOB::getConnection()) { + return false; + } + if (!preg_match('/(\d+)/', $path, $matches)) { + return false; + } + $this->ID = $matches[1]; + + if (!pg_query($this->conn, 'BEGIN')) { + return false; + } + + $this->handle = pg_lo_open($this->conn, $this->ID, $mode); + if (!is_resource($this->handle)) { + return false; + } + + // fetch size of lob + pg_lo_seek($this->handle, 0, PGSQL_SEEK_END); + $this->size = (int) pg_lo_tell($this->handle); + pg_lo_seek($this->handle, 0, PGSQL_SEEK_SET); + + return true; + } + + function stream_read($length) + { + return pg_lo_read($this->handle, $length); + } + + function stream_seek($offset, $whence = SEEK_SET) + { + return pg_lo_seek($this->handle, $offset, $whence); + } + + function stream_tell() + { + return pg_lo_tell($this->handle); + } + + function stream_eof() + { + return pg_lo_tell($this->handle) >= $this->size; + } + + function stream_flush() + { + return true; + } + + function stream_stat() + { + return array('size' => $this->size, 'ino' => $this->ID); + } + + function stream_write($data) + { + return pg_lo_write($this->handle, $data); + } + + function stream_close() + { + if (pg_lo_close($this->handle)) { + return pg_query($this->conn, 'COMMIT'); + } else { + pg_query($this->conn ,'ROLLBACK'); + return false; + } + } + /**#@-*/ +} + +?> diff --git a/thirdparty/pear/HTTP/Header.php b/thirdparty/pear/HTTP/Header.php new file mode 100644 index 0000000..fd6c497 --- /dev/null +++ b/thirdparty/pear/HTTP/Header.php @@ -0,0 +1,531 @@ + + * @author Davey Shafik + * @author Michael Wallner + * @copyright 2003-2005 The Authors + * @license BSD, revised + * @version CVS: $Id: Header.php,v 1.32 2005/11/08 19:06:10 mike Exp $ + * @link http://pear.php.net/package/HTTP_Header + */ + +/** + * Requires HTTP + */ +require_once 'HTTP.php'; + +/**#@+ + * Information Codes + */ +define('HTTP_HEADER_STATUS_100', '100 Continue'); +define('HTTP_HEADER_STATUS_101', '101 Switching Protocols'); +define('HTTP_HEADER_STATUS_102', '102 Processing'); +define('HTTP_HEADER_STATUS_INFORMATIONAL',1); +/**#@-*/ + +/**#+ + * Success Codes + */ +define('HTTP_HEADER_STATUS_200', '200 OK'); +define('HTTP_HEADER_STATUS_201', '201 Created'); +define('HTTP_HEADER_STATUS_202', '202 Accepted'); +define('HTTP_HEADER_STATUS_203', '203 Non-Authoritative Information'); +define('HTTP_HEADER_STATUS_204', '204 No Content'); +define('HTTP_HEADER_STATUS_205', '205 Reset Content'); +define('HTTP_HEADER_STATUS_206', '206 Partial Content'); +define('HTTP_HEADER_STATUS_207', '207 Multi-Status'); +define('HTTP_HEADER_STATUS_SUCCESSFUL',2); +/**#@-*/ + +/**#@+ + * Redirection Codes + */ +define('HTTP_HEADER_STATUS_300', '300 Multiple Choices'); +define('HTTP_HEADER_STATUS_301', '301 Moved Permanently'); +define('HTTP_HEADER_STATUS_302', '302 Found'); +define('HTTP_HEADER_STATUS_303', '303 See Other'); +define('HTTP_HEADER_STATUS_304', '304 Not Modified'); +define('HTTP_HEADER_STATUS_305', '305 Use Proxy'); +define('HTTP_HEADER_STATUS_306', '306 (Unused)'); +define('HTTP_HEADER_STATUS_307', '307 Temporary Redirect'); +define('HTTP_HEADER_STATUS_REDIRECT',3); +/**#@-*/ + +/**#@+ + * Error Codes + */ +define('HTTP_HEADER_STATUS_400', '400 Bad Request'); +define('HTTP_HEADER_STATUS_401', '401 Unauthorized'); +define('HTTP_HEADER_STATUS_402', '402 Payment Granted'); +define('HTTP_HEADER_STATUS_403', '403 Forbidden'); +define('HTTP_HEADER_STATUS_404', '404 File Not Found'); +define('HTTP_HEADER_STATUS_405', '405 Method Not Allowed'); +define('HTTP_HEADER_STATUS_406', '406 Not Acceptable'); +define('HTTP_HEADER_STATUS_407', '407 Proxy Authentication Required'); +define('HTTP_HEADER_STATUS_408', '408 Request Time-out'); +define('HTTP_HEADER_STATUS_409', '409 Conflict'); +define('HTTP_HEADER_STATUS_410', '410 Gone'); +define('HTTP_HEADER_STATUS_411', '411 Length Required'); +define('HTTP_HEADER_STATUS_412', '412 Precondition Failed'); +define('HTTP_HEADER_STATUS_413', '413 Request Entity Too Large'); +define('HTTP_HEADER_STATUS_414', '414 Request-URI Too Large'); +define('HTTP_HEADER_STATUS_415', '415 Unsupported Media Type'); +define('HTTP_HEADER_STATUS_416', '416 Requested range not satisfiable'); +define('HTTP_HEADER_STATUS_417', '417 Expectation Failed'); +define('HTTP_HEADER_STATUS_422', '422 Unprocessable Entity'); +define('HTTP_HEADER_STATUS_423', '423 Locked'); +define('HTTP_HEADER_STATUS_424', '424 Failed Dependency'); +define('HTTP_HEADER_STATUS_CLIENT_ERROR',4); +/**#@-*/ + +/**#@+ + * Server Errors + */ +define('HTTP_HEADER_STATUS_500', '500 Internal Server Error'); +define('HTTP_HEADER_STATUS_501', '501 Not Implemented'); +define('HTTP_HEADER_STATUS_502', '502 Bad Gateway'); +define('HTTP_HEADER_STATUS_503', '503 Service Unavailable'); +define('HTTP_HEADER_STATUS_504', '504 Gateway Time-out'); +define('HTTP_HEADER_STATUS_505', '505 HTTP Version not supported'); +define('HTTP_HEADER_STATUS_507', '507 Insufficient Storage'); +define('HTTP_HEADER_STATUS_SERVER_ERROR',5); +/**#@-*/ + +/** + * HTTP_Header + * + * @package HTTP_Header + * @category HTTP + * @access public + * @version $Revision: 1.32 $ + */ +class HTTP_Header extends HTTP +{ + /** + * Default Headers + * + * The values that are set as default, are the same as PHP sends by default. + * + * @var array + * @access private + */ + var $_headers = array( + 'content-type' => 'text/html', + 'pragma' => 'no-cache', + 'cache-control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' + ); + + /** + * HTTP version + * + * @var string + * @access private + */ + var $_httpVersion = '1.0'; + + /** + * Constructor + * + * Sets HTTP version. + * + * @access public + * @return object HTTP_Header + */ + function HTTP_Header() + { + if (isset($_SERVER['SERVER_PROTOCOL'])) { + $this->setHttpVersion(substr($_SERVER['SERVER_PROTOCOL'], -3)); + } + } + + /** + * Set HTTP version + * + * @access public + * @return bool Returns true on success or false if version doesn't + * match 1.0 or 1.1 (note: 1 will result in 1.0) + * @param mixed $version HTTP version, either 1.0 or 1.1 + */ + function setHttpVersion($version) + { + $version = round((float) $version, 1); + if ($version < 1.0 || $version > 1.1) { + return false; + } + $this->_httpVersion = sprintf('%0.1f', $version); + return true; + } + + /** + * Get HTTP version + * + * @access public + * @return string + */ + function getHttpVersion() + { + return $this->_httpVersion; + } + + /** + * Set Header + * + * The default value for the Last-Modified header will be current + * date and atime if $value is omitted. + * + * @access public + * @return bool Returns true on success or false if $key was empty or + * $value was not of an scalar type. + * @param string $key The name of the header. + * @param string $value The value of the header. (NULL to unset header) + */ + function setHeader($key, $value = null) + { + if (empty($key) || (isset($value) && !is_scalar($value))) { + return false; + } + + $key = strToLower($key); + if ($key == 'last-modified') { + if (!isset($value)) { + $value = HTTP::Date(time()); + } elseif (is_numeric($value)) { + $value = HTTP::Date($value); + } + } + + if (isset($value)) { + $this->_headers[$key] = $value; + } else { + unset($this->_headers[$key]); + } + + return true; + } + + /** + * Get Header + * + * If $key is omitted, all stored headers will be returned. + * + * @access public + * @return mixed Returns string value of the requested header, + * array values of all headers or false if header $key + * is not set. + * @param string $key The name of the header to fetch. + */ + function getHeader($key = null) + { + if (!isset($key)) { + return $this->_headers; + } + + $key = strToLower($key); + + if (!isset($this->_headers[$key])) { + return false; + } + + return $this->_headers[$key]; + } + + /** + * Send Headers + * + * Send out the header that you set via setHeader(). + * + * @access public + * @return bool Returns true on success or false if headers are already + * sent. + * @param array $keys Headers to (not) send, see $include. + * @param array $include If true only $keys matching headers will be + * sent, if false only header not matching $keys will be + * sent. + */ + function sendHeaders($keys = array(), $include = true) + { + if (headers_sent()) { + return false; + } + + if (count($keys)) { + array_change_key_case($keys, CASE_LOWER); + foreach ($this->_headers as $key => $value) { + if ($include ? in_array($key, $keys) : !in_array($key, $keys)) { + header($key .': '. $value); + } + } + } else { + foreach ($this->_headers as $header => $value) { + header($header .': '. $value); + } + } + return true; + } + + /** + * Send Satus Code + * + * Send out the given HTTP-Status code. Use this for example when you + * want to tell the client this page is cached, then you would call + * sendStatusCode(304). + * + * @see HTTP_Header_Cache::exitIfCached() + * + * @access public + * @return bool Returns true on success or false if headers are already + * sent. + * @param int $code The status code to send, i.e. 404, 304, 200, etc. + */ + function sendStatusCode($code) + { + if (headers_sent()) { + return false; + } + + if ($code == (int) $code && defined('HTTP_HEADER_STATUS_'. $code)) { + $code = constant('HTTP_HEADER_STATUS_'. $code); + } + + if (strncasecmp(PHP_SAPI, 'cgi', 3)) { + header('HTTP/'. $this->_httpVersion .' '. $code); + } else { + header('Status: '. $code); + } + return true; + } + + /** + * Date to Timestamp + * + * Converts dates like + * Mon, 31 Mar 2003 15:26:34 GMT + * Tue, 15 Nov 1994 12:45:26 GMT + * into a timestamp, strtotime() didn't do it in older versions. + * + * @deprecated Use PHPs strtotime() instead. + * @access public + * @return mixed Returns int unix timestamp or false if the date doesn't + * seem to be a valid GMT date. + * @param string $date The GMT date. + */ + function dateToTimestamp($date) + { + static $months = array( + null => 0, 'Jan' => 1, 'Feb' => 2, 'Mar' => 3, 'Apr' => 4, + 'May' => 5, 'Jun' => 6, 'Jul' => 7, 'Aug' => 8, 'Sep' => 9, + 'Oct' => 10, 'Nov' => 11, 'Dec' => 12 + ); + + if (-1 < $timestamp = strToTime($date)) { + return $timestamp; + } + + if (!preg_match('~[^,]*,\s(\d+)\s(\w+)\s(\d+)\s(\d+):(\d+):(\d+).*~', + $date, $m)) { + return false; + } + + // [0] => Mon, 31 Mar 2003 15:42:55 GMT + // [1] => 31 [2] => Mar [3] => 2003 [4] => 15 [5] => 42 [6] => 55 + return mktime($m[4], $m[5], $m[6], $months[$m[2]], $m[1], $m[3]); + } + + /** + * Redirect + * + * This function redirects the client. This is done by issuing a Location + * header and exiting. Additionally to HTTP::redirect() you can also add + * parameters to the url. + * + * If you dont need parameters to be added, simply use HTTP::redirect() + * otherwise use HTTP_Header::redirect(). + * + * @see HTTP::redirect() + * @author Wolfram Kriesing + * @access public + * @return void + * @param string $url The URL to redirect to, if none is given it + * redirects to the current page. + * @param array $param Array of query string parameters to add; usually + * a set of key => value pairs; if an array entry consists + * only of an value it is used as key and the respective + * value is fetched from $GLOBALS[$value] + * @param bool $session Whether the session name/id should be added + */ + function redirect($url = null, $param = array(), $session = false) + { + if (!isset($url)) { + $url = $_SERVER['PHP_SELF']; + } + + $qs = array(); + + if ($session) { + $qs[] = session_name() .'='. session_id(); + } + + if (is_array($param) && count($param)) { + if (count($param)) { + foreach ($param as $key => $val) { + if (is_string($key)) { + $qs[] = urlencode($key) .'='. urlencode($val); + } else { + $qs[] = urlencode($val) .'='. urlencode(@$GLOBALS[$val]); + } + } + } + } + + if ($qstr = implode('&', $qs)) { + $purl = parse_url($url); + $url .= (isset($purl['query']) ? '&' : '?') . $qstr; + } + + parent::redirect($url); + } + + /**#@+ + * @author Davey Shafik + * @param int $http_code HTTP Code to check + * @access public + */ + + /** + * Return HTTP Status Code Type + * + * @return int|false + */ + function getStatusType($http_code) + { + if(is_int($http_code) && defined('HTTP_HEADER_STATUS_' .$http_code) || defined($http_code)) { + $type = substr($http_code,0,1); + switch ($type) { + case HTTP_HEADER_STATUS_INFORMATIONAL: + case HTTP_HEADER_STATUS_SUCCESSFUL: + case HTTP_HEADER_STATUS_REDIRECT: + case HTTP_HEADER_STATUS_CLIENT_ERROR: + case HTTP_HEADER_STATUS_SERVER_ERROR: + return $type; + break; + default: + return false; + break; + } + } else { + return false; + } + } + + /** + * Return Status Code Message + * + * @return string|false + */ + function getStatusText($http_code) + { + if ($this->getStatusType($http_code)) { + if (is_int($http_code) && defined('HTTP_HEADER_STATUS_' .$http_code)) { + return substr(constant('HTTP_HEADER_STATUS_' .$http_code),4); + } else { + return substr($http_code,4); + } + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Information (1xx) + * + * @return boolean + */ + function isInformational($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_INFORMATIONAL; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Successful (2xx) + * + * @return boolean + */ + function isSuccessful($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_SUCCESSFUL; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is a Redirect (3xx) + * + * @return boolean + */ + function isRedirect($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_REDIRECT; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is a Client Error (4xx) + * + * @return boolean + */ + function isClientError($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_CLIENT_ERROR; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Server Error (5xx) + * + * @return boolean + */ + function isServerError($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return $status_type{0} == HTTP_HEADER_STATUS_SERVER_ERROR; + } else { + return false; + } + } + + /** + * Checks if HTTP Status code is Server OR Client Error (4xx or 5xx) + * + * @return boolean + */ + function isError($http_code) + { + if ($status_type = $this->getStatusType($http_code)) { + return (($status_type == HTTP_HEADER_STATUS_CLIENT_ERROR) || ($status_type == HTTP_HEADER_STATUS_SERVER_ERROR)) ? true : false; + } else { + return false; + } + } + /**#@-*/ +} +?> diff --git a/thirdparty/pear/HTTP/Header/Cache.php b/thirdparty/pear/HTTP/Header/Cache.php new file mode 100644 index 0000000..af26b76 --- /dev/null +++ b/thirdparty/pear/HTTP/Header/Cache.php @@ -0,0 +1,238 @@ + + * @author Michael Wallner + * @copyright 2003-2005 The Authors + * @license BSD, revised + * @version CVS: $Id: Cache.php,v 1.24 2005/11/08 19:06:14 mike Exp $ + * @link http://pear.php.net/package/HTTP_Header + */ + +/** + * Requires HTTP_Header + */ +require_once 'HTTP/Header.php'; + +/** + * HTTP_Header_Cache + * + * This package provides methods to easier handle caching of HTTP pages. That + * means that the pages can be cached at the client (user agent or browser) and + * your application only needs to send "hey client you already have the pages". + * + * Which is done by sending the HTTP-Status "304 Not Modified", so that your + * application load and the network traffic can be reduced, since you only need + * to send the complete page once. This is really an advantage e.g. for + * generated style sheets, or simply pages that do only change rarely. + * + * Usage: + * + * require_once 'HTTP/Header/Cache.php'; + * $httpCache = new HTTP_Header_Cache(4, 'weeks'); + * $httpCache->sendHeaders(); + * // your code goes here + * + * + * @package HTTP_Header + * @category HTTP + * @access public + * @version $Revision: 1.24 $ + */ +class HTTP_Header_Cache extends HTTP_Header +{ + /** + * Constructor + * + * Set the amount of time to cache. + * + * @access public + * @return object HTTP_Header_Cache + * @param int $expires + * @param string $unit + */ + function HTTP_Header_Cache($expires = 0, $unit = 'seconds') + { + parent::HTTP_Header(); + $this->setHeader('Pragma', 'cache'); + $this->setHeader('Last-Modified', $this->getCacheStart()); + $this->setHeader('Cache-Control', 'private, must-revalidate, max-age=0'); + + if ($expires) { + if (!$this->isOlderThan($expires, $unit)) { + $this->exitCached(); + } + $this->setHeader('Last-Modified', time()); + } + } + + /** + * Get Cache Start + * + * Returns the unix timestamp of the If-Modified-Since HTTP header or the + * current time if the header was not sent by the client. + * + * @access public + * @return int unix timestamp + */ + function getCacheStart() + { + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !$this->isPost()) { + return strtotime(current($array = explode(';', + $_SERVER['HTTP_IF_MODIFIED_SINCE']))); + } + return time(); + } + + /** + * Is Older Than + * + * You can call it like this: + * + * $httpCache->isOlderThan(1, 'day'); + * $httpCache->isOlderThan(47, 'days'); + * + * $httpCache->isOlderThan(1, 'week'); + * $httpCache->isOlderThan(3, 'weeks'); + * + * $httpCache->isOlderThan(1, 'hour'); + * $httpCache->isOlderThan(5, 'hours'); + * + * $httpCache->isOlderThan(1, 'minute'); + * $httpCache->isOlderThan(15, 'minutes'); + * + * $httpCache->isOlderThan(1, 'second'); + * $httpCache->isOlderThan(15); + * + * + * If you specify something greater than "weeks" as time untit, it just + * works approximatly, because a month is taken to consist of 4.3 weeks. + * + * @access public + * @return bool Returns true if requested page is older than specified. + * @param int $time The amount of time. + * @param string $unit The unit of the time amount - (year[s], month[s], + * week[s], day[s], hour[s], minute[s], second[s]). + */ + function isOlderThan($time = 0, $unit = 'seconds') + { + if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || $this->isPost()) { + return true; + } + if (!$time) { + return false; + } + + switch (strtolower($unit)) + { + case 'year': + case 'years': + $time *= 12; + case 'month': + case 'months': + $time *= 4.3; + case 'week': + case 'weeks': + $time *= 7; + case 'day': + case 'days': + $time *= 24; + case 'hour': + case 'hours': + $time *= 60; + case 'minute': + case 'minutes': + $time *= 60; + } + + return (time() - $this->getCacheStart()) > $time; + } + + /** + * Is Cached + * + * Check whether we can consider to be cached on the client side. + * + * @access public + * @return bool Whether the page/resource is considered to be cached. + * @param int $lastModified Unix timestamp of last modification. + */ + function isCached($lastModified = 0) + { + if ($this->isPost()) { + return false; + } + if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && !$lastModified) { + return true; + } + if (!$seconds = time() - $lastModified) { + return false; + } + return !$this->isOlderThan($seconds); + } + + /** + * Is Post + * + * Check if request method is "POST". + * + * @access public + * @return bool + */ + function isPost() + { + return isset($_SERVER['REQUEST_METHOD']) and + 'POST' == $_SERVER['REQUEST_METHOD']; + } + + /** + * Exit If Cached + * + * Exit with "HTTP 304 Not Modified" if we consider to be cached. + * + * @access public + * @return void + * @param int $lastModified Unix timestamp of last modification. + */ + function exitIfCached($lastModified = 0) + { + if ($this->isCached($lastModified)) { + $this->exitCached(); + } + } + + /** + * Exit Cached + * + * Exit with "HTTP 304 Not Modified". + * + * @access public + * @return void + */ + function exitCached() + { + $this->sendHeaders(); + $this->sendStatusCode(304); + exit; + } + + /** + * Set Last Modified + * + * @access public + * @return void + * @param int $lastModified The unix timestamp of last modification. + */ + function setLastModified($lastModified = null) + { + $this->setHeader('Last-Modified', $lastModified); + } +} +?> diff --git a/thirdparty/pear/HTTP/Request.php b/thirdparty/pear/HTTP/Request.php new file mode 100644 index 0000000..f7fa93e --- /dev/null +++ b/thirdparty/pear/HTTP/Request.php @@ -0,0 +1,1191 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: Request.php,v 1.43 2005/11/06 18:29:14 avb Exp $ +// +// HTTP_Request Class +// +// Simple example, (Fetches yahoo.com and displays it): +// +// $a = &new HTTP_Request('http://www.yahoo.com/'); +// $a->sendRequest(); +// echo $a->getResponseBody(); +// + +require_once 'PEAR.php'; +require_once 'Net/Socket.php'; +require_once 'Net/URL.php'; + +define('HTTP_REQUEST_METHOD_GET', 'GET', true); +define('HTTP_REQUEST_METHOD_HEAD', 'HEAD', true); +define('HTTP_REQUEST_METHOD_POST', 'POST', true); +define('HTTP_REQUEST_METHOD_PUT', 'PUT', true); +define('HTTP_REQUEST_METHOD_DELETE', 'DELETE', true); +define('HTTP_REQUEST_METHOD_OPTIONS', 'OPTIONS', true); +define('HTTP_REQUEST_METHOD_TRACE', 'TRACE', true); + +define('HTTP_REQUEST_HTTP_VER_1_0', '1.0', true); +define('HTTP_REQUEST_HTTP_VER_1_1', '1.1', true); + +class HTTP_Request { + + /** + * Instance of Net_URL + * @var object Net_URL + */ + var $_url; + + /** + * Type of request + * @var string + */ + var $_method; + + /** + * HTTP Version + * @var string + */ + var $_http; + + /** + * Request headers + * @var array + */ + var $_requestHeaders; + + /** + * Basic Auth Username + * @var string + */ + var $_user; + + /** + * Basic Auth Password + * @var string + */ + var $_pass; + + /** + * Socket object + * @var object Net_Socket + */ + var $_sock; + + /** + * Proxy server + * @var string + */ + var $_proxy_host; + + /** + * Proxy port + * @var integer + */ + var $_proxy_port; + + /** + * Proxy username + * @var string + */ + var $_proxy_user; + + /** + * Proxy password + * @var string + */ + var $_proxy_pass; + + /** + * Post data + * @var array + */ + var $_postData; + + /** + * Request body + * @var string + */ + var $_body; + + /** + * A list of methods that MUST NOT have a request body, per RFC 2616 + * @var array + */ + var $_bodyDisallowed = array('TRACE'); + + /** + * Files to post + * @var array + */ + var $_postFiles = array(); + + /** + * Connection timeout. + * @var float + */ + var $_timeout; + + /** + * HTTP_Response object + * @var object HTTP_Response + */ + var $_response; + + /** + * Whether to allow redirects + * @var boolean + */ + var $_allowRedirects; + + /** + * Maximum redirects allowed + * @var integer + */ + var $_maxRedirects; + + /** + * Current number of redirects + * @var integer + */ + var $_redirects; + + /** + * Whether to append brackets [] to array variables + * @var bool + */ + var $_useBrackets = true; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Whether to save response body in response object property + * @var bool + */ + var $_saveBody = true; + + /** + * Timeout for reading from socket (array(seconds, microseconds)) + * @var array + */ + var $_readTimeout = null; + + /** + * Options to pass to Net_Socket::connect. See stream_context_create + * @var array + */ + var $_socketOptions = null; + + /** + * Constructor + * + * Sets up the object + * @param string The url to fetch/access + * @param array Associative array of parameters which can have the following keys: + *
    + *
  • method - Method to use, GET, POST etc (string)
  • + *
  • http - HTTP Version to use, 1.0 or 1.1 (string)
  • + *
  • user - Basic Auth username (string)
  • + *
  • pass - Basic Auth password (string)
  • + *
  • proxy_host - Proxy server host (string)
  • + *
  • proxy_port - Proxy server port (integer)
  • + *
  • proxy_user - Proxy auth username (string)
  • + *
  • proxy_pass - Proxy auth password (string)
  • + *
  • timeout - Connection timeout in seconds (float)
  • + *
  • allowRedirects - Whether to follow redirects or not (bool)
  • + *
  • maxRedirects - Max number of redirects to follow (integer)
  • + *
  • useBrackets - Whether to append [] to array variable names (bool)
  • + *
  • saveBody - Whether to save response body in response object property (bool)
  • + *
  • readTimeout - Timeout for reading / writing data over the socket (array (seconds, microseconds))
  • + *
  • socketOptions - Options to pass to Net_Socket object (array)
  • + *
+ * @access public + */ + function HTTP_Request($url = '', $params = array()) + { + $this->_sock = &new Net_Socket(); + $this->_method = HTTP_REQUEST_METHOD_GET; + $this->_http = HTTP_REQUEST_HTTP_VER_1_1; + $this->_requestHeaders = array(); + $this->_postData = array(); + $this->_body = null; + + $this->_user = null; + $this->_pass = null; + + $this->_proxy_host = null; + $this->_proxy_port = null; + $this->_proxy_user = null; + $this->_proxy_pass = null; + + $this->_allowRedirects = false; + $this->_maxRedirects = 3; + $this->_redirects = 0; + + $this->_timeout = null; + $this->_response = null; + + foreach ($params as $key => $value) { + $this->{'_' . $key} = $value; + } + + if (!empty($url)) { + $this->setURL($url); + } + + // Default useragent + $this->addHeader('User-Agent', 'PEAR HTTP_Request class ( http://pear.php.net/ )'); + + // Make sure keepalives dont knobble us + $this->addHeader('Connection', 'close'); + + // Basic authentication + if (!empty($this->_user)) { + $this->addHeader('Authorization', 'Basic ' . base64_encode($this->_user . ':' . $this->_pass)); + } + + // Use gzip encoding if possible + // Avoid gzip encoding if using multibyte functions (see #1781) + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http && extension_loaded('zlib') && + 0 == (2 & ini_get('mbstring.func_overload'))) { + + $this->addHeader('Accept-Encoding', 'gzip'); + } + } + + /** + * Generates a Host header for HTTP/1.1 requests + * + * @access private + * @return string + */ + function _generateHostHeader() + { + if ($this->_url->port != 80 AND strcasecmp($this->_url->protocol, 'http') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port != 443 AND strcasecmp($this->_url->protocol, 'https') == 0) { + $host = $this->_url->host . ':' . $this->_url->port; + + } elseif ($this->_url->port == 443 AND strcasecmp($this->_url->protocol, 'https') == 0 AND strpos($this->_url->url, ':443') !== false) { + $host = $this->_url->host . ':' . $this->_url->port; + + } else { + $host = $this->_url->host; + } + + return $host; + } + + /** + * Resets the object to its initial state (DEPRECATED). + * Takes the same parameters as the constructor. + * + * @param string $url The url to be requested + * @param array $params Associative array of parameters + * (see constructor for details) + * @access public + * @deprecated deprecated since 1.2, call the constructor if this is necessary + */ + function reset($url, $params = array()) + { + $this->HTTP_Request($url, $params); + } + + /** + * Sets the URL to be requested + * + * @param string The url to be requested + * @access public + */ + function setURL($url) + { + $this->_url = &new Net_URL($url, $this->_useBrackets); + + if (!empty($this->_url->user) || !empty($this->_url->pass)) { + $this->setBasicAuth($this->_url->user, $this->_url->pass); + } + + if (HTTP_REQUEST_HTTP_VER_1_1 == $this->_http) { + $this->addHeader('Host', $this->_generateHostHeader()); + } + } + + /** + * Sets a proxy to be used + * + * @param string Proxy host + * @param int Proxy port + * @param string Proxy username + * @param string Proxy password + * @access public + */ + function setProxy($host, $port = 8080, $user = null, $pass = null) + { + $this->_proxy_host = $host; + $this->_proxy_port = $port; + $this->_proxy_user = $user; + $this->_proxy_pass = $pass; + + if (!empty($user)) { + $this->addHeader('Proxy-Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + } + + /** + * Sets basic authentication parameters + * + * @param string Username + * @param string Password + */ + function setBasicAuth($user, $pass) + { + $this->_user = $user; + $this->_pass = $pass; + + $this->addHeader('Authorization', 'Basic ' . base64_encode($user . ':' . $pass)); + } + + /** + * Sets the method to be used, GET, POST etc. + * + * @param string Method to use. Use the defined constants for this + * @access public + */ + function setMethod($method) + { + $this->_method = $method; + } + + /** + * Sets the HTTP version to use, 1.0 or 1.1 + * + * @param string Version to use. Use the defined constants for this + * @access public + */ + function setHttpVer($http) + { + $this->_http = $http; + } + + /** + * Adds a request header + * + * @param string Header name + * @param string Header value + * @access public + */ + function addHeader($name, $value) + { + $this->_requestHeaders[strtolower($name)] = $value; + } + + /** + * Removes a request header + * + * @param string Header name to remove + * @access public + */ + function removeHeader($name) + { + if (isset($this->_requestHeaders[strtolower($name)])) { + unset($this->_requestHeaders[strtolower($name)]); + } + } + + /** + * Adds a querystring parameter + * + * @param string Querystring parameter name + * @param string Querystring parameter value + * @param bool Whether the value is already urlencoded or not, default = not + * @access public + */ + function addQueryString($name, $value, $preencoded = false) + { + $this->_url->addQueryString($name, $value, $preencoded); + } + + /** + * Sets the querystring to literally what you supply + * + * @param string The querystring data. Should be of the format foo=bar&x=y etc + * @param bool Whether data is already urlencoded or not, default = already encoded + * @access public + */ + function addRawQueryString($querystring, $preencoded = true) + { + $this->_url->addRawQueryString($querystring, $preencoded); + } + + /** + * Adds postdata items + * + * @param string Post data name + * @param string Post data value + * @param bool Whether data is already urlencoded or not, default = not + * @access public + */ + function addPostData($name, $value, $preencoded = false) + { + if ($preencoded) { + $this->_postData[$name] = $value; + } else { + $this->_postData[$name] = $this->_arrayMapRecursive('urlencode', $value); + } + } + + /** + * Recursively applies the callback function to the value + * + * @param mixed Callback function + * @param mixed Value to process + * @access private + * @return mixed Processed value + */ + function _arrayMapRecursive($callback, $value) + { + if (!is_array($value)) { + return call_user_func($callback, $value); + } else { + $map = array(); + foreach ($value as $k => $v) { + $map[$k] = $this->_arrayMapRecursive($callback, $v); + } + return $map; + } + } + + /** + * Adds a file to upload + * + * This also changes content-type to 'multipart/form-data' for proper upload + * + * @access public + * @param string name of file-upload field + * @param mixed file name(s) + * @param mixed content-type(s) of file(s) being uploaded + * @return bool true on success + * @throws PEAR_Error + */ + function addFile($inputName, $fileName, $contentType = 'application/octet-stream') + { + if (!is_array($fileName) && !is_readable($fileName)) { + return PEAR::raiseError("File '{$fileName}' is not readable"); + } elseif (is_array($fileName)) { + foreach ($fileName as $name) { + if (!is_readable($name)) { + return PEAR::raiseError("File '{$name}' is not readable"); + } + } + } + $this->addHeader('Content-Type', 'multipart/form-data'); + $this->_postFiles[$inputName] = array( + 'name' => $fileName, + 'type' => $contentType + ); + return true; + } + + /** + * Adds raw postdata (DEPRECATED) + * + * @param string The data + * @param bool Whether data is preencoded or not, default = already encoded + * @access public + * @deprecated deprecated since 1.3.0, method addBody() should be used instead + */ + function addRawPostData($postdata, $preencoded = true) + { + $this->_body = $preencoded ? $postdata : urlencode($postdata); + } + + /** + * Sets the request body (for POST, PUT and similar requests) + * + * @param string Request body + * @access public + */ + function setBody($body) + { + $this->_body = $body; + } + + /** + * Clears any postdata that has been added (DEPRECATED). + * + * Useful for multiple request scenarios. + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearPostData() + { + $this->_postData = null; + } + + /** + * Appends a cookie to "Cookie:" header + * + * @param string $name cookie name + * @param string $value cookie value + * @access public + */ + function addCookie($name, $value) + { + $cookies = isset($this->_requestHeaders['cookie']) ? $this->_requestHeaders['cookie']. '; ' : ''; + $this->addHeader('Cookie', $cookies . $name . '=' . $value); + } + + /** + * Clears any cookies that have been added (DEPRECATED). + * + * Useful for multiple request scenarios + * + * @access public + * @deprecated deprecated since 1.2 + */ + function clearCookies() + { + $this->removeHeader('Cookie'); + } + + /** + * Sends the request + * + * @access public + * @param bool Whether to store response body in Response object property, + * set this to false if downloading a LARGE file and using a Listener + * @return mixed PEAR error on error, true otherwise + */ + function sendRequest($saveBody = true) + { + if (!is_a($this->_url, 'Net_URL')) { + return PEAR::raiseError('No URL given.'); + } + + $host = isset($this->_proxy_host) ? $this->_proxy_host : $this->_url->host; + $port = isset($this->_proxy_port) ? $this->_proxy_port : $this->_url->port; + + // 4.3.0 supports SSL connections using OpenSSL. The function test determines + // we running on at least 4.3.0 + if (strcasecmp($this->_url->protocol, 'https') == 0 AND function_exists('file_get_contents') AND extension_loaded('openssl')) { + if (isset($this->_proxy_host)) { + return PEAR::raiseError('HTTPS proxies are not supported.'); + } + $host = 'ssl://' . $host; + } + + // magic quotes may fuck up file uploads and chunked response processing + $magicQuotes = ini_get('magic_quotes_runtime'); + ini_set('magic_quotes_runtime', false); + + // If this is a second request, we may get away without + // re-connecting if they're on the same server + $err = $this->_sock->connect($host, $port, null, $this->_timeout, $this->_socketOptions); + PEAR::isError($err) or $err = $this->_sock->write($this->_buildRequest()); + + if (!PEAR::isError($err)) { + if (!empty($this->_readTimeout)) { + $this->_sock->setTimeout($this->_readTimeout[0], $this->_readTimeout[1]); + } + + $this->_notify('sentRequest'); + + // Read the response + $this->_response = &new HTTP_Response($this->_sock, $this->_listeners); + $err = $this->_response->process($this->_saveBody && $saveBody); + } + + ini_set('magic_quotes_runtime', $magicQuotes); + + if (PEAR::isError($err)) { + return $err; + } + + + // Check for redirection + if ( $this->_allowRedirects + AND $this->_redirects <= $this->_maxRedirects + AND $this->getResponseCode() > 300 + AND $this->getResponseCode() < 399 + AND !empty($this->_response->_headers['location'])) { + + + $redirect = $this->_response->_headers['location']; + + // Absolute URL + if (preg_match('/^https?:\/\//i', $redirect)) { + $this->_url = &new Net_URL($redirect); + $this->addHeader('Host', $this->_generateHostHeader()); + // Absolute path + } elseif ($redirect{0} == '/') { + $this->_url->path = $redirect; + + // Relative path + } elseif (substr($redirect, 0, 3) == '../' OR substr($redirect, 0, 2) == './') { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $redirect = Net_URL::resolvePath($redirect); + $this->_url->path = $redirect; + + // Filename, no path + } else { + if (substr($this->_url->path, -1) == '/') { + $redirect = $this->_url->path . $redirect; + } else { + $redirect = dirname($this->_url->path) . '/' . $redirect; + } + $this->_url->path = $redirect; + } + + $this->_redirects++; + return $this->sendRequest($saveBody); + + // Too many redirects + } elseif ($this->_allowRedirects AND $this->_redirects > $this->_maxRedirects) { + return PEAR::raiseError('Too many redirects'); + } + + $this->_sock->disconnect(); + + return true; + } + + /** + * Returns the response code + * + * @access public + * @return mixed Response code, false if not set + */ + function getResponseCode() + { + return isset($this->_response->_code) ? $this->_response->_code : false; + } + + /** + * Returns either the named header or all if no name given + * + * @access public + * @param string The header name to return, do not set to get all headers + * @return mixed either the value of $headername (false if header is not present) + * or an array of all headers + */ + function getResponseHeader($headername = null) + { + if (!isset($headername)) { + return isset($this->_response->_headers)? $this->_response->_headers: array(); + } else { + $headername = strtolower($headername); + return isset($this->_response->_headers[$headername]) ? $this->_response->_headers[$headername] : false; + } + } + + /** + * Returns the body of the response + * + * @access public + * @return mixed response body, false if not set + */ + function getResponseBody() + { + return isset($this->_response->_body) ? $this->_response->_body : false; + } + + /** + * Returns cookies set in response + * + * @access public + * @return mixed array of response cookies, false if none are present + */ + function getResponseCookies() + { + return isset($this->_response->_cookies) ? $this->_response->_cookies : false; + } + + /** + * Builds the request string + * + * @access private + * @return string The request string + */ + function _buildRequest() + { + $separator = ini_get('arg_separator.output'); + ini_set('arg_separator.output', '&'); + $querystring = ($querystring = $this->_url->getQueryString()) ? '?' . $querystring : ''; + ini_set('arg_separator.output', $separator); + + $host = isset($this->_proxy_host) ? $this->_url->protocol . '://' . $this->_url->host : ''; + $port = (isset($this->_proxy_host) AND $this->_url->port != 80) ? ':' . $this->_url->port : ''; + $path = (empty($this->_url->path)? '/': $this->_url->path) . $querystring; + $url = $host . $port . $path; + + $request = $this->_method . ' ' . $url . ' HTTP/' . $this->_http . "\r\n"; + + if (in_array($this->_method, $this->_bodyDisallowed) || + (HTTP_REQUEST_METHOD_POST != $this->_method && empty($this->_body)) || + (HTTP_REQUEST_METHOD_POST != $this->_method && empty($this->_postData) && empty($this->_postFiles))) { + + $this->removeHeader('Content-Type'); + } else { + if (empty($this->_requestHeaders['content-type'])) { + // Add default content-type + $this->addHeader('Content-Type', 'application/x-www-form-urlencoded'); + } elseif ('multipart/form-data' == $this->_requestHeaders['content-type']) { + $boundary = 'HTTP_Request_' . md5(uniqid('request') . microtime()); + $this->addHeader('Content-Type', 'multipart/form-data; boundary=' . $boundary); + } + } + + // Request Headers + if (!empty($this->_requestHeaders)) { + foreach ($this->_requestHeaders as $name => $value) { + $canonicalName = implode('-', array_map('ucfirst', explode('-', $name))); + $request .= $canonicalName . ': ' . $value . "\r\n"; + } + } + + // No post data or wrong method, so simply add a final CRLF + if (in_array($this->_method, $this->_bodyDisallowed) || + (HTTP_REQUEST_METHOD_POST != $this->_method && empty($this->_body))) { + + $request .= "\r\n"; + + // Post data if it's an array + } elseif (HTTP_REQUEST_METHOD_POST == $this->_method && + (!empty($this->_postData) || !empty($this->_postFiles))) { + + // "normal" POST request + if (!isset($boundary)) { + $postdata = implode('&', array_map( + create_function('$a', 'return $a[0] . \'=\' . $a[1];'), + $this->_flattenArray('', $this->_postData) + )); + + // multipart request, probably with file uploads + } else { + $postdata = ''; + if (!empty($this->_postData)) { + $flatData = $this->_flattenArray('', $this->_postData); + foreach ($flatData as $item) { + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $item[0] . '"'; + $postdata .= "\r\n\r\n" . urldecode($item[1]) . "\r\n"; + } + } + foreach ($this->_postFiles as $name => $value) { + if (is_array($value['name'])) { + $varname = $name . ($this->_useBrackets? '[]': ''); + } else { + $varname = $name; + $value['name'] = array($value['name']); + } + foreach ($value['name'] as $key => $filename) { + $fp = fopen($filename, 'r'); + $data = fread($fp, filesize($filename)); + fclose($fp); + $basename = basename($filename); + $type = is_array($value['type'])? @$value['type'][$key]: $value['type']; + + $postdata .= '--' . $boundary . "\r\n"; + $postdata .= 'Content-Disposition: form-data; name="' . $varname . '"; filename="' . $basename . '"'; + $postdata .= "\r\nContent-Type: " . $type; + $postdata .= "\r\n\r\n" . $data . "\r\n"; + } + } + $postdata .= '--' . $boundary . "--\r\n"; + } + $request .= 'Content-Length: ' . strlen($postdata) . "\r\n\r\n"; + $request .= $postdata; + + // Explicitly set request body + } elseif (!empty($this->_body)) { + + $request .= 'Content-Length: ' . strlen($this->_body) . "\r\n\r\n"; + $request .= $this->_body; + } + + return $request; + } + + /** + * Helper function to change the (probably multidimensional) associative array + * into the simple one. + * + * @param string name for item + * @param mixed item's values + * @return array array with the following items: array('item name', 'item value'); + */ + function _flattenArray($name, $values) + { + if (!is_array($values)) { + return array(array($name, $values)); + } else { + $ret = array(); + foreach ($values as $k => $v) { + if (empty($name)) { + $newName = $k; + } elseif ($this->_useBrackets) { + $newName = $name . '[' . $k . ']'; + } else { + $newName = $name; + } + $ret = array_merge($ret, $this->_flattenArray($newName, $v)); + } + return $ret; + } + } + + + /** + * Adds a Listener to the list of listeners that are notified of + * the object's events + * + * @param object HTTP_Request_Listener instance to attach + * @return boolean whether the listener was successfully attached + * @access public + */ + function attach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener')) { + return false; + } + $this->_listeners[$listener->getId()] =& $listener; + return true; + } + + + /** + * Removes a Listener from the list of listeners + * + * @param object HTTP_Request_Listener instance to detach + * @return boolean whether the listener was successfully detached + * @access public + */ + function detach(&$listener) + { + if (!is_a($listener, 'HTTP_Request_Listener') || + !isset($this->_listeners[$listener->getId()])) { + return false; + } + unset($this->_listeners[$listener->getId()]); + return true; + } + + + /** + * Notifies all registered listeners of an event. + * + * Events sent by HTTP_Request object + * 'sentRequest': after the request was sent + * Events sent by HTTP_Response object + * 'gotHeaders': after receiving response headers (headers are passed in $data) + * 'tick': on receiving a part of response body (the part is passed in $data) + * 'gzTick': on receiving a gzip-encoded part of response body (ditto) + * 'gotBody': after receiving the response body (passes the decoded body in $data if it was gzipped) + * + * @param string Event name + * @param mixed Additional data + * @access private + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } +} + + +/** +* Response class to complement the Request class +*/ +class HTTP_Response +{ + /** + * Socket object + * @var object + */ + var $_sock; + + /** + * Protocol + * @var string + */ + var $_protocol; + + /** + * Return code + * @var string + */ + var $_code; + + /** + * Response headers + * @var array + */ + var $_headers; + + /** + * Cookies set in response + * @var array + */ + var $_cookies; + + /** + * Response body + * @var string + */ + var $_body = ''; + + /** + * Used by _readChunked(): remaining length of the current chunk + * @var string + */ + var $_chunkLength = 0; + + /** + * Attached listeners + * @var array + */ + var $_listeners = array(); + + /** + * Constructor + * + * @param object Net_Socket socket to read the response from + * @param array listeners attached to request + * @return mixed PEAR Error on error, true otherwise + */ + function HTTP_Response(&$sock, &$listeners) + { + $this->_sock =& $sock; + $this->_listeners =& $listeners; + } + + + /** + * Processes a HTTP response + * + * This extracts response code, headers, cookies and decodes body if it + * was encoded in some way + * + * @access public + * @param bool Whether to store response body in object property, set + * this to false if downloading a LARGE file and using a Listener. + * This is assumed to be true if body is gzip-encoded. + * @throws PEAR_Error + * @return mixed true on success, PEAR_Error in case of malformed response + */ + function process($saveBody = true) + { + do { + $line = $this->_sock->readLine(); + if (sscanf($line, 'HTTP/%s %s', $http_version, $returncode) != 2) { + return PEAR::raiseError('Malformed response.'); + } else { + $this->_protocol = 'HTTP/' . $http_version; + $this->_code = intval($returncode); + } + while ('' !== ($header = $this->_sock->readLine())) { + $this->_processHeader($header); + } + } while (100 == $this->_code); + + $this->_notify('gotHeaders', $this->_headers); + + // If response body is present, read it and decode + $chunked = isset($this->_headers['transfer-encoding']) && ('chunked' == $this->_headers['transfer-encoding']); + $gzipped = isset($this->_headers['content-encoding']) && ('gzip' == $this->_headers['content-encoding']); + $hasBody = false; + if (!isset($this->_headers['content-length']) || 0 != $this->_headers['content-length']) { + while (!$this->_sock->eof()) { + if ($chunked) { + $data = $this->_readChunked(); + } else { + $data = $this->_sock->read(4096); + } + if ('' == $data) { + break; + } else { + $hasBody = true; + if ($saveBody || $gzipped) { + $this->_body .= $data; + } + $this->_notify($gzipped? 'gzTick': 'tick', $data); + } + } + } + if ($hasBody) { + // Uncompress the body if needed + if ($gzipped) { + $this->_body = gzinflate(substr($this->_body, 10)); + $this->_notify('gotBody', $this->_body); + } else { + $this->_notify('gotBody'); + } + } + return true; + } + + + /** + * Processes the response header + * + * @access private + * @param string HTTP header + */ + function _processHeader($header) + { + list($headername, $headervalue) = explode(':', $header, 2); + $headername = strtolower($headername); + $headervalue = ltrim($headervalue); + + if ('set-cookie' != $headername) { + if (isset($this->_headers[$headername])) { + $this->_headers[$headername] .= ',' . $headervalue; + } else { + $this->_headers[$headername] = $headervalue; + } + } else { + $this->_parseCookie($headervalue); + } + } + + + /** + * Parse a Set-Cookie header to fill $_cookies array + * + * @access private + * @param string value of Set-Cookie header + */ + function _parseCookie($headervalue) + { + $cookie = array( + 'expires' => null, + 'domain' => null, + 'path' => null, + 'secure' => false + ); + + // Only a name=value pair + if (!strpos($headervalue, ';')) { + $pos = strpos($headervalue, '='); + $cookie['name'] = trim(substr($headervalue, 0, $pos)); + $cookie['value'] = trim(substr($headervalue, $pos + 1)); + + // Some optional parameters are supplied + } else { + $elements = explode(';', $headervalue); + $pos = strpos($elements[0], '='); + $cookie['name'] = trim(substr($elements[0], 0, $pos)); + $cookie['value'] = trim(substr($elements[0], $pos + 1)); + + for ($i = 1; $i < count($elements); $i++) { + if (false === strpos($elements[$i], '=')) { + $elName = trim($elements[$i]); + $elValue = null; + } else { + list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i])); + } + $elName = strtolower($elName); + if ('secure' == $elName) { + $cookie['secure'] = true; + } elseif ('expires' == $elName) { + $cookie['expires'] = str_replace('"', '', $elValue); + } elseif ('path' == $elName || 'domain' == $elName) { + $cookie[$elName] = urldecode($elValue); + } else { + $cookie[$elName] = $elValue; + } + } + } + $this->_cookies[] = $cookie; + } + + + /** + * Read a part of response body encoded with chunked Transfer-Encoding + * + * @access private + * @return string + */ + function _readChunked() + { + // at start of the next chunk? + if (0 == $this->_chunkLength) { + $line = $this->_sock->readLine(); + if (preg_match('/^([0-9a-f]+)/i', $line, $matches)) { + $this->_chunkLength = hexdec($matches[1]); + // Chunk with zero length indicates the end + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // make this an eof() + return ''; + } + } else { + return ''; + } + } + $data = $this->_sock->read($this->_chunkLength); + $this->_chunkLength -= strlen($data); + if (0 == $this->_chunkLength) { + $this->_sock->readLine(); // Trailing CRLF + } + return $data; + } + + + /** + * Notifies all registered listeners of an event. + * + * @param string Event name + * @param mixed Additional data + * @access private + * @see HTTP_Request::_notify() + */ + function _notify($event, $data = null) + { + foreach (array_keys($this->_listeners) as $id) { + $this->_listeners[$id]->update($this, $event, $data); + } + } +} // End class HTTP_Response +?> diff --git a/thirdparty/pear/HTTP/Request/Listener.php b/thirdparty/pear/HTTP/Request/Listener.php new file mode 100644 index 0000000..38b115a --- /dev/null +++ b/thirdparty/pear/HTTP/Request/Listener.php @@ -0,0 +1,96 @@ + | +// +-----------------------------------------------------------------------+ +// +// $Id: Listener.php,v 1.2 2003/10/26 10:28:29 avb Exp $ +// + +/** + * This class implements the Observer part of a Subject-Observer + * design pattern. It listens to the events sent by a + * HTTP_Request or HTTP_Response instance. + * + * @package HTTP_Request + * @author Alexey Borzov + * @version $Revision: 1.2 $ + */ +class HTTP_Request_Listener +{ + /** + * A listener's identifier + * @var string + */ + var $_id; + + /** + * Constructor, sets the object's identifier + * + * @access public + */ + function HTTP_Request_Listener() + { + $this->_id = md5(uniqid('http_request_', 1)); + } + + + /** + * Returns the listener's identifier + * + * @access public + * @return string + */ + function getId() + { + return $this->_id; + } + + + /** + * This method is called when Listener is notified of an event + * + * @access public + * @param object an object the listener is attached to + * @param string Event name + * @param mixed Additional data + * @abstract + */ + function update(&$subject, $event, $data = null) + { + echo "Notified of event: '$event'\n"; + if (null !== $data) { + echo "Additional data: "; + var_dump($data); + } + } +} +?> diff --git a/thirdparty/pear/HTTP/Upload.php b/thirdparty/pear/HTTP/Upload.php new file mode 100644 index 0000000..2737d39 --- /dev/null +++ b/thirdparty/pear/HTTP/Upload.php @@ -0,0 +1,856 @@ + http://www.gnu.org/copyleft/lesser.txt +// (c) 2001-2004 by Tomas Von Veschler Cox +// +// ********************************************** +// +// $Id: Upload.php,v 1.42 2004/08/08 09:37:50 wenz Exp $ + +/* + * Pear File Uploader class. Easy and secure managment of files + * submitted via HTML Forms. + * + * Leyend: + * - you can add error msgs in your language in the HTTP_Upload_Error class + * + * TODO: + * - try to think a way of having all the Error system in other + * file and only include it when an error ocurrs + * + * -- Notes for users HTTP_Upload >= 0.9.0 -- + * + * Error detection was enhanced, so you no longer need to + * check for PEAR::isError() in $upload->getFiles() or call + * $upload->isMissing(). Instead you'll + * get the error when do a check for $file->isError(). + * + * Example: + * + * $upload = new HTTP_Upload('en'); + * $file = $upload->getFiles('i_dont_exist_in_form_definition'); + * if ($file->isError()) { + * die($file->getMessage()); + * } + * + * -- + * + */ + +require_once 'PEAR.php'; + +/** + * defines default chmod + */ +define('HTTP_UPLOAD_DEFAULT_CHMOD', 0660); + +/** + * Error Class for HTTP_Upload + * + * @author Tomas V.V.Cox + * @see http://vulcanonet.com/soft/index.php?pack=uploader + * @package HTTP_Upload + * @category HTTP + * @access public + */ +class HTTP_Upload_Error extends PEAR +{ + /** + * Selected language for error messages + * @var string + */ + var $lang = 'en'; + + /** + * Whether HTML entities shall be encoded automatically + * @var boolean + */ + var $html = false; + + /** + * Constructor + * + * Creates a new PEAR_Error + * + * @param string $lang The language selected for error code messages + * @access public + */ + function HTTP_Upload_Error($lang = null, $html = false) + { + $this->lang = ($lang !== null) ? $lang : $this->lang; + $this->html = ($html !== false) ? $html : $this->html; + $ini_size = preg_replace('/m/i', '000000', ini_get('upload_max_filesize')); + + if (function_exists('version_compare') && + version_compare(phpversion(), '4.1', 'ge')) { + $maxsize = (isset($_POST['MAX_FILE_SIZE'])) ? + $_POST['MAX_FILE_SIZE'] : null; + } else { + global $HTTP_POST_VARS; + $maxsize = (isset($HTTP_POST_VARS['MAX_FILE_SIZE'])) ? + $HTTP_POST_VARS['MAX_FILE_SIZE'] : null; + } + + if (empty($maxsize) || ($maxsize > $ini_size)) { + $maxsize = $ini_size; + } + // XXXXX Add here error messages in your language + $this->error_codes = array( + 'TOO_LARGE' => array( + 'es' => "Fichero demasiado largo. El maximo permitido es: $maxsize bytes.", + 'en' => "File size too large. The maximum permitted size is: $maxsize bytes.", + 'de' => "Datei zu groß. Die zulässige Maximalgröße ist: $maxsize Bytes.", + 'nl' => "Het bestand is te groot, de maximale grootte is: $maxsize bytes.", + 'fr' => "Le fichier est trop gros. La taille maximum autorisée est: $maxsize bytes.", + 'it' => "Il file é troppo grande. Il massimo permesso é: $maxsize bytes.", + 'pt_BR' => "Arquivo muito grande. O tamanho máximo permitido é $maxsize bytes." + ), + 'MISSING_DIR' => array( + 'es' => 'Falta directorio destino.', + 'en' => 'Missing destination directory.', + 'de' => 'Kein Zielverzeichnis definiert.', + 'nl' => 'Geen bestemmings directory.', + 'fr' => 'Le répertoire de destination n\'est pas défini.', + 'it' => 'Manca la directory di destinazione.', + 'pt_BR' => 'Ausência de diretório de destino.' + ), + 'IS_NOT_DIR' => array( + 'es' => 'El directorio destino no existe o es un fichero regular.', + 'en' => 'The destination directory doesn\'t exist or is a regular file.', + 'de' => 'Das angebene Zielverzeichnis existiert nicht oder ist eine Datei.', + 'nl' => 'De doeldirectory bestaat niet, of is een gewoon bestand.', + 'fr' => 'Le répertoire de destination n\'existe pas ou il s\'agit d\'un fichier régulier.', + 'it' => 'La directory di destinazione non esiste o é un file.', + 'pt_BR' => 'O diretório de destino não existe ou é um arquivo.' + ), + 'NO_WRITE_PERMS' => array( + 'es' => 'El directorio destino no tiene permisos de escritura.', + 'en' => 'The destination directory doesn\'t have write perms.', + 'de' => 'Fehlende Schreibrechte für das Zielverzeichnis.', + 'nl' => 'Geen toestemming om te schrijven in de doeldirectory.', + 'fr' => 'Le répertoire de destination n\'a pas les droits en écriture.', + 'it' => 'Non si hanno i permessi di scrittura sulla directory di destinazione.', + 'pt_BR' => 'O diretório de destino não possui permissão para escrita.' + ), + 'NO_USER_FILE' => array( + 'es' => 'No se ha escogido fichero para el upload.', + 'en' => 'You haven\'t selected any file for uploading.', + 'de' => 'Es wurde keine Datei für den Upload ausgewählt.', + 'nl' => 'Er is geen bestand opgegeven om te uploaden.', + 'fr' => 'Vous n\'avez pas sélectionné de fichier à envoyer.', + 'it' => 'Nessun file selezionato per l\'upload.', + 'pt_BR' => 'Nenhum arquivo selecionado para upload.' + ), + 'BAD_FORM' => array( + 'es' => 'El formulario no contiene method="post" enctype="multipart/form-data" requerido.', + 'en' => 'The html form doesn\'t contain the required method="post" enctype="multipart/form-data".', + 'de' => 'Das HTML-Formular enthält nicht die Angabe method="post" enctype="multipart/form-data" '. + 'im >form<-Tag.', + 'nl' => 'Het HTML-formulier bevat niet de volgende benodigde '. + 'eigenschappen: method="post" enctype="multipart/form-data".', + 'fr' => 'Le formulaire HTML ne contient pas les attributs requis : '. + ' method="post" enctype="multipart/form-data".', + 'it' => 'Il modulo HTML non contiene gli attributi richiesti: "'. + ' method="post" enctype="multipart/form-data".', + 'pt_BR' => 'O formulário HTML não possui o method="post" enctype="multipart/form-data" requerido.' + ), + 'E_FAIL_COPY' => array( + 'es' => 'Fallo al copiar el fichero temporal.', + 'en' => 'Failed to copy the temporary file.', + 'de' => 'Temporäre Datei konnte nicht kopiert werden.', + 'nl' => 'Het tijdelijke bestand kon niet gekopieerd worden.', + 'fr' => 'L\'enregistrement du fichier temporaire a échoué.', + 'it' => 'Copia del file temporaneo fallita.', + 'pt_BR' => 'Falha ao copiar o arquivo temporário.' + ), + 'E_FAIL_MOVE' => array( + 'es' => 'No puedo mover el fichero.', + 'en' => 'Impossible to move the file.', + 'de' => 'Datei kann nicht verschoben werden.', + 'nl' => 'Het bestand kon niet verplaatst worden.', + 'fr' => 'Impossible de déplacer le fichier.', + 'pt_BR' => 'Não foi possível mover o arquivo.' + ), + 'FILE_EXISTS' => array( + 'es' => 'El fichero destino ya existe.', + 'en' => 'The destination file already exists.', + 'de' => 'Die zu erzeugende Datei existiert bereits.', + 'nl' => 'Het doelbestand bestaat al.', + 'fr' => 'Le fichier de destination existe déjà.', + 'it' => 'File destinazione già esistente.', + 'pt_BR' => 'O arquivo de destino já existe.' + ), + 'CANNOT_OVERWRITE' => array( + 'es' => 'El fichero destino ya existe y no se puede sobreescribir.', + 'en' => 'The destination file already exists and could not be overwritten.', + 'de' => 'Die zu erzeugende Datei existiert bereits und konnte nicht überschrieben werden.', + 'nl' => 'Het doelbestand bestaat al, en kon niet worden overschreven.', + 'fr' => 'Le fichier de destination existe déjà et ne peux pas être remplacé.', + 'it' => 'File destinazione già esistente e non si può sovrascrivere.', + 'pt_BR' => 'O arquivo de destino já existe e não pôde ser sobrescrito.' + ), + 'NOT_ALLOWED_EXTENSION' => array( + 'es' => 'Extension de fichero no permitida.', + 'en' => 'File extension not permitted.', + 'de' => 'Unerlaubte Dateiendung.', + 'nl' => 'Niet toegestane bestands-extensie.', + 'fr' => 'Le fichier a une extension non autorisée.', + 'it' => 'Estensione del File non permessa.', + 'pt_BR' => 'Extensão de arquivo não permitida.' + ), + 'PARTIAL' => array( + 'es' => 'El fichero fue parcialmente subido', + 'en' => 'The file was only partially uploaded.', + 'de' => 'Die Datei wurde unvollständig übertragen.', + 'nl' => 'Het bestand is slechts gedeeltelijk geupload.', + 'pt_BR' => 'O arquivo não foi enviado por completo.' + ), + 'ERROR' => array( + 'es' => 'Error en subida:', + 'en' => 'Upload error:', + 'de' => 'Fehler beim Upload:', + 'nl' => 'Upload fout:', + 'pt_BR' => 'Erro de upload:' + ), + 'DEV_NO_DEF_FILE' => array( + 'es' => 'No está definido en el formulario este nombre de fichero como <input type="file" name=?>.', + 'en' => 'This filename is not defined in the form as <input type="file" name=?>.', + 'de' => 'Dieser Dateiname ist im Formular nicht als <input type="file" name=?> definiert.', + 'nl' => 'Deze bestandsnaam is niett gedefineerd in het formulier als <input type="file" name=?>.' + ) + ); + } + + /** + * returns the error code + * + * @param string $e_code type of error + * @return string Error message + */ + function errorCode($e_code) + { + if (!empty($this->error_codes[$e_code][$this->lang])) { + $msg = $this->html ? + html_entity_decode($this->error_codes[$e_code][$this->lang]) : + $this->error_codes[$e_code][$this->lang]; + } else { + $msg = $e_code; + } + + if (!empty($this->error_codes['ERROR'][$this->lang])) { + $error = $this->error_codes['ERROR'][$this->lang]; + } else { + $error = $this->error_codes['ERROR']['en']; + } + return $error.' '.$msg; + } + + /** + * Overwrites the PEAR::raiseError method + * + * @param string $e_code type of error + * @return object PEAR_Error a PEAR-Error object + * @access public + */ + function raiseError($e_code) + { + return PEAR::raiseError($this->errorCode($e_code), $e_code); + } +} + +/** + * This class provides an advanced file uploader system + * for file uploads made from html forms + + * + * @author Tomas V.V.Cox + * @see http://vulcanonet.com/soft/index.php?pack=uploader + * @package HTTP_Upload + * @category HTTP + * @access public + */ +class HTTP_Upload extends HTTP_Upload_Error +{ + /** + * Contains an array of "uploaded files" objects + * @var array + */ + var $files = array(); + + /** + * Contains the desired chmod for uploaded files + * @var int + * @access private + */ + var $_chmod = HTTP_UPLOAD_DEFAULT_CHMOD; + + /** + * Constructor + * + * @param string $lang Language to use for reporting errors + * @see Upload_Error::error_codes + * @access public + */ + function HTTP_Upload($lang = null) + { + $this->HTTP_Upload_Error($lang); + if (function_exists('version_compare') && + version_compare(phpversion(), '4.1', 'ge')) + { + $this->post_files = $_FILES; + if (isset($_SERVER['CONTENT_TYPE'])) { + $this->content_type = $_SERVER['CONTENT_TYPE']; + } + } else { + global $HTTP_POST_FILES, $HTTP_SERVER_VARS; + $this->post_files = $HTTP_POST_FILES; + if (isset($HTTP_SERVER_VARS['CONTENT_TYPE'])) { + $this->content_type = $HTTP_SERVER_VARS['CONTENT_TYPE']; + } + } + } + + /** + * Get files + * + * @param mixed $file If: + * - not given, function will return array of upload_file objects + * - is int, will return the $file position in upload_file objects array + * - is string, will return the upload_file object corresponding + * to $file name of the form. For ex: + * if form is + * to get this file use: $upload->getFiles('userfile') + * + * @return mixed array or object (see @param $file above) or Pear_Error + * @access public + */ + function &getFiles($file = null) + { + static $is_built = false; + //build only once for multiple calls + if (!$is_built) { + $files = &$this->_buildFiles(); + if (PEAR::isError($files)) { + // there was an error with the form. + // Create a faked upload embedding the error + $this->files['_error'] = &new HTTP_Upload_File( + '_error', null, + null, null, + null, $files->getCode(), + $this->lang, $this->_chmod); + } else { + $this->files = $files; + } + $is_built = true; + } + if ($file !== null) { + if (is_int($file)) { + $pos = 0; + foreach ($this->files as $obj) { + if ($pos == $file) { + return $obj; + } + $pos++; + } + } elseif (is_string($file) && isset($this->files[$file])) { + return $this->files[$file]; + } + if (isset($this->files['_error'])) { + return $this->files['_error']; + } else { + // developer didn't specify this name in the form + // warn him about it with a faked upload + return new HTTP_Upload_File( + '_error', null, + null, null, + null, 'DEV_NO_DEF_FILE', + $this->lang); + } + } + return $this->files; + } + + /** + * Creates the list of the uploaded file + * + * @return array of HTTP_Upload_File objects for every file + */ + function &_buildFiles() + { + // Form method check + if (!isset($this->content_type) || + strpos($this->content_type, 'multipart/form-data') !== 0) + { + return $this->raiseError('BAD_FORM'); + } + // In 4.1 $_FILES isn't initialized when no uploads + // XXX (cox) afaik, in >= 4.1 and <= 4.3 only + if (function_exists('version_compare') && + version_compare(phpversion(), '4.1', 'ge')) + { + $error = $this->isMissing(); + if (PEAR::isError($error)) { + return $error; + } + } + + // map error codes from 4.2.0 $_FILES['userfile']['error'] + if (function_exists('version_compare') && + version_compare(phpversion(), '4.2.0', 'ge')) { + $uploadError = array( + 1 => 'TOO_LARGE', + 2 => 'TOO_LARGE', + 3 => 'PARTIAL', + 4 => 'NO_USER_FILE' + ); + } + + + // Parse $_FILES (or $HTTP_POST_FILES) + $files = array(); + foreach ($this->post_files as $userfile => $value) { + if (is_array($value['name'])) { + foreach ($value['name'] as $key => $val) { + $err = $value['error'][$key]; + if (isset($err) && $err !== 0 && isset($uploadError[$err])) { + $error = $uploadError[$err]; + } else { + $error = null; + } + $name = basename($value['name'][$key]); + $tmp_name = $value['tmp_name'][$key]; + $size = $value['size'][$key]; + $type = $value['type'][$key]; + $formname = $userfile . "[$key]"; + $files[$formname] = new HTTP_Upload_File($name, $tmp_name, + $formname, $type, $size, $error, $this->lang, $this->_chmod); + } + // One file + } else { + $err = $value['error']; + if (isset($err) && $err !== 0 && isset($uploadError[$err])) { + $error = $uploadError[$err]; + } else { + $error = null; + } + $name = basename($value['name']); + $tmp_name = $value['tmp_name']; + $size = $value['size']; + $type = $value['type']; + $formname = $userfile; + $files[$formname] = new HTTP_Upload_File($name, $tmp_name, + $formname, $type, $size, $error, $this->lang, $this->_chmod); + } + } + return $files; + } + + /** + * Checks if the user submited or not some file + * + * @return mixed False when are files or PEAR_Error when no files + * @access public + * @see Read the note in the source code about this function + */ + function isMissing() + { + if (count($this->post_files) < 1) { + return $this->raiseError('NO_USER_FILE'); + } + //we also check if at least one file has more than 0 bytes :) + $files = array(); + $size = 0; + foreach ($this->post_files as $userfile => $value) { + if (is_array($value['name'])) { + foreach ($value['name'] as $key => $val) { + $size += $value['size'][$key]; + } + } else { //one file + $size = $value['size']; + } + } + if ($size == 0) { + $this->raiseError('NO_USER_FILE'); + } + return false; + } + + /** + * Sets the chmod to be used for uploaded files + * + * @param int Desired mode + */ + function setChmod($mode) + { + $this->_chmod = $mode; + } +} + +/** + * This class provides functions to work with the uploaded file + * + * @author Tomas V.V.Cox + * @see http://vulcanonet.com/soft/index.php?pack=uploader + * @package HTTP_Upload + * @category HTTP + * @access public + */ +class HTTP_Upload_File extends HTTP_Upload_Error +{ + /** + * If the random seed was initialized before or not + * @var boolean; + */ + var $_seeded = 0; + + /** + * Assoc array with file properties + * @var array + */ + var $upload = array(); + + /** + * If user haven't selected a mode, by default 'safe' will be used + * @var boolean + */ + var $mode_name_selected = false; + + /** + * It's a common security risk in pages who has the upload dir + * under the document root (remember the hack of the Apache web?) + * + * @var array + * @access private + * @see HTTP_Upload_File::setValidExtensions() + */ + var $_extensions_check = array('php', 'phtm', 'phtml', 'php3', 'inc'); + + /** + * @see HTTP_Upload_File::setValidExtensions() + * @var string + * @access private + */ + var $_extensions_mode = 'deny'; + + /** + * Contains the desired chmod for uploaded files + * @var int + * @access private + */ + var $_chmod = HTTP_UPLOAD_DEFAULT_CHMOD; + + /** + * Constructor + * + * @param string $name destination file name + * @param string $tmp temp file name + * @param string $formname name of the form + * @param string $type Mime type of the file + * @param string $size size of the file + * @param string $error error on upload + * @param string $lang used language for errormessages + * @access public + */ + function HTTP_Upload_File($name = null, $tmp = null, $formname = null, + $type = null, $size = null, $error = null, + $lang = null, $chmod = HTTP_UPLOAD_DEFAULT_CHMOD) + { + $this->HTTP_Upload_Error($lang); + $ext = null; + + if (empty($name) || $size == 0) { + $error = 'NO_USER_FILE'; + } elseif ($tmp == 'none') { + $error = 'TOO_LARGE'; + } else { + // strpos needed to detect files without extension + if (($pos = strrpos($name, '.')) !== false) { + $ext = substr($name, $pos + 1); + } + } + + if (function_exists('version_compare') && + version_compare(phpversion(), '4.1', 'ge')) { + if (isset($_POST['MAX_FILE_SIZE']) && + $size > $_POST['MAX_FILE_SIZE']) { + $error = 'TOO_LARGE'; + } + } else { + global $HTTP_POST_VARS; + if (isset($HTTP_POST_VARS['MAX_FILE_SIZE']) && + $size > $HTTP_POST_VARS['MAX_FILE_SIZE']) { + $error = 'TOO_LARGE'; + } + } + + $this->upload = array( + 'real' => $name, + 'name' => $name, + 'form_name' => $formname, + 'ext' => $ext, + 'tmp_name' => $tmp, + 'size' => $size, + 'type' => $type, + 'error' => $error + ); + + $this->_chmod = $chmod; + } + + /** + * Sets the name of the destination file + * + * @param string $mode A valid mode: 'uniq', 'safe' or 'real' or a file name + * @param string $prepend A string to prepend to the name + * @param string $append A string to append to the name + * + * @return string The modified name of the destination file + * @access public + */ + function setName($mode, $prepend = null, $append = null) + { + switch ($mode) { + case 'uniq': + $name = $this->nameToUniq(); + $this->upload['ext'] = $this->nameToSafe($this->upload['ext'], 10); + $name .= '.' . $this->upload['ext']; + break; + case 'safe': + $name = $this->nameToSafe($this->upload['real']); + if (($pos = strrpos($name, '.')) !== false) { + $this->upload['ext'] = substr($name, $pos + 1); + } else { + $this->upload['ext'] = ''; + } + break; + case 'real': + $name = $this->upload['real']; + break; + default: + $name = $mode; + } + $this->upload['name'] = $prepend . $name . $append; + $this->mode_name_selected = true; + return $this->upload['name']; + } + + /** + * Unique file names in the form: 9022210413b75410c28bef.html + * @see HTTP_Upload_File::setName() + */ + function nameToUniq() + { + if (! $this->_seeded) { + srand((double) microtime() * 1000000); + $this->_seeded = 1; + } + $uniq = uniqid(rand()); + return $uniq; + } + + /** + * Format a file name to be safe + * + * @param string $file The string file name + * @param int $maxlen Maximun permited string lenght + * @return string Formatted file name + * @see HTTP_Upload_File::setName() + */ + function nameToSafe($name, $maxlen=250) + { + $noalpha = 'ÁÉÍÓÚÝáéíóúýÂÊÎÔÛâêîôûÀÈÌÒÙàèìòùÄËÏÖÜäëïöüÿÃãÕõÅåÑñÇç@°ºª'; + $alpha = 'AEIOUYaeiouyAEIOUaeiouAEIOUaeiouAEIOUaeiouyAaOoAaNnCcaooa'; + + $name = substr($name, 0, $maxlen); + $name = strtr($name, $noalpha, $alpha); + // not permitted chars are replaced with "_" + return preg_replace('/[^a-zA-Z0-9,._\+\()\-]/', '_', $name); + } + + /** + * The upload was valid + * + * @return bool If the file was submitted correctly + * @access public + */ + function isValid() + { + if ($this->upload['error'] === null) { + return true; + } + return false; + } + + /** + * User haven't submit a file + * + * @return bool If the user submitted a file or not + * @access public + */ + function isMissing() + { + if ($this->upload['error'] == 'NO_USER_FILE') { + return true; + } + return false; + } + + /** + * Some error occured during upload (most common due a file size problem, + * like max size exceeded or 0 bytes long). + * @return bool If there were errors submitting the file (probably + * because the file excess the max permitted file size) + * @access public + */ + function isError() + { + if (in_array($this->upload['error'], array('TOO_LARGE', 'BAD_FORM','DEV_NO_DEF_FILE'))) { + return true; + } + return false; + } + + /** + * Moves the uploaded file to its destination directory. + * + * @param string $dir_dest Destination directory + * @param bool $overwrite Overwrite if destination file exists? + * @return mixed True on success or Pear_Error object on error + * @access public + */ + function moveTo($dir_dest, $overwrite = true) + { + if (!$this->isValid()) { + return $this->raiseError($this->upload['error']); + } + + //Valid extensions check + if (!$this->_evalValidExtensions()) { + return $this->raiseError('NOT_ALLOWED_EXTENSION'); + } + + $err_code = $this->_chk_dir_dest($dir_dest); + if ($err_code !== false) { + return $this->raiseError($err_code); + } + // Use 'safe' mode by default if no other was selected + if (!$this->mode_name_selected) { + $this->setName('safe'); + } + + $name_dest = $dir_dest . DIRECTORY_SEPARATOR . $this->upload['name']; + + if (@is_file($name_dest)) { + if ($overwrite !== true) { + return $this->raiseError('FILE_EXISTS'); + } elseif (!is_writable($name_dest)) { + return $this->raiseError('CANNOT_OVERWRITE'); + } + } + + // copy the file and let php clean the tmp + if (!@move_uploaded_file($this->upload['tmp_name'], $name_dest)) { + return $this->raiseError('E_FAIL_MOVE'); + } + @chmod($name_dest, $this->_chmod); + return $this->getProp('name'); + } + + /** + * Check for a valid destination dir + * + * @param string $dir_dest Destination dir + * @return mixed False on no errors or error code on error + */ + function _chk_dir_dest($dir_dest) + { + if (!$dir_dest) { + return 'MISSING_DIR'; + } + if (!@is_dir ($dir_dest)) { + return 'IS_NOT_DIR'; + } + if (!is_writeable ($dir_dest)) { + return 'NO_WRITE_PERMS'; + } + return false; + } + /** + * Retrive properties of the uploaded file + * @param string $name The property name. When null an assoc array with + * all the properties will be returned + * @return mixed A string or array + * @see HTTP_Upload_File::HTTP_Upload_File() + * @access public + */ + function getProp($name = null) + { + if ($name === null) { + return $this->upload; + } + return $this->upload[$name]; + } + + /** + * Returns a error message, if a error occured + * (deprecated) Use getMessage() instead + * @return string a Error message + * @access public + */ + function errorMsg() + { + return $this->errorCode($this->upload['error']); + } + + /** + * Returns a error message, if a error occured + * @return string a Error message + * @access public + */ + function getMessage() + { + return $this->errorCode($this->upload['error']); + } + + /** + * Function to restrict the valid extensions on file uploads + * + * @param array $exts File extensions to validate + * @param string $mode The type of validation: + * 1) 'deny' Will deny only the supplied extensions + * 2) 'accept' Will accept only the supplied extensions + * as valid + * @access public + */ + function setValidExtensions($exts, $mode = 'deny') + { + $this->_extensions_check = $exts; + $this->_extensions_mode = $mode; + } + + /** + * Evaluates the validity of the extensions set by setValidExtensions + * + * @return bool False on non valid extension, true if they are valid + * @access private + */ + function _evalValidExtensions() + { + $exts = $this->_extensions_check; + settype($exts, 'array'); + if ($this->_extensions_mode == 'deny') { + if (in_array($this->getProp('ext'), $exts)) { + return false; + } + // mode == 'accept' + } else { + if (!in_array($this->getProp('ext'), $exts)) { + return false; + } + } + return true; + } +} +?> \ No newline at end of file