<?php /** * THttpRequest, THttpCookie, THttpCookieCollection, TUri class file * * @author Qiang Xue <qiang.xue@gmail.com> * @link http://www.pradosoft.com/ * @copyright Copyright © 2005 PradoSoft * @license http://www.pradosoft.com/license/ * @version $Id$ * @package System.Web */ /** * THttpRequest class * * THttpRequest provides storage and access scheme for user request sent via HTTP. * It also encapsulates a uniform way to parse and construct URLs. * * User post data can be retrieved from THttpRequest by using it like an associative array. * For example, to test if a user supplies a variable named 'param1', you can use, * <code> * if(isset($request['param1'])) ... * // equivalent to: * // if($request->contains('param1')) ... * </code> * To get the value of 'param1', use, * <code> * $value=$request['param1']; * // equivalent to: * // $value=$request->itemAt('param1'); * </code> * To traverse the user post data, use * <code> * foreach($request as $name=>$value) ... * </code> * Note, POST and GET variables are merged together in THttpRequest. * If a variable name appears in both POST and GET data, then POST data * takes precedence. * * To construct a URL that can be recognized by Prado, use {@link constructUrl()}. * THttpRequest also provides the cookies sent by the user, user information such * as his browser capabilities, accepted languages, etc. * Currently, THttpRequest recognizes the following URL format: * <code> * /index.php?ServiceID=ServiceParameter * </code> * where ServiceID is as defined in the application configuration (e.g. * the default page service's service ID is 'page'). * Therefore, your GET variable names should not conflict with the service * IDs that your application supports. * * By default, THttpRequest is registered with {@link TApplication} as the * request module. It can be accessed via {@link TApplication::getRequest()}. * * @author Qiang Xue <qiang.xue@gmail.com> * @version $Id$ * @package System.Web * @since 3.0 */ class THttpRequest extends TApplicationComponent implements IteratorAggregate,ArrayAccess,Countable,IModule { /** * Separator used to separate GET variable name and value when URL format is Path. */ private $_separator=','; /** * @var boolean whether the module is initialized */ private $_initialized=false; /** * @var string requested service ID */ private $_serviceID=null; /** * @var string requested service parameter */ private $_serviceParam=null; /** * @var THttpCookieCollection cookies sent from user */ private $_cookies=null; /** * @var string requested URI (URL w/o host info) */ private $_requestUri; /** * @var string path info of URL */ private $_pathInfo; private $_urlFormat=THttpRequestUrlFormat::Get; private $_services; private $_requestResolved=false; private $_enableCookieValidation=false; /** * @var string request URL */ private $_url=null; /** * @var string module id */ private $_id; /** * @var array contains all request variables */ private $_items=array(); /** * @return string id of this module */ public function getID() { return $this->_id; } /** * @param string id of this module */ public function setID($value) { $this->_id=$value; } /** * Initializes the module. * This method is required by IModule and is invoked by application. * @param TXmlElement module configuration */ public function init($config) { // Fill in default request info when the script is run in command line if(php_sapi_name()==='cli') { $_SERVER['REMOTE_ADDR']='127.0.0.1'; $_SERVER['REQUEST_METHOD']='GET'; $_SERVER['SERVER_NAME']='localhost'; $_SERVER['SERVER_PORT']=80; $_SERVER['HTTP_USER_AGENT']=''; } // Info about server variables: // PHP_SELF contains real URI (w/ path info, w/o query string) // SCRIPT_NAME is the real URI for the requested script (w/o path info and query string) // QUERY_STRING is the string following the '?' in the ur (eg the a=x part in http://foo/bar?a=x) // REQUEST_URI contains the URI part entered in the browser address bar // SCRIPT_FILENAME is the file path to the executing script if(isset($_SERVER['REQUEST_URI'])) $this->_requestUri=$_SERVER['REQUEST_URI']; else // TBD: in this case, SCRIPT_NAME need to be escaped $this->_requestUri=$_SERVER['SCRIPT_NAME'].(empty($_SERVER['QUERY_STRING'])?'':'?'.$_SERVER['QUERY_STRING']); if(isset($_SERVER['PATH_INFO'])) $this->_pathInfo=$_SERVER['PATH_INFO']; else if(strpos($_SERVER['PHP_SELF'],$_SERVER['SCRIPT_NAME'])===0) $this->_pathInfo=substr($_SERVER['PHP_SELF'],strlen($_SERVER['SCRIPT_NAME'])); else $this->_pathInfo=''; if(get_magic_quotes_gpc()) { if(isset($_GET)) $_GET=$this->stripSlashes($_GET); if(isset($_POST)) $_POST=$this->stripSlashes($_POST); if(isset($_REQUEST)) $_REQUEST=$this->stripSlashes($_REQUEST); if(isset($_COOKIE)) $_COOKIE=$this->stripSlashes($_COOKIE); } if($this->getUrlFormat()===THttpRequestUrlFormat::Path && ($pathInfo=trim($this->_pathInfo,'/'))!=='') $this->_items=array_merge($this->parseUrl(),$_POST); else $this->_items=array_merge($_GET,$_POST); $this->_initialized=true; $this->getApplication()->setRequest($this); } /** * Strips slashes from input data. * This method is applied when magic quotes is enabled. * @param mixed input data to be processed * @param mixed processed data */ public function stripSlashes(&$data) { return is_array($data)?array_map(array($this,'stripSlashes'),$data):stripslashes($data); } /** * @return TUri the request URL */ public function getUrl() { if($this->_url===null) { $secure=$this->getIsSecureConnection(); $url=$secure?'https://':'http://'; if(empty($_SERVER['HTTP_HOST'])) { $url.=$_SERVER['SERVER_NAME']; $port=$_SERVER['SERVER_PORT']; if(($port!=80 && !$secure) || ($port!=443 && $secure)) $url.=':'.$port; } else $url.=$_SERVER['HTTP_HOST']; $url.=$this->getRequestUri(); $this->_url=new TUri($url); } return $this->_url; } /** * @return THttpRequestUrlFormat the format of URLs. Defaults to THttpRequestUrlFormat::Get. */ public function getUrlFormat() { return $this->_urlFormat; } /** * Sets the format of URLs constructed and interpretted by the request module. * A Get URL format is like index.php?name1=value1&name2=value2 * while a Path URL format is like index.php/name1,value1/name2,value. * Changing the UrlFormat will affect {@link constructUrl} and how GET variables * are parsed. * @param THttpRequestUrlFormat the format of URLs. */ public function setUrlFormat($value) { $this->_urlFormat=TPropertyValue::ensureEnum($value,'THttpRequestUrlFormat'); } /** * @return string separator used to separate GET variable name and value when URL format is Path. Defaults to comma ','. */ public function getUrlParamSeparator() { return $this->_separator; } /** * @param string separator used to separate GET variable name and value when URL format is Path. * @throws TInvalidDataValueException if the separator is not a single character */ public function setUrlParamSeparator($value) { if(strlen($value)===1) $this->_separator=$value; else throw new TInvalidDataValueException('httprequest_separator_invalid'); } /** * @return string request type, can be GET, POST, HEAD, or PUT */ public function getRequestType() { return $_SERVER['REQUEST_METHOD']; } /** * @return boolean if the request is sent via secure channel (https) */ public function getIsSecureConnection() { return isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'],'off'); } /** * @return string part of the request URL after script name and before question mark. */ public function getPathInfo() { return $this->_pathInfo; } /** * @return string part of that request URL after the question mark */ public function getQueryString() { return isset($_SERVER['QUERY_STRING'])?$_SERVER['QUERY_STRING']:''; } /** * @return string part of that request URL after the host info (including pathinfo and query string) */ public function getRequestUri() { return $this->_requestUri; } /** * @return string schema and hostname of the requested URL */ public function getBaseUrl() { return ($this->getIsSecureConnection() ? "https://" : "http://") . $_SERVER ['HTTP_HOST']; } /** * @return string entry script URL (w/o host part) */ public function getApplicationUrl() { return $_SERVER['SCRIPT_NAME']; } /** * @return string entry script URL (w/ host part) */ public function getAbsoluteApplicationUrl() { return $this->getBaseUrl() . $this->getApplicationUrl(); } /** * @return string application entry script file path (processed w/ realpath()) */ public function getApplicationFilePath() { return realpath($_SERVER['SCRIPT_FILENAME']); } /** * @return string server name */ public function getServerName() { return $_SERVER['SERVER_NAME']; } /** * @return integer server port number */ public function getServerPort() { return $_SERVER['SERVER_PORT']; } /** * @return string URL referrer, null if not present */ public function getUrlReferrer() { return isset($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:null; } /** * @return array user browser capabilities * @see get_browser */ public function getBrowser() { return get_browser(); } /** * @return string user agent */ public function getUserAgent() { return $_SERVER['HTTP_USER_AGENT']; } /** * @return string user IP address */ public function getUserHostAddress() { return $_SERVER['REMOTE_ADDR']; } /** * @return string user host name, null if cannot be determined */ public function getUserHost() { return isset($_SERVER['REMOTE_HOST'])?$_SERVER['REMOTE_HOST']:null; } /** * @return string user browser accept types */ public function getAcceptTypes() { // TBD: break it into array?? return $_SERVER['HTTP_ACCEPT']; } /** * Returns a list of user preferred languages. * The languages are returned as an array. Each array element * represents a single language preference. The languages are ordered * according to user preferences. The first language is the most preferred. * @return array list of user preferred languages. */ public function getUserLanguages() { return Prado::getUserLanguages(); } /** * @return boolean whether cookies should be validated. Defaults to false. */ public function getEnableCookieValidation() { return $this->_enableCookieValidation; } /** * @param boolean whether cookies should be validated. */ public function setEnableCookieValidation($value) { $this->_enableCookieValidation=TPropertyValue::ensureBoolean($value); } /** * @return THttpCookieCollection list of cookies to be sent */ public function getCookies() { if($this->_cookies===null) { $this->_cookies=new THttpCookieCollection; if($this->getEnableCookieValidation()) { $sm=$this->getApplication()->getSecurityManager(); foreach($_COOKIE as $key=>$value) { if(($value=$sm->validateData($value))!==false) $this->_cookies->add(new THttpCookie($key,$value)); } } else { foreach($_COOKIE as $key=>$value) $this->_cookies->add(new THttpCookie($key,$value)); } } return $this->_cookies; } /** * @return array list of uploaded files. */ public function getUploadedFiles() { return $_FILES; } /** * @return array list of server variables. */ public function getServerVariables() { return $_SERVER; } /** * @return array list of environment variables. */ public function getEnvironmentVariables() { return $_ENV; } /** * Constructs a URL that is recognizable by Prado. * You may override this method to provide your own way of URL formatting. * If you do so, you may also need to override {@link parseUrl} so that the URL can be properly parsed. * The URL is constructed as the following format: * /entryscript.php?serviceID=serviceParameter&get1=value1&... * If {@link setUrlFormat UrlFormat} is Path, the following format is used instead: * /entryscript.php/serviceID/serviceParameter/get1,value1/get2,value2... * @param string service ID * @param string service parameter * @param array GET parameters, null if not needed * @param boolean whether to encode the ampersand in URL, defaults to false. * @param boolean whether to encode the GET parameters (their names and values), defaults to true. * @return string URL * @see parseUrl */ public function constructUrl($serviceID,$serviceParam,$getItems=null,$encodeAmpersand=false,$encodeGetItems=true) { $url=$serviceID.'='.$serviceParam; $amp=$encodeAmpersand?'&':'&'; if(is_array($getItems) || $getItems instanceof Traversable) { if($encodeGetItems) { foreach($getItems as $name=>$value) { if(is_array($value)) { $name=urlencode($name.'[]'); foreach($value as $v) $url.=$amp.$name.'='.urlencode($v); } else $url.=$amp.urlencode($name).'='.urlencode($value); } } else { foreach($getItems as $name=>$value) { if(is_array($value)) { foreach($value as $v) $url.=$amp.$name.'[]='.$v; } else $url.=$amp.$name.'='.$value; } } } if($this->getUrlFormat()===THttpRequestUrlFormat::Path) { $url=strtr($url,array($amp=>'/','?'=>'/','='=>$this->_separator)); if(defined('SID') && SID != '' && !((int)ini_get('session.use_cookies')===1 && ((int)ini_get('session.use_only_cookies')===1))) $url.='?'.SID; return $this->getApplicationUrl().'/'.$url; } else { if(defined('SID') && SID != '' && !((int)ini_get('session.use_cookies')===1 && ((int)ini_get('session.use_only_cookies')===1))) $url.=$amp.SID; return $this->getApplicationUrl().'?'.$url; } } /** * Parses the request URL and returns an array of input parameters (including GET variables). * This method is invoked when the URL format is Path. * You may override this method to support customized URL format. * @return array list of input parameters, indexed by parameter names * @see constructUrl */ protected function parseUrl() { if($this->_pathInfo!=='') { $paths=explode('/',$this->_pathInfo); $getVariables=$_GET; $serviceID=null; foreach($paths as $path) { if(($path=trim($path))!=='') { if(($pos=strpos($path,$this->_separator))!==false) { $name=substr($path,0,$pos); $value=substr($path,$pos+1); if(($pos=strpos($name,'[]'))!==false) $getVariables[substr($name,0,$pos)][]=$value; else $getVariables[$name]=$value; } else $getVariables[$path]=''; } } return $getVariables; } else return $_GET; } /** * Resolves the requested servie. * This method implements a URL-based service resolution. * A URL in the format of /index.php?sp=serviceID.serviceParameter * will be resolved with the serviceID and the serviceParameter. * You may override this method to provide your own way of service resolution. * @see constructUrl */ protected function resolveRequest() { Prado::trace("Resolving request from ".$_SERVER['REMOTE_ADDR'],'System.Web.THttpRequest'); $this->_requestResolved=true; foreach($this->_services as $id) { if($this->contains($id)) { $this->setServiceID($id); $this->setServiceParameter($this->itemAt($id)); break; } } } protected function getRequestResolved() { return $this->_requestResolved; } /** * @return array IDs of the available services */ public function getAvailableServices() { return $this->_services; } /** * @param array IDs of the available services */ public function setAvailableServices($services) { $this->_services=$services; } /** * @return string requested service ID */ public function getServiceID() { if(!$this->_requestResolved) $this->resolveRequest(); return $this->_serviceID; } /** * Sets the requested service ID. * @param string requested service ID */ protected function setServiceID($value) { $this->_serviceID=$value; } /** * @return string requested service parameter */ public function getServiceParameter() { if(!$this->_requestResolved) $this->resolveRequest(); return $this->_serviceParam; } /** * Sets the requested service parameter. * @param string requested service parameter */ protected function setServiceParameter($value) { $this->_serviceParam=$value; } //------ The following methods enable THttpRequest to be TMap-like ----- /** * Returns an iterator for traversing the items in the list. * This method is required by the interface IteratorAggregate. * @return Iterator an iterator for traversing the items in the list. */ public function getIterator() { return new TMapIterator($this->_items); } /** * @return integer the number of items in the request */ public function getCount() { return count($this->_items); } /** * Returns the number of items in the request. * This method is required by Countable interface. * @return integer number of items in the request. */ public function count() { return $this->getCount(); } /** * @return array the key list */ public function getKeys() { return array_keys($this->_items); } /** * Returns the item with the specified key. * This method is exactly the same as {@link offsetGet}. * @param mixed the key * @return mixed the element at the offset, null if no element is found at the offset */ public function itemAt($key) { return isset($this->_items[$key]) ? $this->_items[$key] : null; } /** * Adds an item into the request. * Note, if the specified key already exists, the old value will be overwritten. * @param mixed key * @param mixed value */ public function add($key,$value) { $this->_items[$key]=$value; } /** * Removes an item from the request by its key. * @param mixed the key of the item to be removed * @return mixed the removed value, null if no such key exists. * @throws TInvalidOperationException if the item cannot be removed */ public function remove($key) { if(isset($this->_items[$key]) || array_key_exists($key,$this->_items)) { $value=$this->_items[$key]; unset($this->_items[$key]); return $value; } else return null; } /** * Removes all items in the request. */ public function clear() { foreach(array_keys($this->_items) as $key) $this->remove($key); } /** * @param mixed the key * @return boolean whether the request contains an item with the specified key */ public function contains($key) { return isset($this->_items[$key]) || array_key_exists($key,$this->_items); } /** * @return array the list of items in array */ public function toArray() { return $this->_items; } /** * Returns whether there is an element at the specified offset. * This method is required by the interface ArrayAccess. * @param mixed the offset to check on * @return boolean */ public function offsetExists($offset) { return $this->contains($offset); } /** * Returns the element at the specified offset. * This method is required by the interface ArrayAccess. * @param integer the offset to retrieve element. * @return mixed the element at the offset, null if no element is found at the offset */ public function offsetGet($offset) { return $this->itemAt($offset); } /** * Sets the element at the specified offset. * This method is required by the interface ArrayAccess. * @param integer the offset to set element * @param mixed the element value */ public function offsetSet($offset,$item) { $this->add($offset,$item); } /** * Unsets the element at the specified offset. * This method is required by the interface ArrayAccess. * @param mixed the offset to unset element */ public function offsetUnset($offset) { $this->remove($offset); } } /** * THttpCookieCollection class. * * THttpCookieCollection implements a collection class to store cookies. * Besides using all functionalities from {@link TList}, you can also * retrieve a cookie by its name using either {@link findCookieByName} or * simply: * <code> * $cookie=$collection[$cookieName]; * </code> * * @author Qiang Xue <qiang.xue@gmail.com> * @version $Id$ * @package System.Web * @since 3.0 */ class THttpCookieCollection extends TList { /** * @var mixed owner of this collection */ private $_o; /** * Constructor. * @param mixed owner of this collection. */ public function __construct($owner=null) { $this->_o=$owner; } /** * Inserts an item at the specified position. * This overrides the parent implementation by performing additional * operations for each newly added THttpCookie object. * @param integer the speicified position. * @param mixed new item * @throws TInvalidDataTypeException if the item to be inserted is not a THttpCookie object. */ public function insertAt($index,$item) { if($item instanceof THttpCookie) { parent::insertAt($index,$item); if($this->_o instanceof THttpResponse) $this->_o->addCookie($item); } else throw new TInvalidDataTypeException('authorizationrulecollection_authorizationrule_required'); } /** * Removes an item at the specified position. * This overrides the parent implementation by performing additional * cleanup work when removing a TCookie object. * @param integer the index of the item to be removed. * @return mixed the removed item. */ public function removeAt($index) { $item=parent::removeAt($index); if($this->_o instanceof THttpResponse) $this->_o->removeCookie($item); return $item; } /** * @param integer|string index of the cookie in the collection or the cookie's name * @return THttpCookie the cookie found */ public function itemAt($index) { if(is_integer($index)) return parent::itemAt($index); else return $this->findCookieByName($index); } /** * Finds the cookie with the specified name. * @param string the name of the cookie to be looked for * @return THttpCookie the cookie, null if not found */ public function findCookieByName($name) { foreach($this as $cookie) if($cookie->getName()===$name) return $cookie; return null; } } /** * THttpCookie class. * * A THttpCookie instance stores a single cookie, including the cookie name, value, * domain, path, expire, and secure. * * @author Qiang Xue <qiang.xue@gmail.com> * @version $Id$ * @package System.Web * @since 3.0 */ class THttpCookie extends TComponent { /** * @var string domain of the cookie */ private $_domain=''; /** * @var string name of the cookie */ private $_name; /** * @var string value of the cookie */ private $_value=0; /** * @var integer expire of the cookie */ private $_expire=0; /** * @var string path of the cookie */ private $_path='/'; /** * @var boolean whether cookie should be sent via secure connection */ private $_secure=false; /** * Constructor. * @param string name of this cookie * @param string value of this cookie */ public function __construct($name,$value) { $this->_name=$name; $this->_value=$value; } /** * @return string the domain to associate the cookie with */ public function getDomain() { return $this->_domain; } /** * @param string the domain to associate the cookie with */ public function setDomain($value) { $this->_domain=$value; } /** * @return integer the time the cookie expires. This is a Unix timestamp so is in number of seconds since the epoch. */ public function getExpire() { return $this->_expire; } /** * @param integer the time the cookie expires. This is a Unix timestamp so is in number of seconds since the epoch. */ public function setExpire($value) { $this->_expire=TPropertyValue::ensureInteger($value); } /** * @return string the name of the cookie */ public function getName() { return $this->_name; } /** * @param string the name of the cookie */ public function setName($value) { $this->_name=$value; } /** * @return string the value of the cookie */ public function getValue() { return $this->_value; } /** * @param string the value of the cookie */ public function setValue($value) { $this->_value=$value; } /** * @return string the path on the server in which the cookie will be available on, default is '/' */ public function getPath() { return $this->_path; } /** * @param string the path on the server in which the cookie will be available on */ public function setPath($value) { $this->_path=$value; } /** * @return boolean whether the cookie should only be transmitted over a secure HTTPS connection */ public function getSecure() { return $this->_secure; } /** * @param boolean ether the cookie should only be transmitted over a secure HTTPS connection */ public function setSecure($value) { $this->_secure=TPropertyValue::ensureBoolean($value); } } /** * TUri class * * TUri represents a URI. Given a URI * http://joe:whatever@example.com:8080/path/to/script.php?param=value#anchor * it will be decomposed as follows, * - scheme: http * - host: example.com * - port: 8080 * - user: joe * - password: whatever * - path: /path/to/script.php * - query: param=value * - fragment: anchor * * @author Qiang Xue <qiang.xue@gmail.com> * @version $Id$ * @package System.Web * @since 3.0 */ class TUri extends TComponent { /** * @var array list of default ports for known schemes */ private static $_defaultPort=array( 'ftp'=>21, 'gopher'=>70, 'http'=>80, 'https'=>443, 'news'=>119, 'nntp'=>119, 'wais'=>210, 'telnet'=>23 ); /** * @var string scheme of the URI */ private $_scheme; /** * @var string host name of the URI */ private $_host; /** * @var integer port of the URI */ private $_port; /** * @var string user of the URI */ private $_user; /** * @var string password of the URI */ private $_pass; /** * @var string path of the URI */ private $_path; /** * @var string query string of the URI */ private $_query; /** * @var string fragment of the URI */ private $_fragment; /** * @var string the URI */ private $_uri; /** * Constructor. * Decomposes the specified URI into parts. * @param string URI to be represented * @throws TInvalidDataValueException if URI is of bad format */ public function __construct($uri) { if(($ret=@parse_url($uri))!==false) { // decoding??? $this->_scheme=isset($ret['scheme'])?$ret['scheme']:''; $this->_host=isset($ret['host'])?$ret['host']:''; $this->_port=isset($ret['port'])?$ret['port']:''; $this->_user=isset($ret['user'])?$ret['user']:''; $this->_pass=isset($ret['pass'])?$ret['pass']:''; $this->_path=isset($ret['path'])?$ret['path']:''; $this->_query=isset($ret['query'])?$ret['query']:''; $this->_fragment=isset($ret['fragment'])?$ret['fragment']:''; $this->_uri=$uri; } else { throw new TInvalidDataValueException('uri_format_invalid',$uri); } } /** * @return string URI */ public function getUri() { return $this->_uri; } /** * @return string scheme of the URI, such as 'http', 'https', 'ftp', etc. */ public function getScheme() { return $this->_scheme; } /** * @return string hostname of the URI */ public function getHost() { return $this->_host; } /** * @return integer port number of the URI */ public function getPort() { return $this->_port; } /** * @return string username of the URI */ public function getUser() { return $this->_user; } /** * @return string password of the URI */ public function getPassword() { return $this->_pass; } /** * @return string path of the URI */ public function getPath() { return $this->_path; } /** * @return string query string of the URI */ public function getQuery() { return $this->_query; } /** * @return string fragment of the URI */ public function getFragment() { return $this->_fragment; } } /** * THttpRequestUrlFormat class. * THttpRequestUrlFormat defines the enumerable type for the possible URL formats * that can be recognized by {@link THttpRequest}. * * The following enumerable values are defined: * - Get: the URL format is like /path/to/index.php?name1=value1&name2=value2... * - Path: the URL format is like /path/to/index.php/name1,value1/name2,value2... * * @author Qiang Xue <qiang.xue@gmail.com> * @version $Id$ * @package System.Web * @since 3.0.4 */ class THttpRequestUrlFormat extends TEnumerable { const Get='Get'; const Path='Path'; } ?>