From 2509155cc27254467aa3517d37f88e2879e90291 Mon Sep 17 00:00:00 2001 From: Dominique Fournier Date: Thu, 14 Apr 2016 11:48:58 +0000 Subject: [PATCH] file : all the management of the files, with integrated filesystem checks, and virtual chroot support git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@2694 bf3deb0d-5f1a-0410-827f-c0cc1f45334c --- Tests/fileTest.php | 441 +++++++++++++++++++++++++++++++++++ file.php | 560 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1001 insertions(+) create mode 100644 Tests/fileTest.php create mode 100644 file.php diff --git a/Tests/fileTest.php b/Tests/fileTest.php new file mode 100644 index 0000000..84999c1 --- /dev/null +++ b/Tests/fileTest.php @@ -0,0 +1,441 @@ + */ + +/** Test the domframework file part */ +class test_file extends PHPUnit_Framework_TestCase +{ + public function testinit () + { + exec ("rm -rf /tmp/testDFFileDir"); + } + + public function testRealPath01 () + { + $file = new file (); + $res = $file->realpath ("/tmp"); + $this->assertSame($res, "/tmp"); + } + + public function testRealPath02 () + { + $file = new file (); + $res = $file->realpath ("////tmp"); + $this->assertSame($res, "/tmp"); + } + + public function testRealPath03 () + { + $file = new file (); + $res = $file->realpath ("////tmp////"); + $this->assertSame($res, "/tmp"); + } + + public function testRealPath04 () + { + $file = new file (); + $res = $file->realpath (".////tmp////"); + $this->assertSame($res, "./tmp"); + } + + public function testRealPath05 () + { + $file = new file (); + $res = $file->realpath ("../../../../../tmp/file"); + $this->assertSame($res, "/tmp/file"); + } + + public function testRealPath06 () + { + $file = new file (); + $res = $file->realpath ("../../../../../tmp/file/../../../.."); + $this->assertSame($res, "/"); + } + + public function testRealPath07 () + { + $file = new file (); + $res = $file->realpath (".././././../tmp/file/../././.."); + $this->assertSame($res, "/"); + } + + public function testRealPath11 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->realpath ("/tmp2"); + $this->assertSame($res, "/tmp/tmp2"); + } + + public function testRealPath12 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->realpath ("////tmp2"); + $this->assertSame($res, "/tmp/tmp2"); + } + + public function testRealPath13 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->realpath ("////tmp2////"); + $this->assertSame($res, "/tmp/tmp2"); + } + + public function testRealPath14 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->realpath (".////tmp2////"); + $this->assertSame($res, "/tmp/tmp2"); + } + + public function testRealPath15 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->realpath ("../../../../../tmp2/file"); + $this->assertSame($res, "/tmp/tmp2/file"); + } + + public function testRealPath16 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->realpath ("../../../../../tmp2/file/../../../.."); + $this->assertSame($res, "/tmp"); + } + + public function testRealPath17 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->realpath (".././././../tmp2/file/../././.."); + $this->assertSame($res, "/tmp"); + } + + + public function testGetcwd1 () + { + $file = new file (); + $res = $file->getcwd (); + $this->assertSame($res, "."); + } + + public function testGetcwd2 () + { + $file = new file (); + $file->chdir ("/tmp"); + $res = $file->getcwd (); + $this->assertSame($res, "/tmp"); + } + + public function testGetcwd3 () + { + $this->setExpectedException ("Exception", "Path '/NON EXISTS' not found"); + $file = new file (); + $file->chdir ("/NON EXISTS"); + $res = $file->getcwd (); + } + public function testGetcwd4 () + { + $file = new file (); + $file->chdir ("/../../../tmp"); + $res = $file->getcwd (); + $this->assertSame($res, "/tmp"); + } + + public function testGetcwd5 () + { + $file = new file (); + $file->chdir ("../../../../../../tmp"); + $res = $file->getcwd (); + $this->assertSame($res, "/tmp"); + } + + public function testGetcwd6 () + { + $this->setExpectedException ("Exception", "Path './tmp' not found"); + $file = new file (); + $file->chdir ("./tmp"); + $res = $file->getcwd (); + } + + public function testGetcwd7 () + { + $this->setExpectedException ("Exception", + "Path '/tmp/NON EXISTS' not found"); + $file = new file (); + $file->chdir ("/tmp/NON EXISTS"); + $res = $file->getcwd (); + } + + public function testGetcwd8 () + { + $this->setExpectedException ("Exception", + "Parent Path '/NON EXISTS/' not found"); + $file = new file (); + $file->chdir ("/NON EXISTS/tmp"); + $res = $file->getcwd (); + } + + public function testchroot1 () + { + $this->setExpectedException ("Exception"); + $file = new file (); + $res = $file->chroot ("non exists"); + } + + public function testchroot2 () + { + $file = new file (); + $res = $file->chroot ("/tmp"); + $this->assertSame($res, true); + } + + // Without chroot + public function testMkdir1 () + { + $file = new file (); + $res = $file->mkdir ("/tmp/testmkdir1-".time()); + $this->assertSame($res, true); + } + + // With chroot in tmp + public function testMkdir2 () + { + $file = new file (); + $res = $file->chroot ("/tmp"); + $res = $file->mkdir ("/testmkdir2-".time()); + $this->assertSame($res, true); + } + + // Without chroot and relative + public function testMkdir3 () + { + $file = new file (); + $res = $file->mkdir ("/tmp/testmkdir3-".time()); + $this->assertSame($res, true); + } + + // With chroot in tmp and in relative + public function testMkdir4 () + { + $file = new file (); + $res = $file->chroot ("/tmp"); + $res = $file->mkdir ("testmkdir4-".time()); + $this->assertSame($res, true); + } + + // With chroot in tmp + public function testChdir1 () + { + @rmdir ("/tmp/testDFFileDir"); + $file = new file (); + $file->chroot ("/tmp"); + $file->mkdir ("/testDFFileDir"); + $file->chdir ("/testDFFileDir"); + $res = $file->getcwd (); + $this->assertSame($res, "/testDFFileDir"); + } + + // With chroot in tmp + public function testChdir2 () + { + @rmdir ("/tmp/testDFFileDir"); + $file = new file (); + $file->chroot ("/tmp"); + $file->mkdir ("/testDFFileDir"); + $file->chroot ("/"); + $file->chdir ("/testDFFileDir"); + $res = $file->getcwd (); + $this->assertSame($res, "/testDFFileDir"); + } + + // With chroot in tmp + public function testChdir3 () + { + $this->setExpectedException ("Exception", + "Path '/tmp/testDFFileDir/NON EXISTS' not found"); + @rmdir ("/tmp/testDFFileDir"); + $file = new file (); + $file->chroot ("/tmp"); + $file->mkdir ("/testDFFileDir"); + $file->chroot ("/testDFFileDir"); + $file->chdir ("/NON EXISTS"); + } + + // With chroot in tmp + public function testChdir4 () + { + $this->setExpectedException ("Exception", + "Parent Path '/tmp/tmp/' not found"); + @rmdir ("/tmp/testDFFileDir"); + $file = new file (); + $file->chroot ("/tmp"); + $file->mkdir ("/testDFFileDir"); + $file->chroot (".."); + $file->chdir ("/tmp/testDFFileDir"); + } + + public function testFile_get_contents1 () + { + $this->setExpectedException ("Exception", + "File '/tmp/testDFFileDir' is not a file"); + @rmdir ("/tmp/testDFFileDir"); + $file = new file (); + $file->chroot ("/tmp"); + $file->mkdir ("/testDFFileDir"); + $res = $file->file_get_contents ("/testDFFileDir"); + } + + public function testFile_get_contents2 () + { + @rmdir ("/tmp/testDFFileDir"); + $file = new file (); + $file->chroot ("/tmp"); + $file->mkdir ("/testDFFileDir"); + $file->touch ("/testDFFileDir/toto"); + $res = $file->file_get_contents ("/testDFFileDir/toto"); + $this->assertSame($res, ""); + } + + public function testFile_put_contents1 () + { + $file = new file (); + $file->chroot ("/tmp"); + $file->file_put_contents ("/testDFFileDir/toto", "content"); + $res = $file->file_get_contents ("/testDFFileDir/toto"); + $this->assertSame($res, "content"); + } + + public function testFile_put_contents2 () + { + $this->setExpectedException ("Exception", + "File '/tmp/testDFFileDir' is not a file"); + $file = new file (); + $file->file_put_contents ("/tmp/testDFFileDir", "content"); + } + + public function testFile_put_contents3 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->file_put_contents ("/testDFFileDir/toto", "content"); + $this->assertSame($res, 7); + } + + public function testscandir1 () + { + $file = new file (); + $res = $file->scandir ("/tmp/testDFFileDir"); + $this->assertSame ($res, array ("toto")); + } + + public function testscandir2 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->scandir ("/testDFFileDir"); + $this->assertSame ($res, array ("toto")); + } + + public function testscandir3 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->scandir ("/testDFFileDir/"); + $this->assertSame ($res, array ("toto")); + } + + public function testrmdir1 () + { + // Directory not empty and NOT recursive : return false + $file = new file (); + $res = $file->rmdir ("/tmp/testDFFileDir"); + $this->assertSame ($res, false); + } + + public function testrmdir2 () + { + // Directory not empty and NOT recursive : return false + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->rmdir ("/testDFFileDir"); + $this->assertSame ($res, false); + } + + public function testrmdir3 () + { + // Directory not empty and recursive : return true + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->rmdir ("/testDFFileDir", true); + $this->assertSame ($res, true); + } + + public function testUnlink1 () + { + @rmdir ("/tmp/testDFFileDir"); + $file = new file (); + $file->chroot ("/tmp"); + $file->mkdir ("/testDFFileDir"); + $file->touch ("/testDFFileDir/toto"); + $res = $file->unlink ("/testDFFileDir/toto"); + $this->assertSame ($res, true); + } + + public function testIsDir1 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->is_dir ("//testDFFileDir"); + $this->assertSame ($res, true); + } + + public function testIsDir2 () + { + $file = new file (); + $file->chroot ("/tmp"); + $file->touch ("/testDFFileDir/toto"); + $res = $file->is_dir ("//testDFFileDir/toto"); + $this->assertSame ($res, false); + } + + public function testIsFile1 () + { + $file = new file (); + $file->chroot ("/tmp"); + $file->touch ("/testDFFileDir/toto"); + $res = $file->is_file ("/testDFFileDir/toto"); + $this->assertSame ($res, true); + } + + public function testIsFile2 () + { + $file = new file (); + $file->chroot ("/tmp"); + $file->touch ("/testDFFileDir/toto"); + $res = $file->is_file ("//testDFFileDir"); + $this->assertSame ($res, false); + } + + public function testMkdir5 () + { + $file = new file (); + $file->chroot ("/tmp"); + // The parent doesn't exists and not recursive mode : exception + $this->expectException (); + $res = $file->mkdir ("/testDFFileDir/tptp/titi/poo"); + } + + public function testMkdir6 () + { + $file = new file (); + $file->chroot ("/tmp"); + $res = $file->mkdir ("/testDFFileDir/tptp/titi/poo", 0777, true); + $this->assertSame ($res, true); + } +} diff --git a/file.php b/file.php new file mode 100644 index 0000000..f1e822f --- /dev/null +++ b/file.php @@ -0,0 +1,560 @@ + */ + +/** The file method allow to manage files like PHP with a working chroot on all + * plateforms, and a right management compatible with database + * Don't follow links ! + * + * To allow an external authorization test in plus of the filesystem check, you + * must extends the class and overload checkExternalPathRO and + * checkExternalPathRW. + */ +class file +{ + /** The virtual current working directory */ + private $cwd = "."; + /** The real directory used as root in virtual chroot */ + private $baseDir = "/"; + + /** The lock stack */ + private $locks = array (); + + /** Activate the debug and define the minimum priority to save */ + public $debug = 0; + + /** Change the current working directory + * @return bool true if the directory is changed + * @throws If directory not exists, or the directory is not executable + */ + public function chdir ($directory) + { + $this->debug (2, "chdir ($directory)"); + $tmpdirectory = $this->realpath ($directory); + $this->checkPathRO ($tmpdirectory); + if ($this->baseDir === "/") + $this->cwd = $tmpdirectory; + else + $this->cwd = substr ($tmpdirectory, strlen ($this->baseDir)); + $this->debug (1, "chdir $directory -> $this->cwd"); + return true; + } + + /** Chroot in the provided directory + * @param $directory string The directory to chroot + * @return true if the chroot is done, false if there is a failure + * @throws If directory not exists, or the directory is not executable + */ + public function chroot ($directory) + { + // Use the checkPathRO (using the $this->baseDir) to not allow to go away of + // the chroot. + $this->debug (2, "chroot ($directory)"); + $directory = $this->realpath ($directory); + $this->checkPathRO ($directory); + $this->baseDir = preg_replace ("#//+#", "/", $directory); + $this->cwd = "/"; + $this->debug (1, "chroot $directory -> $this->baseDir"); + return true; + } + + /** Checks whether a file or directory exists + * @param string $filename The file or directory to verify + * @return bool true if the file exists, false otherwise + * @throws If parent directory not exists, or is not executable + */ + public function file_exists ($filename) + { + $this->debug (2, "file_exists ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRO (dirname ($filename)); + if (file_exists ($filename) && ! is_link ($filename)) + return true; + return false; + } + + /** Get the file contents + * @param string $filename Name of the file to read + * @return string Content of the file + * @throws If parent directory not exists, is not readable, the file is not + * exists or is not readable + */ + public function file_get_contents ($filename) + { + $this->debug (2, "file_get_contents ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRO (dirname ($filename)); + if (! is_file ($filename)) + throw new \Exception (sprintf (dgettext ("domframework", + "File '%s' is not a file"), + $filename), 500); + if (! is_readable ($filename)) + throw new \Exception (sprintf (dgettext ("domframework", + "File '%s' is not readable"), + $filename), 500); + $contents = file_get_contents ($filename); + $this->debug (1, "file_get_contents ($filename) => ".strlen ($contents). + " bytes"); + return $contents; + } + + /** Write a string to a file + * @param string $filename Path to the file where to write the data + * @param The data to write + * @return the length of the data stored + * @throws If parent directory not exists, is not writeable, or the file + * exists and is not writeable + */ + public function file_put_contents ($filename, $data) + { + $this->debug (2, "file_put_contents ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRW (dirname ($filename)); + if (file_exists ($filename) && ! is_writeable ($filename)) + throw new \Exception (sprintf (dgettext ("domframework", + "File '%s' is not writeable"), + $filename), 500); + if (file_exists ($filename) && ! is_file ($filename)) + throw new \Exception (sprintf (dgettext ("domframework", + "File '%s' is not a file"), + $filename), 500); + $contents = file_put_contents ($filename, $data); + $this->debug (1, "file_put_contents ($filename, \$data) => ". + "$contents bytes"); + return $contents; + + } + + /** Return the current working directory + * @return string the current working directory */ + public function getcwd () + { + $this->debug (1, "getcwd $this->cwd"); + return $this->cwd; + } + + /** Tells whether the given filename is a directory + * @param string $filename The filename to test + * @return bool true if the $filename is a directory and exists, false + * otherwise + * @throws If parent directory not exists, or is not executable + */ + public function is_dir ($filename) + { + $this->debug (2, "is_dir ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRO (dirname ($filename)); + if (file_exists ($filename) && is_dir ($filename)) + return true; + return false; + } + + /** Tells whether the given filename is a valid file + * @param string $filename The filename to test + * @return bool true if the $filename is a file and exists, false otherwise + * @throws If parent directory not exists, or is not executable + */ + public function is_file ($filename) + { + $this->debug (2, "is_file ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRO (dirname ($filename)); + if (file_exists ($filename) && is_file ($filename)) + return true; + return false; + } + + + /** Lock a file exclusively + * @param $filename The file to lock + * @return bool true if the lock is acquired, false otherwise + * @throws If parent directory not exists, or is not writeable + */ + public function lockEX ($filename) + { + $this->debug (2, "lockEX ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRW (dirname ($filename)); + $this->locks[$filename] = fopen ($filename, "rt"); + return flock ($this->locks[$filename], LOCK_EX); + } + + /** Lock a file shared (allow multiple read) + * @param $filename The file to lock + * @return bool true if the lock is acquired, false otherwise + * @throws If parent directory not exists, or is not writeable + */ + public function lockSH ($filename) + { + $this->debug (2, "lockSH ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRW (dirname ($filename)); + $this->locks[$filename] = fopen ($filename, "rt"); + return flock ($this->locks[$filename], LOCK_SH); + } + + /** Unlock a file previously locked + * @param $filename The file to lock + * @return bool true if the lock is acquired, false otherwise + * @throws If parent directory not exists, or is not writeable + */ + public function lockUN ($filename) + { + $this->debug (2, "lockUN ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRW (dirname ($filename)); + $res = true; + if (isset ($this->locks[$filename])) + { + $res = flock ($this->locks[$filename], LOCK_UN); + fclose ($this->locks[$filename]); + } + return $res; + } + + /** Create a new directory + * @param $pathname The directory to create + * @param $mode The mode to create (0777 by default) + * @param $recursive (false by default) + * @return bool true if the directory is correctely created, false if the + * directory already exists + * @throws If parent directory not exists, is not writeable + */ + public function mkdir ($pathname, $mode = 0777, $recursive = false) + { + $this->debug (2, "mkdir ($pathname, $mode, $recursive)"); + $pathname = $this->realpath ($pathname); + if ($recursive) + { + $parents = explode ("/", $pathname); + array_pop ($parents); + $parent = ""; + foreach ($parents as $p) + { + $parent = $parent.$p."/"; + if (! file_exists ($parent)) + { + if (is_writeable (dirname ($parent))) + break; + throw new \Exception (sprintf ("Last Directory '%s' is readonly", + dirname ($parent)), 500); + } + } + if ($parent === dirname ($pathname) && ! is_writeable (dirname ($parent))) + { + throw new \Exception (sprintf ("Parent directory '%s' is readonly", + dirname ($parent)), 500); + } + } + else + { + $this->checkPathRW (dirname ($pathname)); + } + if (file_exists ($pathname)) + throw new \Exception (sprintf (dgettext ("domframework", + "Directory '%s' already exists"), + $pathname), 500); + $rc = mkdir ($pathname, $mode, $recursive); + $this->debug (1, "mkdir ($pathname, $mode, $recursive) => $rc"); + return $rc; + } + + /** Return a ini file converted to an array + * @param string $filename The filename of the ini file being parsed. + * @param bool $process_sections Process the sections + * @return array + */ + public function parse_ini_file ($filename, $process_sections = false) + { + $this->debug (2, "parse_ini_file ($filename, $process_sections)"); + $filename = $this->realpath ($filename); + $this->checkPathRO (dirname ($filename)); + if (! is_file ($filename)) + throw new \Exception (sprintf (dgettext ("domframework", + "File '%s' is not a file"), + $filename), 500); + if (! is_readable ($filename)) + throw new \Exception (sprintf (dgettext ("domframework", + "File '%s' is not readable"), + $filename), 500); + return parse_ini_file ($filename, $process_sections); + } + + /** Return the canonical absolute path. Do not check if the directory exists, + * if there is links. Just calculate the realpath based on the chroot value + * @param string $path the path to analyze + * @return string the canonical absolute path + */ + public function realpath ($path) + { + $oriPath = $path; + $this->debug (2, "realpath ($oriPath)"); + $path = preg_replace ("#//+#", "/", $path); + if (substr ($path, -1) === "/") + $path = substr ($path, 0, -1); + $parts = explode ("/", $path); + $current = $this->cwd; + $tmp = explode ("/", $current); + foreach ($parts as $part) + { + if ($part === "") + $tmp = array(); + elseif ($part === ".") + continue; + elseif ($part === "..") + { + array_pop ($tmp); + continue; + } + else + array_push ($tmp, $part); + } + if (reset ($tmp) === ".") + { + array_shift ($tmp); + $path = $current."/".implode ("/", $tmp); + } + else + $path = "/".implode ("/", $tmp); + if ($this->baseDir !== "/") + $path = $this->baseDir.$path; + $path = preg_replace ("#//+#", "/", $path); + if ($path !== "/" && substr ($path, -1) === "/") + $path = substr ($path, 0, -1); + $this->debug (1, "realpath ($oriPath) => $path"); + return $path; + } + + /** Remove the provided directory + * If the recurse flag is true, remove the content too (files and + * directories) + * @param string $dirname The directory to remove + * @param bool $recursive Remove recursively + * @return bool true if all is removed, false otherwise + * @throws If parent directory not exists, is not writeable or the current + * dir is not writeable + */ + public function rmdir ($dirname, $recursive=false) + { + $this->debug (2, "rmdir ($dirname, $recursive)"); + $tmpdirname = $this->realpath ($dirname); + $this->checkPathRW (dirname ($tmpdirname)); + $this->checkPathRW ($tmpdirname); + if ($recursive === false) + return @rmdir ($tmpdirname); + $files = array_diff (scandir ($tmpdirname), array('.','..')); + foreach ($files as $file) + { + (is_dir ("$tmpdirname/$file")) ? $this->rmdir("$dirname/$file") : + unlink ("$tmpdirname/$file"); + } + return rmdir ($tmpdirname); + } + + /** Return the list of files and directories in the directory. + * Do not return the . and .. virtual dirs. + * The result is sorted + * @param string $directory The directory to read + * @return array the list of files and dirs + * @throws If directory not exists, or is not executable + */ + public function scandir ($directory) + { + $this->debug (2, "scandir ($directory)"); + $directory = $this->realpath ($directory); + $this->checkPathRO ($directory); + $res = array_values (array_diff (scandir ($directory), array('..', '.'))); + natsort ($res); + return $res; + } + + /** Create a new file or update the timestamp if the file exists + * @param string $filename the filename + * @param int $time the timestamp to use (actual timestamp if not defined) + * @return bool true or false on failure + * @throws If parent directory not exists, is not writeable + */ + public function touch ($filename, $time = null, $atime = null) + { + $this->debug (2, "touch ($filename, $time, $atime)"); + $filename = $this->realpath ($filename); + $this->checkPathRW (dirname ($filename)); + if ($time === null) + $time = time (); + if ($atime === null) + $atime = time (); + $rc = touch ($filename, $time, $atime); + $this->debug (1, "touch ($filename, $time, $atime) => $rc"); + return $rc; + } + + /** Delete an existing file. + * @param string $filename The filename to remove + * @return bool true if the file si removed, false otherwise + * @throws If parent directory not exists, or is not executable + */ + public function unlink ($filename) + { + $this->debug (2, "unlink ($filename)"); + $filename = $this->realpath ($filename); + $this->checkPathRW (dirname ($filename)); + if (! file_exists ($filename) || ! is_writeable ($filename)) + return false; + return unlink ($filename); + } + + /** Check all the parents of the $directory if they are available, and + * executable. The path must exists. + * Must use the filesystem path (complete) and not the version in chroot. + * The last directoy must be executable and readable (no test for writeable) + * @param $path string The directory path to check + * @return true if the path is executable for all the parents and for the + * last directory + * @throws if there is a missing part, or a parent is not executable + */ + private function checkPathRO ($path) + { + $this->debug (2, "checkPathRO ($path)"); + $path = preg_replace ("#//+#", "/", $path); + $parents = explode ("/", $path); + array_pop ($parents); + $parent = ""; + foreach ($parents as $p) + { + $parent = $parent.$p."/"; + if (! file_exists ($parent)) + { + $this->debug (1, + "checkPathRO ($path) => Parent Path '$parent' not found"); + throw new \Exception (sprintf (dgettext ("domframework", + "Parent Path '%s' not found"), + $parent), 404); + } + if (! is_executable ($parent)) + { + $this->debug (1, "checkPathRO ($path) => ". + "Parent Directory '$parent' not executable"); + throw new \Exception (sprintf (dgettext ("domframework", + "Parent Directory '%s' not executable"), + $parent), 500); + } + if ($this->checkExternalPathRO ($parent) !== true) + { + $this->debug (1, "checkPathRO ($path) => ". + "Parent Directory '$parent' not accessible by ". + "external check read-only"); + throw new \Exception (sprintf (dgettext ("domframework", + "Parent Directory '%s' not accessible ". + "by external check read-only"), + $parent), 500); + } + } + if (! file_exists ($path)) + { + $this->debug (1, "checkPathRO ($path) => Path '$path' not found"); + throw new \Exception (sprintf (dgettext ("domframework", + "Path '%s' not found"), + $path), 404); + } + if (! is_dir ($path)) + { + $this->debug (1, "checkPathRO ($path) => ". + "Path '$path' is not a directory"); + throw new \Exception (sprintf (dgettext ("domframework", + "Path '%s' is not a directory"), + $path), + 500); + } + if (! is_executable ($path)) + { + $this->debug (1, "checkPathRO ($path) => ". + "Directory '$path' is not executable"); + throw new \Exception (sprintf (dgettext ("domframework", + "Directory '%s' is not executable"), + $path), 500); + } + if (! is_readable ($path)) + { + $this->debug (1, "checkPathRO ($path) => ". + "Directory '$path' is not readable"); + throw new \Exception (sprintf (dgettext ("domframework", + "Directory '%s' is not readable"), + $path), 500); + } + return $this->checkExternalPathRO ($path); + } + + /** Check all the parents of the $directory if they are available, and + * executable. The path must exists. + * Must use the filesystem path (complete) and not the version in chroot. + * The last directoy must be executable and readable and writeable + * @param $path string The directory path to check + * @return true if the path is executable for all the parents and for the + * last directory + * @throws if there is a missing part, or a parent is not executable + */ + private function checkPathRW ($path) + { + $this->debug (2, "checkPathRW ($path)"); + $this->checkPathRO ($path); + if (! is_writeable ($path)) + { + $this->debug (1, "checkPathRW ($path) => ". + "Directory '$path' is not writeable"); + throw new \Exception (sprintf (dgettext ("domframework", + "Directory '%s' is not writeable"), + $path), 500); + } + if ($this->checkExternalPathRW ($path) !== true) + { + $this->debug (1, "checkPathRW ($path) => ". + "Directory '$path' not accessible by ". + "external check read-write"); + throw new \Exception (sprintf (dgettext ("domframework", + "Directory '%s' not accessible ". + "by external check read-write"), + $path), 500); + } + return true; + } + + /** Save a debug log + * @param $prio The message priority. Should be higher than $this->debug to + * save the message + * @param $message The message to save + * @return null; + */ + private function debug ($prio, $message) + { + if ($this->debug === false || $this->debug === 0) + return; + if ($prio <= $this->debug) + { + echo "[$prio] $message\n"; + //file_put_contents ("/tmp/domframework.file.debug", + // date ("Y:m:d H:i:s")." [$prio] $message\n", + // FILE_APPEND); + } + } + + /** External function allowed to be overloaded to test the RO access to a + * resource + * @param string $path The path to test in the filesystem + * @return boolean true if RO access, false if not + */ + public function checkExternalPathRO ($path) + { + return true; + } + + /** External function allowed to be overloaded to test the RW access to a + * resource + * @param string $path The path to test in the filesystem + * @return boolean true if RW access, false if not + */ + public function checkExternalPathRW ($path) + { + return true; + } +}