Request.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. <?php
  2. namespace RequestResponse;
  3. class Request{
  4. const PARAM_ALPHA = 'alpha';
  5. const PARAM_ALPHANUM = 'alphanum';
  6. const PARAM_ALPHAEXT = 'alphaext';
  7. const PARAM_ALPHANUMEXT = 'alphanumext';
  8. const PARAM_INT = 'int';
  9. const PARAM_FLOAT = 'float';
  10. const PARAM_BOOL = 'bool';
  11. const PARAM_RAW = 'raw';
  12. const PARAM_TEXT = 'text';
  13. const PARAM_AUTH = 'auth';
  14. const PARAM_BASE64 = 'base64';
  15. const PARAM_CAPABILITY = 'capability';
  16. const PARAM_CLEANHTML = 'cleanhtml';
  17. const PARAM_EMAIL = 'email';
  18. const PARAM_FILE = 'file';
  19. const PARAM_HOST = 'host';
  20. const PARAM_LANG = 'lang';
  21. const PARAM_LOCALURL = 'localurl';
  22. const PARAM_NOTAGS = 'notags';
  23. const PARAM_PATH = 'path';
  24. const PARAM_PEM = 'pem';
  25. const PARAM_PERMISSION = 'permission';
  26. const PARAM_RAW_TRIMMED = 'raw_trimmed';
  27. const PARAM_SAFEDIR = 'safedir';
  28. const PARAM_SAFEPATH = 'safepath';
  29. const PARAM_SEQUENCE = 'sequence';
  30. const PARAM_TAG = 'tag';
  31. const PARAM_TAGLIST = 'taglist';
  32. const PARAM_THEME = 'theme';
  33. const PARAM_URL = 'url';
  34. const PARAM_USERNAME = 'username';
  35. const PARAM_STRINGID = 'stringid';
  36. public static function required_param($parname, $type) {
  37. if (func_num_args() != 2 or empty($parname) or empty($type)) {
  38. throw new \Exception('required_param() requires $parname and $type to be specified (parameter: '.$parname.')');
  39. }
  40. // POST has precedence.
  41. if (isset($_POST[$parname])) {
  42. $param = $_POST[$parname];
  43. } else if (isset($_GET[$parname])) {
  44. $param = $_GET[$parname];
  45. } else {
  46. throw new \Exception('');
  47. }
  48. if (is_array($param)) {
  49. return required_param_array($parname, $type);
  50. }
  51. return self::clean_param($param, $type);
  52. }
  53. public static function required_param_array($parname, $type) {
  54. if (func_num_args() != 2 or empty($parname) or empty($type)) {
  55. throw new \Exception('required_param_array() requires $parname and $type to be specified (parameter: '.$parname.')');
  56. }
  57. // POST has precedence.
  58. if (isset($_POST[$parname])) {
  59. $param = $_POST[$parname];
  60. } else if (isset($_GET[$parname])) {
  61. $param = $_GET[$parname];
  62. } else {
  63. throw new \Exception('');
  64. }
  65. if (!is_array($param)) {
  66. throw new \Exception('');
  67. }
  68. $result = array();
  69. foreach ($param as $key => $value) {
  70. if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
  71. continue;
  72. }
  73. $result[$key] = clean_param($value, $type);
  74. }
  75. return $result;
  76. }
  77. public static function optional_param($parname, $default = '', $type){
  78. if (func_num_args() != 3 or empty($parname) or empty($type)) {
  79. throw new \Exception('optional_param requires $parname, $default + $type to be specified (parameter: '.$parname.')');
  80. }
  81. if (isset($_POST[$parname])) {
  82. $param = $_POST[$parname];
  83. } else if (isset($_GET[$parname])) {
  84. $param = $_GET[$parname];
  85. } else {
  86. return $default;
  87. }
  88. if (is_array($param)) {
  89. return optional_param_array($parname, $default, $type);
  90. }
  91. return self::clean_param($param, $type);
  92. }
  93. public static function optional_param_array($parname, $default = Array(), $type) {
  94. if (func_num_args() != 3 or empty($parname) or empty($type)) {
  95. throw new \Exception('optional_param_array requires $parname, $default + $type to be specified (parameter: '.$parname.')');
  96. }
  97. // POST has precedence.
  98. if (isset($_POST[$parname])) {
  99. $param = $_POST[$parname];
  100. } else if (isset($_GET[$parname])) {
  101. $param = $_GET[$parname];
  102. } else {
  103. return $default;
  104. }
  105. if (!is_array($param)) {
  106. return $default;
  107. }
  108. $result = array();
  109. foreach ($param as $key => $value) {
  110. if (!preg_match('/^[a-z0-9_-]+$/i', $key)) {
  111. continue;
  112. }
  113. }
  114. return $result;
  115. }
  116. public static function clean_param($param, $type) {
  117. global $CFG;
  118. if (is_array($param)) {
  119. throw new coding_exception('clean_param() can not process arrays, please use clean_param_array() instead.');
  120. } else if (is_object($param)) {
  121. if (method_exists($param, '__toString')) {
  122. $param = $param->__toString();
  123. } else {
  124. throw new coding_exception('clean_param() can not process objects, please use clean_param_array() instead.');
  125. }
  126. }
  127. switch ($type) {
  128. case self::PARAM_RAW:
  129. // No cleaning at all.
  130. $param = fix_utf8($param);
  131. return $param;
  132. case self::PARAM_RAW_TRIMMED:
  133. // No cleaning, but strip leading and trailing whitespace.
  134. $param = fix_utf8($param);
  135. return trim($param);
  136. case self::PARAM_INT:
  137. // Convert to integer.
  138. return (int)$param;
  139. case self::PARAM_FLOAT:
  140. // Convert to float.
  141. return (float)$param;
  142. case self::PARAM_ALPHA:
  143. // Remove everything not `a-z`.
  144. return preg_replace('/[^a-zA-Z]/i', '', $param);
  145. case self::PARAM_ALPHAEXT:
  146. // Remove everything not `a-zA-Z_-` (originally allowed "/" too).
  147. return preg_replace('/[^a-zA-Z_-]/i', '', $param);
  148. case self::PARAM_ALPHANUM:
  149. // Remove everything not `a-zA-Z0-9`.
  150. return preg_replace('/[^A-Za-z0-9]/i', '', $param);
  151. case self::PARAM_ALPHANUMEXT:
  152. // Remove everything not `a-zA-Z0-9_-`.
  153. return preg_replace('/[^A-Za-z0-9_-]/i', '', $param);
  154. case self::PARAM_SEQUENCE:
  155. // Remove everything not `0-9,`.
  156. return preg_replace('/[^0-9,]/i', '', $param);
  157. case self::PARAM_BOOL:
  158. // Convert to 1 or 0.
  159. $tempstr = strtolower($param);
  160. if ($tempstr === 'on' or $tempstr === 'yes' or $tempstr === 'true') {
  161. $param = 1;
  162. } else if ($tempstr === 'off' or $tempstr === 'no' or $tempstr === 'false') {
  163. $param = 0;
  164. } else {
  165. $param = empty($param) ? 0 : 1;
  166. }
  167. return $param;
  168. case self::PARAM_NOTAGS:
  169. // Strip all tags.
  170. $param = fix_utf8($param);
  171. return strip_tags($param);
  172. case self::PARAM_TEXT:
  173. // Leave only tags needed for multilang.
  174. $param = fix_utf8($param);
  175. // If the multilang syntax is not correct we strip all tags because it would break xhtml strict which is required
  176. // for accessibility standards please note this cleaning does not strip unbalanced '>' for BC compatibility reasons.
  177. do {
  178. if (strpos($param, '</lang>') !== false) {
  179. // Old and future mutilang syntax.
  180. $param = strip_tags($param, '<lang>');
  181. if (!preg_match_all('/<.*>/suU', $param, $matches)) {
  182. break;
  183. }
  184. $open = false;
  185. foreach ($matches[0] as $match) {
  186. if ($match === '</lang>') {
  187. if ($open) {
  188. $open = false;
  189. continue;
  190. } else {
  191. break 2;
  192. }
  193. }
  194. if (!preg_match('/^<lang lang="[a-zA-Z0-9_-]+"\s*>$/u', $match)) {
  195. break 2;
  196. } else {
  197. $open = true;
  198. }
  199. }
  200. if ($open) {
  201. break;
  202. }
  203. return $param;
  204. } else if (strpos($param, '</span>') !== false) {
  205. // Current problematic multilang syntax.
  206. $param = strip_tags($param, '<span>');
  207. if (!preg_match_all('/<.*>/suU', $param, $matches)) {
  208. break;
  209. }
  210. $open = false;
  211. foreach ($matches[0] as $match) {
  212. if ($match === '</span>') {
  213. if ($open) {
  214. $open = false;
  215. continue;
  216. } else {
  217. break 2;
  218. }
  219. }
  220. if (!preg_match('/^<span(\s+lang="[a-zA-Z0-9_-]+"|\s+class="multilang"){2}\s*>$/u', $match)) {
  221. break 2;
  222. } else {
  223. $open = true;
  224. }
  225. }
  226. if ($open) {
  227. break;
  228. }
  229. return $param;
  230. }
  231. } while (false);
  232. // Easy, just strip all tags, if we ever want to fix orphaned '&' we have to do that in format_string().
  233. return strip_tags($param);
  234. case self::PARAM_COMPONENT:
  235. // We do not want any guessing here, either the name is correct or not
  236. // please note only normalised component names are accepted.
  237. if (!preg_match('/^[a-z]+(_[a-z][a-z0-9_]*)?[a-z0-9]+$/', $param)) {
  238. return '';
  239. }
  240. if (strpos($param, '__') !== false) {
  241. return '';
  242. }
  243. if (strpos($param, 'mod_') === 0) {
  244. // Module names must not contain underscores because we need to differentiate them from invalid plugin types.
  245. if (substr_count($param, '_') != 1) {
  246. return '';
  247. }
  248. }
  249. return $param;
  250. case self::PARAM_SAFEDIR:
  251. // Remove everything not a-zA-Z0-9_- .
  252. return preg_replace('/[^a-zA-Z0-9_-]/i', '', $param);
  253. case self::PARAM_SAFEPATH:
  254. // Remove everything not a-zA-Z0-9/_- .
  255. return preg_replace('/[^a-zA-Z0-9\/_-]/i', '', $param);
  256. case self::PARAM_FILE:
  257. // Strip all suspicious characters from filename.
  258. $param = fix_utf8($param);
  259. $param = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $param);
  260. if ($param === '.' || $param === '..') {
  261. $param = '';
  262. }
  263. return $param;
  264. case self::PARAM_PATH:
  265. // Strip all suspicious characters from file path.
  266. $param = fix_utf8($param);
  267. $param = str_replace('\\', '/', $param);
  268. // Explode the path and clean each element using the PARAM_FILE rules.
  269. $breadcrumb = explode('/', $param);
  270. foreach ($breadcrumb as $key => $crumb) {
  271. if ($crumb === '.' && $key === 0) {
  272. // Special condition to allow for relative current path such as ./currentdirfile.txt.
  273. } else {
  274. $crumb = clean_param($crumb, PARAM_FILE);
  275. }
  276. $breadcrumb[$key] = $crumb;
  277. }
  278. $param = implode('/', $breadcrumb);
  279. // Remove multiple current path (./././) and multiple slashes (///).
  280. $param = preg_replace('~//+~', '/', $param);
  281. $param = preg_replace('~/(\./)+~', '/', $param);
  282. return $param;
  283. case self::PARAM_HOST:
  284. // Allow FQDN or IPv4 dotted quad.
  285. $param = preg_replace('/[^\.\d\w-]/', '', $param );
  286. // Match ipv4 dotted quad.
  287. if (preg_match('/(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/', $param, $match)) {
  288. // Confirm values are ok.
  289. if ( $match[0] > 255
  290. || $match[1] > 255
  291. || $match[3] > 255
  292. || $match[4] > 255 ) {
  293. // Hmmm, what kind of dotted quad is this?
  294. $param = '';
  295. }
  296. } else if ( preg_match('/^[\w\d\.-]+$/', $param) // Dots, hyphens, numbers.
  297. && !preg_match('/^[\.-]/',$param) // No leading dots/hyphens.
  298. && !preg_match('/[\.-]$/',$param) // No trailing dots/hyphens.
  299. ) {
  300. // All is ok - $param is respected.
  301. } else {
  302. // All is not ok...
  303. $param='';
  304. }
  305. return $param;
  306. case self::PARAM_URL: // Allow safe ftp, http, mailto urls.
  307. $param = fix_utf8($param);
  308. if (!empty($param) && self::validateUrlSyntax($param, 's?H?S?F?E?u-P-a?I?p?f?q?r?')) {
  309. // All is ok, param is respected.
  310. } else {
  311. // Not really ok.
  312. $param ='';
  313. }
  314. return $param;
  315. case self::PARAM_PEM:
  316. $param = trim($param);
  317. // PEM formatted strings may contain letters/numbers and the symbols:
  318. // forward slash: /
  319. // plus sign: +
  320. // equal sign: =
  321. // , surrounded by BEGIN and END CERTIFICATE prefix and suffixes.
  322. if (preg_match('/^-----BEGIN CERTIFICATE-----([\s\w\/\+=]+)-----END CERTIFICATE-----$/', trim($param), $matches)) {
  323. list($wholething, $body) = $matches;
  324. unset($wholething, $matches);
  325. $b64 = clean_param($body, PARAM_BASE64);
  326. if (!empty($b64)) {
  327. return "-----BEGIN CERTIFICATE-----\n$b64\n-----END CERTIFICATE-----\n";
  328. } else {
  329. return '';
  330. }
  331. }
  332. return '';
  333. case self::PARAM_BASE64:
  334. if (!empty($param)) {
  335. // PEM formatted strings may contain letters/numbers and the symbols
  336. // forward slash: /
  337. // plus sign: +
  338. // equal sign: =.
  339. if (0 >= preg_match('/^([\s\w\/\+=]+)$/', trim($param))) {
  340. return '';
  341. }
  342. $lines = preg_split('/[\s]+/', $param, -1, PREG_SPLIT_NO_EMPTY);
  343. // Each line of base64 encoded data must be 64 characters in length, except for the last line which may be less
  344. // than (or equal to) 64 characters long.
  345. for ($i=0, $j=count($lines); $i < $j; $i++) {
  346. if ($i + 1 == $j) {
  347. if (64 < strlen($lines[$i])) {
  348. return '';
  349. }
  350. continue;
  351. }
  352. if (64 != strlen($lines[$i])) {
  353. return '';
  354. }
  355. }
  356. return implode("\n", $lines);
  357. } else {
  358. return '';
  359. }
  360. case self::PARAM_TAGLIST:
  361. $param = fix_utf8($param);
  362. $tags = explode(',', $param);
  363. $result = array();
  364. foreach ($tags as $tag) {
  365. $res = clean_param($tag, PARAM_TAG);
  366. if ($res !== '') {
  367. $result[] = $res;
  368. }
  369. }
  370. if ($result) {
  371. return implode(',', $result);
  372. } else {
  373. return '';
  374. }
  375. case self::PARAM_USERNAME:
  376. $param = fix_utf8($param);
  377. $param = trim($param);
  378. // Convert uppercase to lowercase MDL-16919.
  379. $param = core_text::strtolower($param);
  380. if (empty($CFG->extendedusernamechars)) {
  381. $param = str_replace(" " , "", $param);
  382. // Regular expression, eliminate all chars EXCEPT:
  383. // alphanum, dash (-), underscore (_), at sign (@) and period (.) characters.
  384. $param = preg_replace('/[^-\.@_a-z0-9]/', '', $param);
  385. }
  386. return $param;
  387. case self::PARAM_EMAIL:
  388. $param = fix_utf8($param);
  389. if (validate_email($param)) {
  390. return $param;
  391. } else {
  392. return '';
  393. }
  394. case self::PARAM_STRINGID:
  395. if (preg_match('|^[a-zA-Z][a-zA-Z0-9\.:/_-]*$|', $param)) {
  396. return $param;
  397. } else {
  398. return '';
  399. }
  400. case self::PARAM_TIMEZONE:
  401. // Can be int, float(with .5 or .0) or string seperated by '/' and can have '-_'.
  402. $param = fix_utf8($param);
  403. $timezonepattern = '/^(([+-]?(0?[0-9](\.[5|0])?|1[0-3](\.0)?|1[0-2]\.5))|(99)|[[:alnum:]]+(\/?[[:alpha:]_-])+)$/';
  404. if (preg_match($timezonepattern, $param)) {
  405. return $param;
  406. } else {
  407. return '';
  408. }
  409. default:
  410. // Doh! throw error, switched parameters in optional_param or another serious problem.
  411. print_error("unknownparamtype", '', '', $type);
  412. }
  413. }
  414. function fix_utf8($value) {
  415. if (is_null($value) or $value === '') {
  416. return $value;
  417. } else if (is_string($value)) {
  418. if ((string)(int)$value === $value) {
  419. // Shortcut.
  420. return $value;
  421. }
  422. // No null bytes expected in our data, so let's remove it.
  423. $value = str_replace("\0", '', $value);
  424. // Note: this duplicates min_fix_utf8() intentionally.
  425. static $buggyiconv = null;
  426. if ($buggyiconv === null) {
  427. $buggyiconv = (!function_exists('iconv') or @iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
  428. }
  429. if ($buggyiconv) {
  430. if (function_exists('mb_convert_encoding')) {
  431. $subst = mb_substitute_character();
  432. mb_substitute_character('');
  433. $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
  434. mb_substitute_character($subst);
  435. } else {
  436. // Warn admins on admin/index.php page.
  437. $result = $value;
  438. }
  439. } else {
  440. $result = @iconv('UTF-8', 'UTF-8//IGNORE', $value);
  441. }
  442. return $result;
  443. } else if (is_array($value)) {
  444. foreach ($value as $k => $v) {
  445. $value[$k] = fix_utf8($v);
  446. }
  447. return $value;
  448. } else if (is_object($value)) {
  449. // Do not modify original.
  450. $value = clone($value);
  451. foreach ($value as $k => $v) {
  452. $value->$k = fix_utf8($v);
  453. }
  454. return $value;
  455. } else {
  456. // This is some other type, no utf-8 here.
  457. return $value;
  458. }
  459. }
  460. function validateUrlSyntax( $urladdr, $options="" ){
  461. // Force Options parameter to be lower case
  462. // DISABLED PERMAMENTLY - OK to remove from code
  463. // $options = strtolower($options);
  464. // Check Options Parameter
  465. if (!preg_match( '/^([sHSEFuPaIpfqr][+?-])*$/', $options ))
  466. {
  467. trigger_error("Options attribute malformed", E_USER_ERROR);
  468. }
  469. // Set Options Array, set defaults if options are not specified
  470. // Scheme
  471. if (strpos( $options, 's') === false) $aOptions['s'] = '?';
  472. else $aOptions['s'] = substr( $options, strpos( $options, 's') + 1, 1);
  473. // http://
  474. if (strpos( $options, 'H') === false) $aOptions['H'] = '?';
  475. else $aOptions['H'] = substr( $options, strpos( $options, 'H') + 1, 1);
  476. // https:// (SSL)
  477. if (strpos( $options, 'S') === false) $aOptions['S'] = '?';
  478. else $aOptions['S'] = substr( $options, strpos( $options, 'S') + 1, 1);
  479. // mailto: (email)
  480. if (strpos( $options, 'E') === false) $aOptions['E'] = '-';
  481. else $aOptions['E'] = substr( $options, strpos( $options, 'E') + 1, 1);
  482. // ftp://
  483. if (strpos( $options, 'F') === false) $aOptions['F'] = '-';
  484. else $aOptions['F'] = substr( $options, strpos( $options, 'F') + 1, 1);
  485. // User section
  486. if (strpos( $options, 'u') === false) $aOptions['u'] = '?';
  487. else $aOptions['u'] = substr( $options, strpos( $options, 'u') + 1, 1);
  488. // Password in user section
  489. if (strpos( $options, 'P') === false) $aOptions['P'] = '?';
  490. else $aOptions['P'] = substr( $options, strpos( $options, 'P') + 1, 1);
  491. // Address Section
  492. if (strpos( $options, 'a') === false) $aOptions['a'] = '+';
  493. else $aOptions['a'] = substr( $options, strpos( $options, 'a') + 1, 1);
  494. // IP Address in address section
  495. if (strpos( $options, 'I') === false) $aOptions['I'] = '?';
  496. else $aOptions['I'] = substr( $options, strpos( $options, 'I') + 1, 1);
  497. // Port number
  498. if (strpos( $options, 'p') === false) $aOptions['p'] = '?';
  499. else $aOptions['p'] = substr( $options, strpos( $options, 'p') + 1, 1);
  500. // File Path
  501. if (strpos( $options, 'f') === false) $aOptions['f'] = '?';
  502. else $aOptions['f'] = substr( $options, strpos( $options, 'f') + 1, 1);
  503. // Query Section
  504. if (strpos( $options, 'q') === false) $aOptions['q'] = '?';
  505. else $aOptions['q'] = substr( $options, strpos( $options, 'q') + 1, 1);
  506. // Fragment (Anchor)
  507. if (strpos( $options, 'r') === false) $aOptions['r'] = '?';
  508. else $aOptions['r'] = substr( $options, strpos( $options, 'r') + 1, 1);
  509. // Loop through options array, to search for and replace "-" to "{0}" and "+" to ""
  510. foreach($aOptions as $key => $value)
  511. {
  512. if ($value == '-')
  513. {
  514. $aOptions[$key] = '{0}';
  515. }
  516. if ($value == '+')
  517. {
  518. $aOptions[$key] = '';
  519. }
  520. }
  521. // DEBUGGING - Unescape following line to display to screen current option values
  522. // echo '<pre>'; print_r($aOptions); echo '</pre>';
  523. // Preset Allowed Characters
  524. $alphanum = '[a-zA-Z0-9]'; // Alpha Numeric
  525. $unreserved = '[a-zA-Z0-9_.!~*' . '\'' . '()-]';
  526. $escaped = '(%[0-9a-fA-F]{2})'; // Escape sequence - In Hex - %6d would be a 'm'
  527. $reserved = '[;/?:@&=+$,]'; // Special characters in the URI
  528. // Beginning Regular Expression
  529. // Scheme - Allows for 'http://', 'https://', 'mailto:', or 'ftp://'
  530. $scheme = '(';
  531. if ($aOptions['H'] === '') { $scheme .= 'http://'; }
  532. elseif ($aOptions['S'] === '') { $scheme .= 'https://'; }
  533. elseif ($aOptions['E'] === '') { $scheme .= 'mailto:'; }
  534. elseif ($aOptions['F'] === '') { $scheme .= 'ftp://'; }
  535. else
  536. {
  537. if ($aOptions['H'] === '?') { $scheme .= '|(http://)'; }
  538. if ($aOptions['S'] === '?') { $scheme .= '|(https://)'; }
  539. if ($aOptions['E'] === '?') { $scheme .= '|(mailto:)'; }
  540. if ($aOptions['F'] === '?') { $scheme .= '|(ftp://)'; }
  541. $scheme = str_replace('(|', '(', $scheme); // fix first pipe
  542. }
  543. $scheme .= ')' . $aOptions['s'];
  544. // End setting scheme
  545. // User Info - Allows for 'username@' or 'username:password@'. Note: contrary to rfc, I removed ':' from username section, allowing it only in password.
  546. // /---------------- Username -----------------------\ /-------------------------------- Password ------------------------------\
  547. $userinfo = '((' . $unreserved . '|' . $escaped . '|[;&=+$,]' . ')+(:(' . $unreserved . '|' . $escaped . '|[;:&=+$,]' . ')+)' . $aOptions['P'] . '@)' . $aOptions['u'];
  548. // IP ADDRESS - Allows 0.0.0.0 to 255.255.255.255
  549. $ipaddress = '((((2(([0-4][0-9])|(5[0-5])))|([01]?[0-9]?[0-9]))\.){3}((2(([0-4][0-9])|(5[0-5])))|([01]?[0-9]?[0-9])))';
  550. // Tertiary Domain(s) - Optional - Multi - Although some sites may use other characters, the RFC says tertiary domains have the same naming restrictions as second level domains
  551. $domain_tertiary = '(' . $alphanum . '(([a-zA-Z0-9-]{0,62})' . $alphanum . ')?\.)*';
  552. /* MDL-9295 - take out domain_secondary here and below, so that URLs like http://localhost/ and lan addresses like http://host/ are accepted.
  553. // Second Level Domain - Required - First and last characters must be Alpha-numeric. Hyphens are allowed inside.
  554. $domain_secondary = '(' . $alphanum . '(([a-zA-Z0-9-]{0,62})' . $alphanum . ')?\.)';
  555. */
  556. // we want more relaxed URLs in Moodle: MDL-11462
  557. // Top Level Domain - First character must be Alpha. Last character must be AlphaNumeric. Hyphens are allowed inside.
  558. $domain_toplevel = '([a-zA-Z](([a-zA-Z0-9-]*)[a-zA-Z0-9])?)';
  559. /* // Top Level Domain - Required - Domain List Current As Of December 2004. Use above escaped line to be forgiving of possible future TLD's
  560. $domain_toplevel = '(aero|biz|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|post|pro|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|az|ax|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|sv|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)';
  561. */
  562. // Address can be IP address or Domain
  563. if ($aOptions['I'] === '{0}') { // IP Address Not Allowed
  564. $address = '(' . $domain_tertiary . /* MDL-9295 $domain_secondary . */ $domain_toplevel . ')';
  565. } elseif ($aOptions['I'] === '') { // IP Address Required
  566. $address = '(' . $ipaddress . ')';
  567. } else { // IP Address Optional
  568. $address = '((' . $ipaddress . ')|(' . $domain_tertiary . /* MDL-9295 $domain_secondary . */ $domain_toplevel . '))';
  569. }
  570. $address = $address . $aOptions['a'];
  571. // Port Number - :80 or :8080 or :65534 Allows range of :0 to :65535
  572. // (0-59999) |(60000-64999) |(65000-65499) |(65500-65529) |(65530-65535)
  573. $port_number = '(:(([0-5]?[0-9]{1,4})|(6[0-4][0-9]{3})|(65[0-4][0-9]{2})|(655[0-2][0-9])|(6553[0-5])))' . $aOptions['p'];
  574. // Path - Can be as simple as '/' or have multiple folders and filenames
  575. $path = '(/((;)?(' . $unreserved . '|' . $escaped . '|' . '[:@&=+$,]' . ')+(/)?)*)' . $aOptions['f'];
  576. // Query Section - Accepts ?var1=value1&var2=value2 or ?2393,1221 and much more
  577. $querystring = '(\?(' . $reserved . '|' . $unreserved . '|' . $escaped . ')*)' . $aOptions['q'];
  578. // Fragment Section - Accepts anchors such as #top
  579. $fragment = '(\#(' . $reserved . '|' . $unreserved . '|' . $escaped . ')*)' . $aOptions['r'];
  580. // Building Regular Expression
  581. $regexp = '#^' . $scheme . $userinfo . $address . $port_number . $path . $querystring . $fragment . '$#i';
  582. // DEBUGGING - Uncomment Line Below To Display The Regular Expression Built
  583. // echo '<pre>' . htmlentities(wordwrap($regexp,70,"\n",1)) . '</pre>';
  584. // Running the regular expression
  585. if (preg_match( $regexp, $urladdr ))
  586. {
  587. return true; // The domain passed
  588. }
  589. else
  590. {
  591. return false; // The domain didn't pass the expression
  592. }
  593. }
  594. function validateEmailSyntax( $emailaddr, $options="" ){
  595. // Check Options Parameter
  596. if (!preg_match( '/^([sHSEFuPaIpfqr][+?-])*$/', $options ))
  597. {
  598. trigger_error("Options attribute malformed", E_USER_ERROR);
  599. }
  600. // Set Options Array, set defaults if options are not specified
  601. // Scheme
  602. if (strpos( $options, 's') === false) $aOptions['s'] = '-';
  603. else $aOptions['s'] = substr( $options, strpos( $options, 's') + 1, 1);
  604. // http://
  605. if (strpos( $options, 'H') === false) $aOptions['H'] = '-';
  606. else $aOptions['H'] = substr( $options, strpos( $options, 'H') + 1, 1);
  607. // https:// (SSL)
  608. if (strpos( $options, 'S') === false) $aOptions['S'] = '-';
  609. else $aOptions['S'] = substr( $options, strpos( $options, 'S') + 1, 1);
  610. // mailto: (email)
  611. if (strpos( $options, 'E') === false) $aOptions['E'] = '?';
  612. else $aOptions['E'] = substr( $options, strpos( $options, 'E') + 1, 1);
  613. // ftp://
  614. if (strpos( $options, 'F') === false) $aOptions['F'] = '-';
  615. else $aOptions['F'] = substr( $options, strpos( $options, 'F') + 1, 1);
  616. // User section
  617. if (strpos( $options, 'u') === false) $aOptions['u'] = '+';
  618. else $aOptions['u'] = substr( $options, strpos( $options, 'u') + 1, 1);
  619. // Password in user section
  620. if (strpos( $options, 'P') === false) $aOptions['P'] = '-';
  621. else $aOptions['P'] = substr( $options, strpos( $options, 'P') + 1, 1);
  622. // Address Section
  623. if (strpos( $options, 'a') === false) $aOptions['a'] = '+';
  624. else $aOptions['a'] = substr( $options, strpos( $options, 'a') + 1, 1);
  625. // IP Address in address section
  626. if (strpos( $options, 'I') === false) $aOptions['I'] = '-';
  627. else $aOptions['I'] = substr( $options, strpos( $options, 'I') + 1, 1);
  628. // Port number
  629. if (strpos( $options, 'p') === false) $aOptions['p'] = '-';
  630. else $aOptions['p'] = substr( $options, strpos( $options, 'p') + 1, 1);
  631. // File Path
  632. if (strpos( $options, 'f') === false) $aOptions['f'] = '-';
  633. else $aOptions['f'] = substr( $options, strpos( $options, 'f') + 1, 1);
  634. // Query Section
  635. if (strpos( $options, 'q') === false) $aOptions['q'] = '-';
  636. else $aOptions['q'] = substr( $options, strpos( $options, 'q') + 1, 1);
  637. // Fragment (Anchor)
  638. if (strpos( $options, 'r') === false) $aOptions['r'] = '-';
  639. else $aOptions['r'] = substr( $options, strpos( $options, 'r') + 1, 1);
  640. // Generate options
  641. $newoptions = '';
  642. foreach($aOptions as $key => $value)
  643. {
  644. $newoptions .= $key . $value;
  645. }
  646. // DEBUGGING - Uncomment line below to display generated options
  647. // echo '<pre>' . $newoptions . '</pre>';
  648. // Send to validateUrlSyntax() and return result
  649. return validateUrlSyntax( $emailaddr, $newoptions);
  650. }
  651. function validateFtpSyntax( $ftpaddr, $options="" ){
  652. // Check Options Parameter
  653. if (!preg_match( '/^([sHSEFuPaIpfqr][+?-])*$/', $options ))
  654. {
  655. trigger_error("Options attribute malformed", E_USER_ERROR);
  656. }
  657. // Set Options Array, set defaults if options are not specified
  658. // Scheme
  659. if (strpos( $options, 's') === false) $aOptions['s'] = '?';
  660. else $aOptions['s'] = substr( $options, strpos( $options, 's') + 1, 1);
  661. // http://
  662. if (strpos( $options, 'H') === false) $aOptions['H'] = '-';
  663. else $aOptions['H'] = substr( $options, strpos( $options, 'H') + 1, 1);
  664. // https:// (SSL)
  665. if (strpos( $options, 'S') === false) $aOptions['S'] = '-';
  666. else $aOptions['S'] = substr( $options, strpos( $options, 'S') + 1, 1);
  667. // mailto: (email)
  668. if (strpos( $options, 'E') === false) $aOptions['E'] = '-';
  669. else $aOptions['E'] = substr( $options, strpos( $options, 'E') + 1, 1);
  670. // ftp://
  671. if (strpos( $options, 'F') === false) $aOptions['F'] = '+';
  672. else $aOptions['F'] = substr( $options, strpos( $options, 'F') + 1, 1);
  673. // User section
  674. if (strpos( $options, 'u') === false) $aOptions['u'] = '?';
  675. else $aOptions['u'] = substr( $options, strpos( $options, 'u') + 1, 1);
  676. // Password in user section
  677. if (strpos( $options, 'P') === false) $aOptions['P'] = '?';
  678. else $aOptions['P'] = substr( $options, strpos( $options, 'P') + 1, 1);
  679. // Address Section
  680. if (strpos( $options, 'a') === false) $aOptions['a'] = '+';
  681. else $aOptions['a'] = substr( $options, strpos( $options, 'a') + 1, 1);
  682. // IP Address in address section
  683. if (strpos( $options, 'I') === false) $aOptions['I'] = '?';
  684. else $aOptions['I'] = substr( $options, strpos( $options, 'I') + 1, 1);
  685. // Port number
  686. if (strpos( $options, 'p') === false) $aOptions['p'] = '?';
  687. else $aOptions['p'] = substr( $options, strpos( $options, 'p') + 1, 1);
  688. // File Path
  689. if (strpos( $options, 'f') === false) $aOptions['f'] = '?';
  690. else $aOptions['f'] = substr( $options, strpos( $options, 'f') + 1, 1);
  691. // Query Section
  692. if (strpos( $options, 'q') === false) $aOptions['q'] = '-';
  693. else $aOptions['q'] = substr( $options, strpos( $options, 'q') + 1, 1);
  694. // Fragment (Anchor)
  695. if (strpos( $options, 'r') === false) $aOptions['r'] = '-';
  696. else $aOptions['r'] = substr( $options, strpos( $options, 'r') + 1, 1);
  697. // Generate options
  698. $newoptions = '';
  699. foreach($aOptions as $key => $value)
  700. {
  701. $newoptions .= $key . $value;
  702. }
  703. // DEBUGGING - Uncomment line below to display generated options
  704. // echo '<pre>' . $newoptions . '</pre>';
  705. // Send to validateUrlSyntax() and return result
  706. return validateUrlSyntax( $ftpaddr, $newoptions);
  707. }
  708. }