PageRenderTime 318ms CodeModel.GetById 130ms app.highlight 92ms RepoModel.GetById 80ms app.codeStats 1ms

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