mancubus.class.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. <?php
  2. /**
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  15. *
  16. * 2019 - 2023 https://://www.bananas-playground.net/projekt/selfpaste
  17. */
  18. /**
  19. * Handles the upload and the file itself
  20. */
  21. class Mancubus {
  22. /**
  23. * Content from $_FILES
  24. * @var array
  25. */
  26. private array $_uploadedData;
  27. /**
  28. * The short id
  29. * @var string
  30. */
  31. private string $_short;
  32. private $_saveFilename;
  33. private $_storagePath;
  34. private $_shortURL;
  35. /**
  36. * Mancubus constructor.
  37. */
  38. function __construct() {
  39. }
  40. /**
  41. * Requires a single upload from $_FILES
  42. * @see https://www.php.net/manual/en/features.file-upload.post-method.php
  43. *
  44. * @param $file array
  45. * @return bool
  46. */
  47. public function load(array $file): bool {
  48. $ret = false;
  49. if(isset($file['name'])
  50. && isset($file['type'])
  51. && isset($file['size'])
  52. && isset($file['tmp_name'])
  53. && isset($file['error'])
  54. ) {
  55. $this->_uploadedData = $file;
  56. $ret = true;
  57. }
  58. return $ret;
  59. }
  60. /**
  61. * Either set short to given string
  62. * or create from _saveFilename. In this case _saveFilename is a number
  63. *
  64. * @param string $short
  65. * @return void
  66. */
  67. public function setShort(string $short=''): void {
  68. if($short != '') {
  69. $this->_short = $short;
  70. }
  71. elseif(!empty($this->_saveFilename)) {
  72. $this->_short = Summoner::b64sl_pack_id($this->_saveFilename);
  73. }
  74. }
  75. /**
  76. * Either set _saveFilename to given string
  77. * or create from a random number. In this case _short needs this as a base
  78. *
  79. * @param string $string
  80. * @return void
  81. */
  82. public function setSaveFilename(string $string=''): void {
  83. if($string != '') {
  84. $this->_saveFilename = $string;
  85. }
  86. else {
  87. $r = rand(1000, 9999);
  88. $this->_saveFilename = (string)$r;
  89. }
  90. }
  91. /**
  92. * Set _shortURL to given string
  93. * or create based on SELFPASTE_URL and _short
  94. *
  95. * @param string $string
  96. */
  97. public function setShortURL(string $string=''): void {
  98. if($string != '') {
  99. $this->_shortURL = $string;
  100. }
  101. elseif(!empty($this->_short)) {
  102. $this->_shortURL = SELFPASTE_URL.'/'.$this->_short;
  103. }
  104. }
  105. /**
  106. * set the right storage path based on _saveFilename
  107. * and SELFPASTE_UPLOAD_DIR
  108. *
  109. * @return void
  110. */
  111. public function setStoragePath(): void {
  112. $string = $this->_saveFilename;
  113. if(!empty($string)) {
  114. $p = SELFPASTE_UPLOAD_DIR.'/';
  115. $p .= Summoner::forwardslashStringToPath($string);
  116. $this->_storagePath = $p;
  117. }
  118. }
  119. /**
  120. * After setting importing stuff process the upload
  121. * return status and message
  122. *
  123. * @return array
  124. */
  125. public function process(): array {
  126. $ret = array(
  127. 'message' => '',
  128. 'status' => false
  129. );
  130. try {
  131. $ret = $this->_checkFlood();
  132. $ret = $this->_checkFileUploadStatus();
  133. $ret = $this->_checkAllowedFiletype();
  134. $ret = $this->_checkStorage();
  135. $ret = $this->_moveUploadedFile();
  136. $this->_checkLifetime();
  137. }
  138. catch (Exception $e) {
  139. $ret['message'] = $e->getMessage();
  140. }
  141. return $ret;
  142. }
  143. /**
  144. * Cleans lifetime and floodfiles.
  145. *
  146. * @param bool $verbose
  147. * @return void
  148. */
  149. public function cleanupCronjob(bool $verbose=false): void {
  150. $this->_cleanupFloodFiles($verbose);
  151. $this->_checkLifetime($verbose);
  152. }
  153. /**
  154. * Check if the POST upload worked
  155. *
  156. * @return array message,status
  157. * @throws Exception
  158. */
  159. private function _checkFileUploadStatus(): array {
  160. $check = Summoner::checkFileUploadStatus($this->_uploadedData['error']);
  161. if($check['status'] === true) {
  162. # check has the structure we want already
  163. return $check;
  164. }
  165. else {
  166. throw new Exception($check['message']);
  167. }
  168. }
  169. /**
  170. * Check if the uploaded file matches the allowed filetypes
  171. *
  172. * @return array message,status
  173. * @throws Exception
  174. */
  175. private function _checkAllowedFiletype(): array {
  176. $message = "Filetype not supported";
  177. $status = false;
  178. $workWith = $this->_uploadedData['tmp_name'];
  179. if(!empty($workWith)) {
  180. $finfo = finfo_open(FILEINFO_MIME_TYPE);
  181. $mime = finfo_file($finfo, $workWith);
  182. finfo_close($finfo);
  183. if(str_contains(SELFPASTE_ALLOWED_FILETYPES, $mime)) {
  184. $status = true;
  185. $message = "Filetype allowed";
  186. }
  187. else {
  188. if(DEBUG) $message .= " $mime";
  189. throw new Exception($message);
  190. }
  191. } else {
  192. throw new Exception($message);
  193. }
  194. return array(
  195. 'message' => $message,
  196. 'status' => $status
  197. );
  198. }
  199. /**
  200. * check if SELFPASTE_UPLOAD_DIR and _storagePath
  201. * is creatable. If so create _storagePath
  202. *
  203. * @return array message,status
  204. * @throws Exception
  205. */
  206. private function _checkStorage(): array {
  207. $message = "File storage failure";
  208. $status = false;
  209. $workwith = $this->_storagePath;
  210. if(is_writable(SELFPASTE_UPLOAD_DIR)) {
  211. if (mkdir($workwith,0777,true)) {
  212. $message = "File storage creation success";
  213. $status = true;
  214. }
  215. else {
  216. if(DEBUG) $message .= " ".$workwith;
  217. throw new Exception($message);
  218. }
  219. }
  220. else {
  221. throw new Exception('Storage location not writeable');
  222. }
  223. return array(
  224. 'message' => $message,
  225. 'status' => $status
  226. );
  227. }
  228. /**
  229. * Move the tmp_file from _uploadedData to the new location
  230. * provided by _storagePath and _saveFilename
  231. *
  232. * @return array message,status
  233. * @throws Exception
  234. */
  235. private function _moveUploadedFile(): array {
  236. $message = "File storage failure";
  237. $status = false;
  238. $workwithPath = $this->_storagePath;
  239. $workwithFilename = $this->_saveFilename;
  240. if(!empty($workwithPath) && !empty($workwithFilename)) {
  241. $_newFilename = str_ends_with($workwithPath,'/') ? $workwithPath : $workwithPath.'/';
  242. $_newFilename .= $workwithFilename;
  243. if(move_uploaded_file($this->_uploadedData['tmp_name'], $_newFilename)) {
  244. $status = true;
  245. $message = $this->_shortURL;
  246. }
  247. else {
  248. if(DEBUG) $message .= " $_newFilename";
  249. throw new Exception($message);
  250. }
  251. }
  252. else {
  253. throw new Exception('Failing requirements for saving');
  254. }
  255. return array(
  256. 'message' => $message,
  257. 'status' => $status
  258. );
  259. }
  260. /**
  261. * check if the current paste request is within limits
  262. * for this check if the file exists. If so just return the shortURL
  263. *
  264. * @return array message,status
  265. * @throws Exception
  266. */
  267. private function _checkFlood(): array {
  268. $message = "Failing flood requirements";
  269. $status = false;
  270. $this->_cleanupFloodFiles();
  271. if(!empty($this->_uploadedData['name']) && !empty($this->_shortURL)) {
  272. $filename = md5($_SERVER['REMOTE_ADDR'].$this->_uploadedData['name']);
  273. $filepath = SELFPASTE_UPLOAD_DIR.'/'.$filename;
  274. if(!file_exists($filepath)) {
  275. if(file_put_contents($filepath,$this->_shortURL)) {
  276. $status = true;
  277. $message = $this->_shortURL;
  278. }
  279. else {
  280. throw new Exception("Failed flood prevention requirements");
  281. }
  282. }
  283. else {
  284. $message = file_get_contents($filepath);
  285. throw new Exception($message);
  286. }
  287. }
  288. else {
  289. throw new Exception($message);
  290. }
  291. return array(
  292. 'message' => $message,
  293. 'status' => $status
  294. );
  295. }
  296. /**
  297. * clean up the flood tmp files. Everything older then 30 sec will be deleted.
  298. *
  299. * @param bool $verbose
  300. */
  301. private function _cleanupFloodFiles(bool $verbose=false): void {
  302. $iterator = new DirectoryIterator(SELFPASTE_UPLOAD_DIR);
  303. $now = time();
  304. foreach ($iterator as $file) {
  305. if($file->isDot() || $file->isDir() || str_starts_with($file->getFilename(),'.')) continue;
  306. if ($now - $file->getCTime() >= SELFPASTE_FLOOD_LIFETIME) {
  307. if($verbose === true) echo "Delete ".$file->getFilename()."\n";
  308. unlink(SELFPASTE_UPLOAD_DIR.'/'.$file->getFilename());
  309. }
  310. }
  311. }
  312. /**
  313. * delete all pastes older than SELFPASTE_PASTE_LIFETIME
  314. *
  315. * @param bool $verbose
  316. */
  317. private function _checkLifetime(bool $verbose=false): void {
  318. $iterator = new RecursiveDirectoryIterator(SELFPASTE_UPLOAD_DIR);
  319. $datepointInThePastInSec = strtotime('-'.SELFPASTE_PASTE_LIFETIME.' days');
  320. foreach (new RecursiveIteratorIterator($iterator) as $file) {
  321. $fname = $file->getFilename();
  322. if($file->isDir()
  323. || str_starts_with($file->getFilename(),'.')
  324. || isset($fname[4])
  325. ) continue;
  326. if ($file->getMTime() <= $datepointInThePastInSec) {
  327. if(is_writable($file->getPathname())) {
  328. if($verbose === true) echo "Delete ".$file->getPathname()."\n";
  329. unlink($file->getPathname());
  330. }
  331. }
  332. }
  333. }
  334. }