/code/classes/pinetd/Filesystem.class.php

https://github.com/blekkzor/pinetd2 · PHP · 359 lines · 279 code · 73 blank · 7 comment · 112 complexity · 7be9ef1ed06e332db9dea5f2346ec49b MD5 · raw file

  1. <?php
  2. namespace pinetd;
  3. class Filesystem {
  4. protected $cwd = '/';
  5. protected $root = NULL;
  6. protected $options;
  7. public function setOptions($options) {
  8. $this->options = $options;
  9. }
  10. public function getCwd() {
  11. $dir = $this->cwd;
  12. if (substr($dir, -1) != '/') $dir.='/';
  13. $root = $this->root;
  14. if (substr($root, -1) != '/') $root.='/';
  15. if (substr($dir, 0, strlen($root)) != $root) {
  16. $this->chDir('/');
  17. $cwd = '/';
  18. } else {
  19. $cwd = substr($dir, strlen($root)-1);
  20. if (strlen($cwd) > 1) $cwd = substr($cwd, 0, -1); // strip trailing /
  21. }
  22. return $cwd;
  23. }
  24. public function isWritable($file) {
  25. if (!$this->options['write_level']) return true;
  26. if ($file[0] != '/') $file = $this->cwd.'/'.$file;
  27. if ($file[0] != '/') $file = '/'.$file;
  28. while($file_p != $file) {
  29. $file_p = $file;
  30. $file = preg_replace('#/{2,}#', '/', $file);
  31. $file = preg_replace('#/\\.\\./[^/]+/#', '/', $file);
  32. $file = preg_replace('#/[^/]+/\\.\\./#', '/', $file);
  33. }
  34. $count = count(explode('/', $file))-2;
  35. if ($count < $this->options['write_level']) return false;
  36. return true;
  37. }
  38. public function chDir($dir) {
  39. $new_dir = $this->convertPath($dir);
  40. if ((is_null($new_dir)) || ($new_dir === false)) return false;
  41. $new_dir = $this->root . $new_dir;
  42. if (!is_dir($new_dir)) return false;
  43. $this->cwd = $new_dir;
  44. return true;
  45. }
  46. /**
  47. * \brief Convert a given path by resolving links, and make sure we stay within the FTP root
  48. */
  49. protected function convertPath($path, $cwd = null, $depth = 0) {
  50. $final_path = array();
  51. if ($path[0] != '/') {
  52. // start with CWD value splitted
  53. if (is_null($cwd)) $cwd = $this->getCwd();
  54. $start = explode('/', $cwd);
  55. foreach($start as $elem) if ($elem != '') $final_path[] = $elem;
  56. }
  57. $path = explode('/', $path);
  58. $final_elem = array_pop($path);
  59. $path[] = NULL;
  60. foreach($path as $elem) {
  61. if (is_null($elem)) {
  62. $final = true;
  63. $elem = $final_elem;
  64. } else {
  65. $final = false;
  66. }
  67. if (($elem == '') || ($elem == '.')) continue; // no effect
  68. if ($elem == '..') {
  69. array_pop($final_path);
  70. continue;
  71. }
  72. $realpath = $this->root . '/' . implode('/', $final_path). '/' . $elem; // final path to $elem
  73. if ((!$final) && (!file_exists($realpath))) return false;
  74. if (is_link($realpath)) {
  75. if ($depth > 15) {
  76. // WTF?!!!
  77. return NULL;
  78. }
  79. $link = readlink($realpath);
  80. if ($link[0] == '/') {
  81. $new_path = $this->convertPath($link, $this->root . '/' . implode('/', $final_path), $depth + 1);
  82. } else {
  83. $new_path = $this->convertPath($link, $this->root . '/' . implode('/', $final_path) . '/' . $link, $depth + 1);
  84. }
  85. if (is_null($new_path)) return NULL; // infinite symlink?
  86. $new_path = explode('/', $new_path);
  87. $final_path = array();
  88. foreach($new_path as $xelem) if ($xelem != '') $final_path[] = $xelem;
  89. continue;
  90. }
  91. $final_path[] = $elem;
  92. }
  93. return '/' . implode('/', $final_path);
  94. }
  95. public function setRoot($root, $can_chroot = false) {
  96. if (!chdir($root)) return false;
  97. if ($can_chroot) {
  98. if (!chroot($root)) return false;
  99. $this->root = '/';
  100. return true;
  101. }
  102. $this->root = $root;
  103. return true;
  104. }
  105. public function realpath($path) {
  106. $fil = $this->convertPath($path);
  107. if ((is_null($fil)) || ($fil === false)) return NULL;
  108. return $fil;
  109. }
  110. public function openDir($dir) {
  111. $fil = $this->convertPath($dir);
  112. if ((is_null($fil)) || ($fil === false)) return NULL;
  113. $full_path = $this->root . $fil;
  114. clearstatcache();
  115. $dir = @opendir($full_path);
  116. if (!$dir) return NULL;
  117. return array('type' => 'dir', 'handle' => $dir, 'path' => $full_path);
  118. }
  119. public function readDir($dir) {
  120. if ($dir['type'] != 'dir') return false;
  121. $fil = readdir($dir['handle']);
  122. if ($fil === false) return NULL;
  123. return $this->_stat($dir['path'].'/'.$fil);
  124. }
  125. public function closeDir($dir) {
  126. if ($dir['type'] != 'dir') return false;
  127. closedir($dir['handle']);
  128. return true;
  129. }
  130. public function listDir($dir) {
  131. $fil = $this->convertPath($dir);
  132. if ((is_null($fil)) || ($fil === false)) return NULL;
  133. $full_path = $this->root . $fil;
  134. clearstatcache();
  135. $dir = @opendir($full_path);
  136. if (!$dir) return NULL;
  137. $res = array();
  138. while(($fil = readdir($dir)) !== false) {
  139. $res[] = $this->_stat($full_path.'/'.$fil);
  140. }
  141. closedir($dir);
  142. return $res;
  143. }
  144. public function stat($fil) {
  145. $fil = $this->convertPath($fil);
  146. if ((is_null($fil)) || ($fil === false)) return false;
  147. return $this->_stat($this->root . $fil);
  148. }
  149. protected function _basename($name, $ext = '') {
  150. $name = explode('/', $name);
  151. $name = array_pop($name);
  152. if ((strlen($ext) > 0) && (substr($name, 0 - strlen($ext)) == $ext))
  153. $name = substr($name, 0, 0 - strlen($ext));
  154. return $name;
  155. }
  156. protected function _stat($fil, $follow = true) {
  157. if ($follow) {
  158. $stat = @stat($fil);
  159. } else {
  160. $stat = @lstat($file);
  161. }
  162. if (!$stat) return false;
  163. $flag = '-rwx';
  164. if (is_dir($fil)) $flag='drwx';
  165. if (is_link($fil)) $flag='lrwx';
  166. $mode=substr(decbin($stat["mode"]),-3);
  167. if (substr($mode,0,1)=="1") $xflg ="r"; else $xflg ="-";
  168. if (substr($mode,1,1)=="1") $xflg.="w"; else $xflg.="-";
  169. if (substr($mode,2,1)=="1") $xflg.="x"; else $xflg.="-";
  170. $flag.=$xflg.$xflg;
  171. $blocks=$stat["nlink"];
  172. // FTP-like stat line
  173. list($year, $month, $day, $hour, $mins) = explode('|', date('Y|M|d|H|i', $stat['mtime']));
  174. // $timeline: same year: "HH:SS". Other: " YYYY" (%5d)
  175. if ($year == date('Y')) {
  176. $timeline = sprintf('%02d:%02d', $hour, $mins);
  177. } else {
  178. $timeline = sprintf(' %04d', $year);
  179. }
  180. $res = sprintf('%s %3u %-8d %-8d %8u %s %2d %s %s',
  181. $flag,
  182. $stat['nlink']?:1, /* TODO: nlinks */
  183. 0, /* owner id */
  184. 0, /* group id */
  185. $stat['size'], /* size */
  186. $month, /* month name */
  187. $day,
  188. $timeline,
  189. $this->_basename($fil)
  190. );
  191. if (is_link($fil)) {
  192. $res.=" -> ".readlink($fil);
  193. }
  194. $data = array(
  195. 'name' => $this->_basename($fil),
  196. 'flags' => $flag,
  197. 'mode' => $stat['mode'],
  198. 'blocks' => $blocks,
  199. 'size' => $stat['size'],
  200. 'atime' => $stat['atime'],
  201. 'mtime' => $stat['mtime'],
  202. 'text' => $res,
  203. );
  204. if (is_link($fil)) $data['link'] = readlink($fil);
  205. return $data;
  206. }
  207. public function open($file, $mode) {
  208. if ($mode[0] != 'r') {
  209. if (!$this->isWritable($file)) return false;
  210. }
  211. $fil = $this->convertPath($file);
  212. if ((is_null($fil)) || ($fil === false)) return false;
  213. $fil = $this->root . $fil;
  214. return fopen($fil, $mode);
  215. }
  216. public function close($fp) {
  217. if (is_resource($fp)) {
  218. fclose($fp);
  219. return true;
  220. }
  221. switch($fp['type']) {
  222. case 'dir': closedir($fp['handle']); break;
  223. case 'file': fclose($fp['handle']); break;
  224. default: return false;
  225. }
  226. return true;
  227. }
  228. public function doRecursiveRMD($dir) {
  229. if (!$this->isWritable($dir)) return false;
  230. $fil = $this->convertPath($fullarg);
  231. if ((is_null($fil)) || ($fil === false)) return false;
  232. return $this->_doRecursiveRMD($this->root . $fil);
  233. }
  234. private function _doRecursiveRMD($dir) {
  235. $dh = opendir($dir);
  236. while(($fil = readdir($dh)) !== false) {
  237. if (($fil == '.') || ($fil == '..')) continue;
  238. $f = $dir.'/'.$fil;
  239. if (is_dir($f)) {
  240. $this->_doRecursiveRMD($f);
  241. } else {
  242. @unlink($f);
  243. }
  244. }
  245. closedir($dh);
  246. @rmdir($dir);
  247. return true;
  248. }
  249. public function rmDir($dir) {
  250. if (!$this->isWritable($dir)) return false;
  251. $fil = $this->convertPath($dir);
  252. if ((is_null($fil)) || ($fil === false)) return false;
  253. return @rmdir($this->root . $fil);
  254. }
  255. public function mkDir($dir, $mode = 0777) {
  256. if (!$this->isWritable($dir)) return false;
  257. $fil = $this->convertPath($dir);
  258. if ((is_null($fil)) || ($fil === false)) return false;
  259. return @mkdir($this->root . $fil, $mode);
  260. }
  261. public function size($fil) {
  262. $fil = $this->convertPath($fil);
  263. if ((is_null($fil)) || ($fil === false)) return false;
  264. return @filesize($this->root . $fil);
  265. }
  266. public function unLink($fil) {
  267. if (!$this->isWritable($fil)) return false;
  268. $fil = $this->convertPath($fil);
  269. if ((is_null($fil)) || ($fil === false)) return false;
  270. return @unlink($this->root . $fil);
  271. }
  272. public function rename($from, $to) {
  273. if (!$this->isWritable($from)) return false;
  274. if (!$this->isWritable($to)) return false;
  275. $from = $this->convertPath($from);
  276. if ((is_null($from)) || ($from === false)) return false;
  277. $to = $this->convertPath($to);
  278. if ((is_null($to)) || ($to === false)) return false;
  279. return @rename($this->root . $from, $this->root . $to);
  280. }
  281. public function fileExists($fil) {
  282. $fil = $this->convertPath($fil);
  283. if ((is_null($fil)) || ($fil === false)) return false;
  284. return file_exists($this->root . $fil);
  285. }
  286. }