BioTorrents.de’s version of Gazelle
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

validate.class.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. <?php
  2. /**
  3. * Validate
  4. * CURRENTLY UNTESTED
  5. *
  6. * WCD/OT Gazelle contains two functions by default:
  7. * - SetFields()
  8. * - ValidateForm()
  9. *
  10. * The first is responsible for initializing a number of internal variables.
  11. * The second does the actual validation with rather hideous and clumsy logic.
  12. *
  13. * Bio Gazelle seeks to improve this class and centralize site validation.
  14. * The class should serve these common and use-dependent functions:
  15. *
  16. * - Ensure that no malicious data enters the DB
  17. * - Prevent XSS and SQL injection via $_GET and $_POST
  18. * - Escape all text inputs and HTML outputs
  19. * - Generate secure input elements and DB outputs
  20. * - Enforce $_POST except when $_GET is necessary (e.g., search → RSS)
  21. *
  22. * - Ensure that no junk data enters the DB
  23. * - Enforce HTML form element limits (generated by this class)
  24. * - Prove that a date is valid and output DB-safe datetimes
  25. * - Limit acceptable input to classes/config.php constants
  26. * - Provide structured ways to support user data (e.g., $x Samples)
  27. * - Autocomplete everywhere
  28. * - Enforce DBKEY on all encryped input
  29. *
  30. * - Some more stuff, a running list
  31. * - Check if a successful function returns null, e.g.,
  32. * php > function test() { return; }
  33. * php > var_dump(test());
  34. * NULL
  35. *
  36. * - Check if a failed function returns false, e.g.,
  37. * php > function orwell() { return (2 + 2 === 5); }
  38. * php > var_dump(orwell());
  39. * bool(false)
  40. *
  41. * todo: Support form ID checks
  42. * todo: Number and date validation
  43. */
  44. class Validate
  45. {
  46. /**
  47. * title
  48. *
  49. * Check if a torrent title is valid.
  50. * If so, return the sanitized title.
  51. * If not, return an error.
  52. */
  53. public function textInput($String)
  54. {
  55. # Previously a constant
  56. $MinLength = 10;
  57. $MaxLength = 255;
  58. # Does it exist and is it valid?
  59. if (!$String || !is_string($String)) {
  60. error('No or invalid $String parameter.');
  61. }
  62. # Is it too long or short?
  63. if (count($String)) {
  64. }
  65. }
  66. /**
  67. * Torrent errors
  68. *
  69. * Responsible for the red error messages on bad upload attemps.
  70. * todo: Test $this->TorrentError() on new file checker functions
  71. */
  72. public function TorrentError($Suspect)
  73. {
  74. global $Err;
  75. if (!$Suspect) {
  76. error('No error source :^)');
  77. }
  78. switch (false) {
  79. case $this->HasExtensions($Suspect, 1):
  80. return $Err = "The torrent has one or more files without extensions:\n" . display_str($Suspect);
  81. case $this->CruftFree($Suspect):
  82. return $Err = "The torrent has one or more junk files:\n" . display_str($Suspect);
  83. case $this->SafeCharacters($Suspect):
  84. $BadChars = $this->SafeCharacters('', true);
  85. return $Err = "One or more files has the forbidden characters $BadChars:\n" . display_str($Suspect);
  86. default:
  87. return null;
  88. }
  89. return null;
  90. }
  91. /**
  92. * Check if a file has no extension and return false.
  93. * Otherwise, return an array of the last $x extensions.
  94. */
  95. private function HasExtensions($FileName, $x)
  96. {
  97. if (!is_int($x) || $x <= 0) {
  98. error('Requested number of extensions must be <= 0');
  99. }
  100. if (!strstr('.', $FileName)) {
  101. return false;
  102. }
  103. $Extensions = array_slice(explode('.', strtolower($FileName)), -$x, $x);
  104. return (!empty($Extensions)) ? $Extensions : false;
  105. }
  106. /**
  107. * Check if a file is junk according to a filename blacklist.
  108. * todo: Change $Keywords into an array of regexes
  109. */
  110. public function CruftFree($FileName)
  111. {
  112. $Keywords = [
  113. 'ahashare.com',
  114. 'demonoid.com',
  115. 'demonoid.me',
  116. 'djtunes.com',
  117. 'h33t',
  118. 'housexclusive.net',
  119. 'limetorrents.com',
  120. 'mixesdb.com',
  121. 'mixfiend.blogstop',
  122. 'mixtapetorrent.blogspot',
  123. 'plixid.com',
  124. 'reggaeme.com',
  125. 'scc.nfo',
  126. 'thepiratebay.org',
  127. 'torrentday',
  128. ];
  129. # $Keywords match
  130. foreach ($Keywords as &$Value) {
  131. if (strpos(strtolower($FileName), $Value) !== false) {
  132. return false;
  133. }
  134. }
  135. # Incomplete data
  136. if (preg_match('/INCOMPLETE~\*/i', $FileName)) {
  137. return false;
  138. }
  139. return true;
  140. }
  141. /*
  142. * These characters are invalid on Windows NTFS:
  143. * : ? / < > \ * | "
  144. *
  145. * If no $FileName, return the list of bad characters.
  146. * If $FileName contains, a bad character, return false.
  147. * Otherwise, return true.
  148. *
  149. * todo: Add "/" to the blacklist. This causes problems with nested dirs, apparently
  150. * todo: Make possible preg_match($AllBlockedChars, $Name, $Matches)
  151. */
  152. public function SafeCharacters($FileName, $Pretty = false)
  153. {
  154. $InvalidChars = ':?<>\*|"';
  155. if (empty($FileName)) {
  156. return (!$Pretty) ? $InvalidChars : implode(' ', str_split($InvalidChars));
  157. }
  158. # todo: Regain functionality to return the invalid character
  159. if (preg_match(implode('\\', str_split($InvalidChars)), $Name, $Matches)) {
  160. return false;
  161. }
  162. return true;
  163. }
  164. /**
  165. * Extension Parser
  166. *
  167. * Takes an associative array of file types and extension, e.g.,
  168. * $Archives = [
  169. * '7z' => ['7z'],
  170. * 'bzip2' => ['bz2', 'bzip2'],
  171. * 'gzip' => ['gz', 'gzip', 'tgz', 'tpz'],
  172. * ...
  173. * ];
  174. *
  175. * Then it finds all the extensions in a torrent file list,
  176. * organizes them by file size, and returns the heaviest match.
  177. *
  178. * That way, you can have, e.g., 5 GiB FASTQ sequence data in one file,
  179. * and 100 other small files, and get the format of the actual data.
  180. */
  181. public function ParseExtensions($FileList, $Category, $FileTypes)
  182. {
  183. # Sort $Tor->file_list() output by size
  184. $UnNested = array_values($FileList[1]);
  185. $Sorted = (usort($UnNested, function ($a, $b) {
  186. return $b <=> $a;
  187. })) ? $UnNested : null; # Ternary wrap because &uarr; returns true
  188. # Harvest the wheat
  189. # todo: Entries seem duplicated here
  190. $Heaviest = array_slice($Sorted, 0, 20);
  191. $Matches = [];
  192. # Distill the file format
  193. $FileTypes = $FileTypes[$Category];
  194. $FileTypeNames = array_keys($FileTypes);
  195. foreach ($Heaviest as $Heaviest) {
  196. # Collect the last 2 period-separated tokens
  197. $Extensions = array_slice(explode('.', strtolower($Heaviest[1])), -2, 2);
  198. $Matches = array_merge($Extensions);
  199. # todo: Reduce nesting by one level
  200. foreach ($Matches as $Match) {
  201. $Match = strtolower($Match);
  202. foreach ($FileTypeNames as $FileTypeName) {
  203. $SearchMe = [ $FileTypeName, $FileTypes[$FileTypeName] ];
  204. if (in_array($Match, $SearchMe[1])) {
  205. return $SearchMe[0];
  206. break;
  207. }
  208. }
  209. # Return the last element (Other or None)
  210. return array_key_last($FileTypes);
  211. }
  212. }
  213. }
  214. /**
  215. * Make input
  216. * https://www.w3schools.com/html/html_form_input_types.asp
  217. *
  218. * Generate a secure HTML input element.
  219. * Takes $Type, $Name (also used for id), and $Classes.
  220. * Outputs a hardened input with the requested attributes.
  221. *
  222. * todo: See if this could work, one ugly switch for all forms in sections/
  223. */
  224. public function MakeInput($Type = 'text', $Name = '', $Classes = [])
  225. {
  226. if (!is_str($Type) || !is_str($Name)) {
  227. error('$Type and $Name must be strings');
  228. }
  229. # Intentionally double quoted PHP constructing $HTML
  230. # <input type='text' id='title_jp' name='title_jp' class='one two'
  231. $HTML = "";
  232. $HTML .= "<input type='$Type' id='$Name' name='$Name'";
  233. $HTML .= (!empty($Classes)) ? " class='" . implode(' ', $Classes) . "'" : "";
  234. /*
  235. 'button'
  236. 'checkbox'
  237. 'color'
  238. 'date'
  239. 'datetime-local'
  240. 'email'
  241. 'file'
  242. 'hidden'
  243. 'image'
  244. 'month'
  245. 'number'
  246. 'password'
  247. 'radio'
  248. 'range'
  249. 'reset'
  250. 'search'
  251. 'submit'
  252. 'tel'
  253. 'text'
  254. 'time'
  255. 'url'
  256. 'week'
  257. */
  258. return null;
  259. }
  260. /**
  261. * Legacy class
  262. */
  263. public $Fields = [];
  264. public function SetFields($FieldName, $Required, $FieldType, $ErrorMessage, $Options = [])
  265. {
  266. $this->Fields[$FieldName]['Type'] = strtolower($FieldType);
  267. $this->Fields[$FieldName]['Required'] = $Required;
  268. $this->Fields[$FieldName]['ErrorMessage'] = $ErrorMessage;
  269. if (!empty($Options['maxlength'])) {
  270. $this->Fields[$FieldName]['MaxLength'] = $Options['maxlength'];
  271. }
  272. if (!empty($Options['minlength'])) {
  273. $this->Fields[$FieldName]['MinLength'] = $Options['minlength'];
  274. }
  275. if (!empty($Options['comparefield'])) {
  276. $this->Fields[$FieldName]['CompareField'] = $Options['comparefield'];
  277. }
  278. if (!empty($Options['allowperiod'])) {
  279. $this->Fields[$FieldName]['AllowPeriod'] = $Options['allowperiod'];
  280. }
  281. if (!empty($Options['allowcomma'])) {
  282. $this->Fields[$FieldName]['AllowComma'] = $Options['allowcomma'];
  283. }
  284. if (!empty($Options['inarray'])) {
  285. $this->Fields[$FieldName]['InArray'] = $Options['inarray'];
  286. }
  287. if (!empty($Options['regex'])) {
  288. $this->Fields[$FieldName]['Regex'] = $Options['regex'];
  289. }
  290. }
  291. public function ValidateForm($ValidateArray)
  292. {
  293. reset($this->Fields);
  294. foreach ($this->Fields as $FieldKey => $Field) {
  295. $ValidateVar = $ValidateArray[$FieldKey];
  296. # todo: Change this to a switch statement
  297. if ($ValidateVar !== '' || !empty($Field['Required']) || $Field['Type'] === 'date') {
  298. if ($Field['Type'] === 'string') {
  299. if (isset($Field['MaxLength'])) {
  300. $MaxLength = $Field['MaxLength'];
  301. } else {
  302. $MaxLength = 255;
  303. }
  304. if (isset($Field['MinLength'])) {
  305. $MinLength = $Field['MinLength'];
  306. } else {
  307. $MinLength = 1;
  308. }
  309. if (strlen($ValidateVar) > $MaxLength) {
  310. return $Field['ErrorMessage'];
  311. } elseif (strlen($ValidateVar) < $MinLength) {
  312. return $Field['ErrorMessage'];
  313. }
  314. } elseif ($Field['Type'] === 'number') {
  315. if (isset($Field['MaxLength'])) {
  316. $MaxLength = $Field['MaxLength'];
  317. } else {
  318. $MaxLength = '';
  319. }
  320. if (isset($Field['MinLength'])) {
  321. $MinLength = $Field['MinLength'];
  322. } else {
  323. $MinLength = 0;
  324. }
  325. $Match = '0-9';
  326. if (isset($Field['AllowPeriod'])) {
  327. $Match .= '.';
  328. }
  329. if (isset($Field['AllowComma'])) {
  330. $Match .= ',';
  331. }
  332. if (preg_match('/[^'.$Match.']/', $ValidateVar) || strlen($ValidateVar) < 1) {
  333. return $Field['ErrorMessage'];
  334. } elseif ($MaxLength !== '' && $ValidateVar > $MaxLength) {
  335. return $Field['ErrorMessage'].'!!';
  336. } elseif ($ValidateVar < $MinLength) {
  337. return $Field['ErrorMessage']."$MinLength";
  338. }
  339. } elseif ($Field['Type'] === 'email') {
  340. if (isset($Field['MaxLength'])) {
  341. $MaxLength = $Field['MaxLength'];
  342. } else {
  343. $MaxLength = 255;
  344. }
  345. if (isset($Field['MinLength'])) {
  346. $MinLength = $Field['MinLength'];
  347. } else {
  348. $MinLength = 6;
  349. }
  350. if (!preg_match("/^".EMAIL_REGEX."$/i", $ValidateVar)) {
  351. return $Field['ErrorMessage'];
  352. } elseif (strlen($ValidateVar) > $MaxLength) {
  353. return $Field['ErrorMessage'];
  354. } elseif (strlen($ValidateVar) < $MinLength) {
  355. return $Field['ErrorMessage'];
  356. }
  357. } elseif ($Field['Type'] === 'link') {
  358. if (isset($Field['MaxLength'])) {
  359. $MaxLength = $Field['MaxLength'];
  360. } else {
  361. $MaxLength = 255;
  362. }
  363. if (isset($Field['MinLength'])) {
  364. $MinLength = $Field['MinLength'];
  365. } else {
  366. $MinLength = 10;
  367. }
  368. if (!preg_match('/^'.URL_REGEX.'$/i', $ValidateVar)) {
  369. return $Field['ErrorMessage'];
  370. } elseif (strlen($ValidateVar) > $MaxLength) {
  371. return $Field['ErrorMessage'];
  372. } elseif (strlen($ValidateVar) < $MinLength) {
  373. return $Field['ErrorMessage'];
  374. }
  375. } elseif ($Field['Type'] === 'username') {
  376. if (isset($Field['MaxLength'])) {
  377. $MaxLength = $Field['MaxLength'];
  378. } else {
  379. $MaxLength = 20;
  380. }
  381. if (isset($Field['MinLength'])) {
  382. $MinLength = $Field['MinLength'];
  383. } else {
  384. $MinLength = 1;
  385. }
  386. if (!preg_match(USERNAME_REGEX, $ValidateVar)) {
  387. return $Field['ErrorMessage'];
  388. } elseif (strlen($ValidateVar) > $MaxLength) {
  389. return $Field['ErrorMessage'];
  390. } elseif (strlen($ValidateVar) < $MinLength) {
  391. return $Field['ErrorMessage'];
  392. }
  393. } elseif ($Field['Type'] === 'checkbox') {
  394. if (!isset($ValidateArray[$FieldKey])) {
  395. return $Field['ErrorMessage'];
  396. }
  397. } elseif ($Field['Type'] === 'compare') {
  398. if ($ValidateArray[$Field['CompareField']] !== $ValidateVar) {
  399. return $Field['ErrorMessage'];
  400. }
  401. } elseif ($Field['Type'] === 'inarray') {
  402. if (array_search($ValidateVar, $Field['InArray']) === false) {
  403. return $Field['ErrorMessage'];
  404. }
  405. } elseif ($Field['Type'] === 'regex') {
  406. if (!preg_match($Field['Regex'], $ValidateVar)) {
  407. return $Field['ErrorMessage'];
  408. }
  409. }
  410. }
  411. } // while
  412. } // function
  413. } // class
  414. /**
  415. * File checker stub class
  416. *
  417. * Not technically part of the Validate class (yet).
  418. * Useful torrent file functions such as finding disallowed characters.
  419. * This will eventually move inside Validate for upload_handle.php.
  420. */
  421. $Keywords = array(
  422. 'ahashare.com', 'demonoid.com', 'demonoid.me', 'djtunes.com', 'h33t', 'housexclusive.net',
  423. 'limetorrents.com', 'mixesdb.com', 'mixfiend.blogstop', 'mixtapetorrent.blogspot',
  424. 'plixid.com', 'reggaeme.com' , 'scc.nfo', 'thepiratebay.org', 'torrentday');
  425. function check_file($Type, $Name)
  426. {
  427. check_name($Name);
  428. check_extensions($Type, $Name);
  429. }
  430. function check_name($Name)
  431. {
  432. global $Keywords;
  433. $NameLC = strtolower($Name);
  434. foreach ($Keywords as &$Value) {
  435. if (strpos($NameLC, $Value) !== false) {
  436. forbidden_error($Name);
  437. }
  438. }
  439. if (preg_match('/INCOMPLETE~\*/i', $Name)) {
  440. forbidden_error($Name);
  441. }
  442. /*
  443. * These characters are invalid in NTFS on Windows systems:
  444. * : ? / < > \ * | "
  445. *
  446. * todo: Add "/" to the blacklist. This causes problems with nested dirs, apparently
  447. * todo: Make possible preg_match($AllBlockedChars, $Name, $Matches)
  448. *
  449. * Only the following characters need to be escaped (see the link below):
  450. * \ - ^ ]
  451. *
  452. * http://www.php.net/manual/en/regexp.reference.character-classes.php
  453. */
  454. $AllBlockedChars = ' : ? < > \ * | " ';
  455. if (preg_match('/[\\:?<>*|"]/', $Name, $Matches)) {
  456. character_error($Matches[0], $AllBlockedChars);
  457. }
  458. }
  459. function check_extensions($Type, $Name)
  460. {
  461. # todo: Make generic or subsume into Validate->ParseExtensions()
  462. /*
  463. if (!isset($MusicExtensions[get_file_extension($Name)])) {
  464. invalid_error($Name);
  465. }
  466. */
  467. }
  468. function get_file_extension($FileName)
  469. {
  470. return strtolower(substr(strrchr($FileName, '.'), 1));
  471. }
  472. /**
  473. * Error functions
  474. *
  475. * Responsible for the red error messages on bad upload attemps.
  476. * todo: Make one function, e.g., Validate->error($type)
  477. */
  478. function invalid_error($Name)
  479. {
  480. global $Err;
  481. $Err = 'The torrent contained one or more invalid files (' . display_str($Name) . ')';
  482. }
  483. function forbidden_error($Name)
  484. {
  485. global $Err;
  486. $Err = 'The torrent contained one or more forbidden files (' . display_str($Name) . ')';
  487. }
  488. function character_error($Character, $AllBlockedChars)
  489. {
  490. global $Err;
  491. $Err = "One or more of the files or folders in the torrent has a name that contains the forbidden character '$Character'. Please rename the files as necessary and recreate the torrent.<br /><br />\nNote: The complete list of characters that are disallowed are shown below:<br />\n\t\t$AllBlockedChars";
  492. }