mancubus.class.php 9.2 KB

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