RequestUtil.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. <?php
  2. /** @package verysimple::HTTP */
  3. /** import supporting libraries */
  4. /**
  5. * Static utility class for processing form post/request data
  6. *
  7. * Contains various methods for retrieving user input from forms
  8. *
  9. * @package verysimple::HTTP
  10. * @author VerySimple Inc.
  11. * @copyright 1997-2011 VerySimple, Inc. http://www.verysimple.com
  12. * @license http://www.gnu.org/licenses/lgpl.html LGPL
  13. * @version 1.4
  14. */
  15. class RequestUtil
  16. {
  17. /** @var bool set to true and all non-ascii characters in request variables will be html encoded */
  18. static $ENCODE_NON_ASCII = false;
  19. /** @var bool set to false to skip is_uploaded_file. This allows for simulated file uploads during unit testing */
  20. static $VALIDATE_FILE_UPLOAD = true;
  21. /** @var body contents, only read once in case GetBody is called multiple times */
  22. private static $bodyCache = '';
  23. /** @var true if the request body has already been read */
  24. private static $bodyCacheIsReady = false;
  25. /**
  26. * @var bool
  27. * @deprecated use $VALIDATE_FILE_UPLOAD instead
  28. */
  29. static $TestMode = false;
  30. /**
  31. * Returns the remote host IP address, attempting to locate originating
  32. * IP of the requester in the case of proxy/load balanced requests.
  33. *
  34. * @see http://en.wikipedia.org/wiki/X-Forwarded-For
  35. * @return string
  36. */
  37. static function GetRemoteHost()
  38. {
  39. if (array_key_exists('HTTP_X_CLUSTER_CLIENT_IP',$_SERVER)) return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
  40. if (array_key_exists('HTTP_X_FORWARDED_FOR',$_SERVER)) return $_SERVER['HTTP_X_FORWARDED_FOR'];
  41. if (array_key_exists('X_FORWARDED_FOR',$_SERVER)) return $_SERVER['X_FORWARDED_FOR'];
  42. if (array_key_exists('REMOTE_ADDR',$_SERVER)) return $_SERVER['REMOTE_ADDR'];
  43. return "0.0.0.0";
  44. }
  45. /**
  46. * Returns true if the current session is running in SSL
  47. */
  48. static function IsSSL()
  49. {
  50. return isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != "" && $_SERVER['HTTPS'] != "off";
  51. }
  52. /** In the case of URL re-writing, sometimes querystrings appended to a URL can get
  53. * lost. This function examines the original request URI and updates $_REQUEST
  54. * superglobal to ensure that it contains all of values in the qeurtystring
  55. *
  56. */
  57. public static function NormalizeUrlRewrite()
  58. {
  59. $uri = array();
  60. if (isset($_SERVER["REQUEST_URI"]))
  61. {
  62. $uri = parse_url($_SERVER["REQUEST_URI"]);
  63. }
  64. elseif (isset($_SERVER["QUERY_STRING"]))
  65. {
  66. $uri['query'] = $_SERVER["QUERY_STRING"];
  67. }
  68. if (isset($uri['query']))
  69. {
  70. $parts = explode("&",$uri['query']);
  71. foreach ($parts as $part)
  72. {
  73. $keyval = explode("=",$part,2);
  74. $_REQUEST[$keyval[0]] = isset($keyval[1]) ? urldecode($keyval[1]) : "";
  75. }
  76. }
  77. }
  78. /**
  79. * Returns the root url of the server without any subdirectories
  80. * @return string URL path with trailing slash
  81. */
  82. public static function GetServerRootUrl()
  83. {
  84. $url = self::GetCurrentURL(false);
  85. $parts = explode('/', $url);
  86. if (count($parts) < 2) throw new Exception('RequestUtil is unable to determine the server root');
  87. return $parts[0] . '//' . $parts[2] . '/';
  88. }
  89. /**
  90. * Returns the base url of the currently executing script. For example
  91. * the script http://localhost/myapp/index.php would return http://localhost/myapp/
  92. * @return string URL path with trailing slash
  93. */
  94. public static function GetBaseURL()
  95. {
  96. $url = self::GetCurrentURL(false);
  97. $slash = strripos($url,"/");
  98. return substr($url,0,$slash+1);
  99. }
  100. /**
  101. * Returns the parts of the url as deliminated by forward slashes for example /this/that/other
  102. * will be returned as an array [this,that,other]
  103. * @param string root folder for the app (ex. 'myapp' or 'myapp/subdir1')
  104. * @return array
  105. */
  106. public static function GetUrlParts($appRoot = '')
  107. {
  108. $urlqs = explode("?", self::GetCurrentURL(),2);
  109. $url = $urlqs[0];
  110. // if a root folder was provided, then we need to strip that out as well
  111. if ($appRoot) $url = str_replace($appRoot.'/','',$url);
  112. $parts = explode("/", $url);
  113. // we only want the parts starting with #3 (after http://server/)
  114. array_shift($parts);
  115. array_shift($parts);
  116. array_shift($parts);
  117. // if there is no action specified then we don't want to return an array with an empty string
  118. while (count($parts) && $parts[0] == '')
  119. {
  120. array_shift($parts);
  121. }
  122. return $parts;
  123. }
  124. /**
  125. * Returns the request method (GET, POST, PUT, DELETE). This is detected based
  126. * on the HTTP request method, a special URL parameter, or a request header
  127. * with the name 'X-HTTP-Method-Override'
  128. *
  129. * For clients or servers that don't support PUT/DELETE requests, the emulated
  130. * param can be used or the override header
  131. *
  132. * @param string name of the querystring parameter that has the overridden request method
  133. */
  134. public static function GetMethod($emulateHttpParamName = '_method')
  135. {
  136. if (array_key_exists($emulateHttpParamName, $_REQUEST)) return $_REQUEST[$emulateHttpParamName];
  137. $headers = self::GetRequestHeaders();
  138. // this is used by backbone
  139. if (array_key_exists('X-HTTP-Method-Override', $headers))
  140. {
  141. return $headers['X-HTTP-Method-Override'];
  142. }
  143. return array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : '';
  144. }
  145. /**
  146. * Return all request headers using the best method available for the server environment
  147. * @return array
  148. */
  149. public static function GetRequestHeaders()
  150. {
  151. if (function_exists('getallheaders')) return getallheaders();
  152. $headers = array();
  153. foreach ($_SERVER as $k => $v)
  154. {
  155. if (substr($k, 0, 5) == "HTTP_")
  156. {
  157. $k = str_replace('_', ' ', substr($k, 5));
  158. $k = str_replace(' ', '-', ucwords(strtolower($k)));
  159. $headers[$k] = $v;
  160. }
  161. }
  162. return $headers;
  163. }
  164. /**
  165. * Returns the body/payload of a request. this is cached so that this method
  166. * may be called multiple times.
  167. *
  168. * Note: if this is a PUT request and the body is not returning data, then
  169. * you must look in other code an libraries that may read from php://input,
  170. * which can only be read one time for PUT requests
  171. *
  172. * @return string
  173. */
  174. public static function GetBody()
  175. {
  176. if (!self::$bodyCacheIsReady)
  177. {
  178. self::$bodyCache = @file_get_contents('php://input');
  179. self::$bodyCacheIsReady = true;
  180. }
  181. return self::$bodyCache;
  182. }
  183. /**
  184. * Used primarily for unit testing. Set the contents of the request body
  185. * @param string $contents
  186. */
  187. public static function SetBody($contents)
  188. {
  189. self::$bodyCache = $contents;
  190. self::$bodyCacheIsReady = true;
  191. }
  192. /**
  193. * Return the HTTP headers sent along with the request. This will attempt
  194. * to use apache_request_headers if available in the environment, otherwise
  195. * will manually build the headers using $_SERVER superglobal
  196. * @return array
  197. */
  198. public static function GetHeaders()
  199. {
  200. $headers = false;
  201. if (function_exists('apache_request_headers')) $headers = apache_request_headers();
  202. if ($headers === false)
  203. {
  204. // apache_request_headers is not supported in this environment
  205. $headers = array();
  206. foreach ($_SERVER as $key => $value)
  207. {
  208. if (substr($key, 0, 5) <> 'HTTP_')
  209. {
  210. continue;
  211. }
  212. $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
  213. $headers[$header] = $value;
  214. }
  215. }
  216. return $headers;
  217. }
  218. /** Returns the full URL of the PHP page that is currently executing
  219. *
  220. * @param bool $include_querystring (optional) Specify true/false to include querystring. Default is true.
  221. * @param bool $append_post_vars true to append post variables to the querystring as GET parameters Default is false
  222. * @return string URL
  223. */
  224. public static function GetCurrentURL($include_querystring = true, $append_post_vars = false)
  225. {
  226. $server_protocol = isset($_SERVER["SERVER_PROTOCOL"]) ? $_SERVER["SERVER_PROTOCOL"] : "";
  227. $http_host = isset($_SERVER["HTTP_HOST"]) ? $_SERVER["HTTP_HOST"] : "";
  228. $server_port = isset($_SERVER["SERVER_PORT"]) ? $_SERVER["SERVER_PORT"] : "";
  229. $protocol = substr($server_protocol, 0, strpos($server_protocol, "/"))
  230. . (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on" ? "S" : "");
  231. $port = "";
  232. $domainport = explode(":",$http_host);
  233. $domain = $domainport[0];
  234. $port = (isset($domainport[1])) ? $domainport[1] : $server_port;
  235. // ports 80 and 443 are generally not included in the url
  236. $port = ($port == "" || $port == "80" || $port == "443") ? "" : (":" . $port);
  237. if (isset($_SERVER['REQUEST_URI']))
  238. {
  239. // REQUEST_URI is more accurate but isn't always defined on windows
  240. // in particular for the format http://www.domain.com/?var=val
  241. $pq = explode("?",$_SERVER['REQUEST_URI'],2);
  242. $path = $pq[0];
  243. $qs = isset($pq[1]) ? "?" . $pq[1] : "";
  244. }
  245. else
  246. {
  247. // otherwise use SCRIPT_NAME & QUERY_STRING
  248. $path = isset($_SERVER['SCRIPT_NAME']) ? $_SERVER['SCRIPT_NAME'] : "";
  249. $qs = isset($_SERVER['QUERY_STRING']) ? "?" . $_SERVER['QUERY_STRING'] : "";
  250. }
  251. // if we also want the post variables appended we can get them as a querystring from php://input
  252. if ($append_post_vars && isset($_POST))
  253. {
  254. $post = self::GetBody();
  255. $qs .= $qs ? "&$post" : "?$post";
  256. }
  257. $url = strtolower($protocol) . "://" . $domain . $port . $path . ($include_querystring ? $qs : "");
  258. return $url;
  259. }
  260. /**
  261. * Returns a form upload as a FileUpload object. This function throws an exeption on fail
  262. * with details, so it is recommended to use try/catch when calling this function
  263. *
  264. * @param string $fieldname name of the html form field
  265. * @param bool $b64encode true to base64encode file data (default false)
  266. * @param bool $ignore_empty true to not throw exception if form fields doesn't contain a file (default false)
  267. * @param int $max_kb maximum size allowed for upload (default unlimited)
  268. * @param array $ok_types if array is provided, only files with those Extensions will be allowed (default all)
  269. * @return FileUpload object (or null if $ignore_empty = true and there is no file data)
  270. */
  271. public static function GetFileUpload($fieldname, $ignore_empty = false, $max_kb = 0, $ok_types = null)
  272. {
  273. // make sure there is actually a file upload
  274. if (!isset($_FILES[$fieldname]))
  275. {
  276. // this means the form field wasn't present which is generally an error
  277. // however if ignore is specified, then return empty string
  278. if ($ignore_empty)
  279. {
  280. return null;
  281. }
  282. throw new Exception("\$_FILES['".$fieldname."'] is empty. Did you forget to add enctype='multipart/form-data' to your form code?");
  283. }
  284. // make sure a file was actually uploaded, otherwise return null
  285. if($_FILES[$fieldname]['error'] == 4)
  286. {
  287. return;
  288. }
  289. // get the upload ref
  290. $upload = $_FILES[$fieldname];
  291. // make sure there were no errors during upload, but ignore case where
  292. if ($upload['error'])
  293. {
  294. $error_codes[0] = "The file uploaded with success.";
  295. $error_codes[1] = "The uploaded file exceeds the upload_max_filesize directive in php.ini.";
  296. $error_codes[2] = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the html form.";
  297. $error_codes[3] = "The uploaded file was only partially uploaded.";
  298. $error_codes[4] = "No file was uploaded.";
  299. throw new Exception("Error uploading file: " . $error_codes[$upload['error']]);
  300. }
  301. // backwards compatibility
  302. if (self::$TestMode) self::$VALIDATE_FILE_UPLOAD = false;
  303. // make sure this is a legit file request
  304. if ( self::$VALIDATE_FILE_UPLOAD && is_uploaded_file($upload['tmp_name']) == false )
  305. {
  306. throw new Exception("Unable to access this upload: " . $fieldname);
  307. }
  308. // get the filename and Extension
  309. $tmp_path = $upload['tmp_name'];
  310. $info = pathinfo($upload['name']);
  311. require_once("FileUpload.php");
  312. $fupload = new FileUpload();
  313. $fupload->Name = $info['basename'];
  314. $fupload->Size = $upload['size'];
  315. $fupload->Type = $upload['type'];
  316. $fupload->Extension = strtolower($info['extension']);
  317. if ($ok_types && !in_array($fupload->Extension, $ok_types) )
  318. {
  319. throw new Exception("The file '".htmlentities($fupload->Name)."' is not a type that is allowed. Allowed file types are: " . (implode(", ",$ok_types)) . ".");
  320. }
  321. if ($max_kb && ($fupload->Size/1024) > $max_kb)
  322. {
  323. throw new Exception("The file '".htmlentities($fupload->Name)."' is to large. Maximum allowed size is " . number_format($max_kb/1024,2) . "Mb");
  324. }
  325. // open the file and read the entire contents
  326. $fh = fopen($tmp_path,"r");
  327. $fupload->Data = fread($fh, filesize($tmp_path));
  328. fclose($fh);
  329. return $fupload;
  330. }
  331. /**
  332. * Returns a form upload as an xml document with the file data base64 encoded.
  333. * suitable for storing in a clob or blob
  334. *
  335. * @param string $fieldname name of the html form field
  336. * @param bool $b64encode true to base64encode file data (default true)
  337. * @param bool $ignore_empty true to not throw exception if form fields doesn't contain a file (default false)
  338. * @param int $max_kb maximum size allowed for upload (default unlimited)
  339. * @param array $ok_types if array is provided, only files with those Extensions will be allowed (default all)
  340. * @return string or null
  341. */
  342. public static function GetFile($fieldname, $b64encode = true, $ignore_empty = false, $max_kb = 0, $ok_types = null)
  343. {
  344. $fupload = self::GetFileUpload($fieldname, $ignore_empty, $max_kb, $ok_types);
  345. return ($fupload) ? $fupload->ToXML($b64encode) : null;
  346. }
  347. /**
  348. * Sets a value as if it was sent from the browser - primarily used for unit testing
  349. *
  350. * @param string $key
  351. * @param variant $val
  352. */
  353. public static function Set($key, $val)
  354. {
  355. $_REQUEST[$key] = $val;
  356. }
  357. /**
  358. * Clears all browser input - primarily used for unit testing
  359. *
  360. */
  361. public static function ClearAll()
  362. {
  363. $_REQUEST = array();
  364. $_FILES = array();
  365. self::$bodyCache = "";
  366. self::$bodyCacheIsReady = false;
  367. }
  368. /**
  369. * Returns a form parameter as a string, handles null values. Note that if
  370. * $ENCODE_NON_ASCII = true then the value will be passed through VerySimpleStringUtil::EncodeToHTML
  371. * before being returned.
  372. *
  373. * If the form field is a multi-value type (checkbox, etc) then an array may be returned
  374. *
  375. * @param string $fieldname
  376. * @param string $default value returned if $_REQUEST[$fieldname] is blank or null (default = empty string)
  377. * @param bool $escape if true htmlspecialchars($val) is returned (default = false)
  378. * @param bool $ignorecase if true then request fieldname will not be case sensitive (default = false)
  379. * @return string | array
  380. */
  381. public static function Get($fieldname, $default = "", $escape = false, $ignorecase = false)
  382. {
  383. $val = null;
  384. if ($ignorecase)
  385. {
  386. $_REQUEST_LOWER = array_change_key_case($_REQUEST, CASE_LOWER);
  387. $val = (isset($_REQUEST_LOWER[strtolower($fieldname)]) && $_REQUEST_LOWER[strtolower($fieldname)] != "") ? $_REQUEST_LOWER[strtolower($fieldname)] : $default;
  388. }
  389. else
  390. {
  391. $val = (isset($_REQUEST[$fieldname]) && $_REQUEST[$fieldname] != "") ? $_REQUEST[$fieldname] : $default;
  392. }
  393. if ($escape)
  394. {
  395. $val = htmlspecialchars($val, ENT_COMPAT, null, false);
  396. }
  397. if (self::$ENCODE_NON_ASCII)
  398. {
  399. require_once("verysimple/String/VerySimpleStringUtil.php");
  400. if (is_array($val))
  401. {
  402. foreach ($val as $k=>$v)
  403. {
  404. $val[$k] = VerySimpleStringUtil::EncodeToHTML($v);
  405. }
  406. }
  407. else
  408. {
  409. $val = VerySimpleStringUtil::EncodeToHTML($val);
  410. }
  411. }
  412. return $val;
  413. }
  414. /**
  415. * Returns true if the given form field has non-ascii characters
  416. * @param string $fieldname
  417. * @return bool
  418. */
  419. public static function HasNonAsciiChars($fieldname)
  420. {
  421. require_once("verysimple/String/VerySimpleStringUtil.php");
  422. $val = isset($_REQUEST[$fieldname]) ? $_REQUEST[$fieldname] : '';
  423. return VerySimpleStringUtil::EncodeToHTML($val) != $val;
  424. }
  425. /**
  426. * Returns a form parameter and persists it in the session. If the form parameter was not passed
  427. * again, then it returns the session value. if the session value doesn't exist, then it returns
  428. * the default setting
  429. *
  430. * @param string $fieldname
  431. * @param string $default
  432. * @return string
  433. */
  434. public static function GetPersisted($fieldname, $default = "",$escape = false)
  435. {
  436. if ( isset($_REQUEST[$fieldname]) )
  437. {
  438. $_SESSION["_PERSISTED_".$fieldname] = self::Get($fieldname, $default, $escape);
  439. }
  440. if ( !isset($_SESSION["_PERSISTED_".$fieldname]) )
  441. {
  442. $_SESSION["_PERSISTED_".$fieldname] = $default;
  443. }
  444. return $_SESSION["_PERSISTED_".$fieldname];
  445. }
  446. /**
  447. * Returns a form parameter as a date formatted for mysql YYYY-MM-DD,
  448. * expects some type of date format. if default value is not provided,
  449. * will return today. if default value is empty string "" will return
  450. * empty string.
  451. *
  452. * @param string $fieldname
  453. * @param string $default default value = today
  454. * @param bool $includetime whether to include the time in addition to date
  455. * @return string
  456. */
  457. public static function GetAsDate($fieldname, $default = "date('Y-m-d')", $includetime = false)
  458. {
  459. $returnVal = self::Get($fieldname,$default);
  460. if ($returnVal == "date('Y-m-d')")
  461. {
  462. return date('Y-m-d');
  463. }
  464. elseif ($returnVal == "date('Y-m-d H:i:s')")
  465. {
  466. return date('Y-m-d H:i:s');
  467. }
  468. elseif ($returnVal == "")
  469. {
  470. return "";
  471. }
  472. else
  473. {
  474. if ($includetime)
  475. {
  476. if (self::Get($fieldname."Hour"))
  477. {
  478. $hour = self::Get($fieldname."Hour",date("H"));
  479. $minute = self::Get($fieldname."Minute",date("i"));
  480. $ampm = self::Get($fieldname."AMPM","AM");
  481. if ($ampm == "PM")
  482. {
  483. $hour = ($hour*1)+12;
  484. }
  485. $returnVal .= " " . $hour . ":" . $minute . ":" . "00";
  486. }
  487. return date("Y-m-d H:i:s",strtotime($returnVal));
  488. }
  489. else
  490. {
  491. return date("Y-m-d",strtotime($returnVal));
  492. }
  493. }
  494. }
  495. /**
  496. * Returns a form parameter as a date formatted for mysql YYYY-MM-DD HH:MM:SS,
  497. * expects some type of date format. if default value is not provided,
  498. * will return now. if default value is empty string "" will return
  499. * empty string.
  500. *
  501. * @param string $fieldname
  502. * @param string $default default value = today
  503. * @return string
  504. */
  505. public static function GetAsDateTime($fieldname, $default = "date('Y-m-d H:i:s')")
  506. {
  507. return self::GetAsDate($fieldname,$default,true);
  508. }
  509. /**
  510. * Returns a form parameter minus currency symbols
  511. *
  512. * @param string $fieldname
  513. * @return string
  514. */
  515. public static function GetCurrency($fieldname)
  516. {
  517. return str_replace(array(',','$'),'',self::Get($fieldname));
  518. }
  519. }
  520. ?>