diff --git a/api/src/Vfs/Sqlfs/StreamWrapper.php b/api/src/Vfs/Sqlfs/StreamWrapper.php index fc3f2f946f..ce99232e54 100644 --- a/api/src/Vfs/Sqlfs/StreamWrapper.php +++ b/api/src/Vfs/Sqlfs/StreamWrapper.php @@ -1867,6 +1867,10 @@ GROUP BY A.fs_id'; $ins_stmt = $del_stmt = null; foreach($props as &$prop) { + if(!array_key_exists('name', $prop)) + { + return false; // Name is missing + } if (!isset($prop['ns'])) $prop['ns'] = Vfs::DEFAULT_PROP_NAMESPACE; if (!isset($prop['val']) || self::$pdo_type != 'mysql') // for non mysql, we have to delete the prop anyway, as there's no REPLACE! diff --git a/api/tests/Vfs/ProppatchTest.php b/api/tests/Vfs/ProppatchTest.php new file mode 100644 index 0000000000..b44b635c6f --- /dev/null +++ b/api/tests/Vfs/ProppatchTest.php @@ -0,0 +1,116 @@ +files[] = $test_file = $this->getFilename(); + $contents = $this->getName() . "\nJust a test ;)\n"; + $this->assertNotFalse( + file_put_contents(Vfs::PREFIX . $test_file, $contents), + "Could not write test file $test_file" + ); + + $proppatch = [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']]; + $this->assertTrue( + Vfs::proppatch($test_file, $proppatch), + "Could not set properties" + ); + $read = Vfs::propfind($test_file); + + $this->assertEquals($proppatch, $read, + "Read proppatch does not match what was written" + ); + + // Try to delete, while we're here + $proppatch[0]['val'] = null; + $this->assertTrue( + Vfs::proppatch($test_file, $proppatch), + "Could not delete properties by setting val = null" + ); + $read_deleted = Vfs::propfind($test_file); + $this->assertNotFalse($read_deleted, "Problem reading properties after deleting"); + $this->assertEquals([], $read_deleted, "Found properties after deleting"); + } + + /** + * Test that an invalid proppatch is not accepted + */ + public function testInvalidProppatch() + { + $this->files[] = $test_file = $this->getFilename(); + $contents = $this->getName() . "\nJust a test ;)\n"; + $this->assertNotFalse( + file_put_contents(Vfs::PREFIX . $test_file, $contents), + "Could not write test file $test_file" + ); + + $proppatch = [['No worky']]; + $this->assertFalse( + Vfs::proppatch($test_file, $proppatch), + "Managed to set invalid properties" + ); + } + + public function testWriteWithNoPermissionsFails() + { + $this->files[] = $test_file = $this->getFilename(); + Vfs::remove($test_file); + $contents = $this->getName() . "\nJust a test ;)\n"; + $this->assertNotFalse( + file_put_contents(Vfs::PREFIX . $test_file, $contents), + "Could not write test file $test_file" + ); + + // Change owner so we lose permission + Vfs::$is_root = true; + $this->assertTrue( + Vfs::chown($test_file, 'anonymous'), + "Could not chown test file '$test_file'" + ); + Vfs::$is_root = false; + + // Try to set property + $proppatch = [['ns' => Vfs::DEFAULT_PROP_NAMESPACE, 'name' => 'test', 'val' => 'something']]; + $this->assertFalse( + Vfs::proppatch($test_file, $proppatch), + "Managed to set properties with no permission" + ); + + } +} \ No newline at end of file diff --git a/api/tests/Vfs/StreamWrapperBase.php b/api/tests/Vfs/StreamWrapperBase.php new file mode 100644 index 0000000000..2100a39958 --- /dev/null +++ b/api/tests/Vfs/StreamWrapperBase.php @@ -0,0 +1,284 @@ + 5 + ); + + protected function setUp() : void + { + // Check we have basic access + if(!is_readable($GLOBALS['egw_info']['server']['files_dir'])) + { + $this->markTestSkipped('No read access to files dir "' .$GLOBALS['egw_info']['server']['files_dir'].'"' ); + } + if(!is_writable($GLOBALS['egw_info']['server']['files_dir'])) + { + $this->markTestSkipped('No write access to files dir "' .$GLOBALS['egw_info']['server']['files_dir'].'"' ); + } + } + + protected function tearDown() : void + { + LoggedInTest::tearDownAfterClass(); + LoggedInTest::setupBeforeClass(); + + // Re-init, since they look at user, fstab, etc. + // Also, further tests that access the filesystem fail if we don't + Vfs::clearstatcache(); + Vfs::init_static(); + Vfs\StreamWrapper::init_static(); + + // Need to ask about mounts, or other tests fail + Vfs::mount(); + + $backup = Vfs::$is_root; + Vfs::$is_root = true; + + if(static::LOG_LEVEL > 1) + { + error_log($this->getName() . ' files for removal:'); + error_log(implode("\n",$this->files)); + error_log($this->getName() . ' mounts for removal:'); + error_log(implode("\n",$this->mounts)); + } + + // Remove any added files (as root to limit versioning issues) + if(in_array('/',$this->files)) + { + $this->fail('Tried to remove root'); + } + foreach($this->files as $file) + { + Vfs::unlink($file); + } + + // Remove any mounts + foreach($this->mounts as $mount) + { + Vfs::umount($mount); + } + + Vfs::$is_root = $backup; + } + + /** + * Make a filename that reflects the current test + */ + protected function getFilename($path = null) + { + if(is_null($path)) $path = Vfs::get_home_dir().'/'; + if(substr($path,-1,1) !== '/') $path = $path . '/'; + + return $path . get_class(this) . '_' . $this->getName() . '.txt'; + } + + /** + * Start versioning for the given path + * + * @param string $path + */ + protected function mountVersioned($path) + { + if (!class_exists('EGroupware\Stylite\Vfs\Versioning\StreamWrapper')) + { + $this->markTestSkipped("No versioning available"); + } + if(substr($path, -1) == '/') $path = substr($path, 0, -1); + $backup = Vfs::$is_root; + Vfs::$is_root = true; + $url = Versioning\StreamWrapper::PREFIX.$path; + $this->assertTrue(Vfs::mount($url,$path), "Unable to mount $path as versioned"); + Vfs::$is_root = $backup; + + $this->mounts[] = $path; + Vfs::clearstatcache(); + Vfs::init_static(); + Vfs\StreamWrapper::init_static(); + } + + /** + * Mount a test filesystem path (api/tests/fixtures/Vfs/filesystem_mount) + * at the given VFS path + * + * @param string $path + */ + protected function mountFilesystem($path) + { + // Vfs breaks if path has trailing / + if(substr($path, -1) == '/') $path = substr($path, 0, -1); + + $backup = Vfs::$is_root; + Vfs::$is_root = true; + $fs_path = realpath(__DIR__ . '/../fixtures/Vfs/filesystem_mount'); + if(!file_exists($fs_path)) + { + $this->fail("Missing filesystem test directory 'api/tests/fixtures/Vfs/filesystem_mount'"); + } + $url = Filesystem\StreamWrapper::SCHEME.'://default'. $fs_path. + '?user='.$GLOBALS['egw_info']['user']['account_id'].'&group=Default&mode=775'; + $this->assertTrue(Vfs::mount($url,$path), "Unable to mount $url to $path"); + Vfs::$is_root = $backup; + + $this->mounts[] = $path; + Vfs::clearstatcache(); + Vfs::init_static(); + Vfs\StreamWrapper::init_static(); + } + + /** + * Merge a test filesystem path (api/tests/fixtures/Vfs/filesystem_mount) + * with the given VFS path + * + * @param string $path + */ + protected function mountMerge($path) + { + // Vfs breaks if path has trailing / + if(substr($path, -1) == '/') $path = substr($path, 0, -1); + + + $backup = Vfs::$is_root; + Vfs::$is_root = true; + + // I guess merge needs the dir in SQLFS first + if(!Vfs::is_dir($dir)) Vfs::mkdir($path); + Vfs::chmod($path, 0750); + Vfs::chown($path, $GLOBALS['egw_info']['user']['account_id']); + + $url = \EGroupware\Stylite\Vfs\Merge\StreamWrapper::SCHEME.'://default'.$path.'?merge=' . realpath(__DIR__ . '/../fixtures/Vfs/filesystem_mount'); + $this->assertTrue(Vfs::mount($url,$path), "Unable to mount $url to $path"); + Vfs::$is_root = $backup; + + $this->mounts[] = $path; + Vfs::clearstatcache(); + Vfs::init_static(); + Vfs\StreamWrapper::init_static(); + } + + /** + * Add some files to the given path so there's something to find. + * + * @param string $path + * + * @return array of paths + */ + protected function addFiles($path, $content = false) + { + if(substr($path, -1) != '/') + { + $path .= '/'; + } + if(!$content) + { + $content = 'Test for ' . $this->getName() ."\n". Api\DateTime::to(); + } + $files = array(); + + // Plain file + $files[] = $file = $path.'test_file.txt'; + $this->assertTrue( + file_put_contents(Vfs::PREFIX.$file, $content) !== FALSE, + 'Unable to write test file "' . Vfs::PREFIX . $file .'" - check file permissions for CLI user' + ); + + // Subdirectory + $files[] = $dir = $path.'sub_dir/'; + if(Vfs::is_dir($dir)) + { + Vfs::remove($dir); + } + $this->assertTrue( + Vfs::mkdir($dir), + 'Unable to create subdirectory ' . $dir + ); + + // File in a subdirectory + $files[] = $file = $dir.'subdir_test_file.txt'; + $this->assertTrue( + file_put_contents(Vfs::PREFIX.$file, $content) !== FALSE, + 'Unable to write test file "' . Vfs::PREFIX . $file .'" - check file permissions for CLI user' + ); + + // Symlinked file + /* We don't test these because they don't work - the target will always + * be outside the share root + // Always says its empty + $files[] = $symlink = $path.'symlink.txt'; + if(Vfs::file_exists($symlink)) Vfs::remove($symlink); + $this->assertTrue( + Vfs::symlink($file, $symlink), + "Unable to create symlink $symlink => $file" + ); + + // Symlinked dir + $files[] = $symlinked_dir = $path.'sym_dir/'; + $this->assertTrue( + Vfs::symlink($dir, $symlinked_dir), + 'Unable to create symlinked directory ' . $symlinked_dir + ); +*/ + return $files; + } + + /** + * Make an infolog entry + */ + protected function make_infolog() + { + $bo = new \infolog_bo(); + $element = array( + 'info_subject' => "Test infolog for #{$this->getName()}", + 'info_des' => 'Test element for ' . $this->getName() . "\n" . Api\DateTime::to(), + 'info_status' => 'open' + ); + + $element_id = $bo->write($element, true, true, true, true); + return $element_id; + } +} \ No newline at end of file diff --git a/api/tests/Vfs/StreamWrapperTest.php b/api/tests/Vfs/StreamWrapperTest.php new file mode 100644 index 0000000000..1feb73bd1e --- /dev/null +++ b/api/tests/Vfs/StreamWrapperTest.php @@ -0,0 +1,89 @@ +files[] = $test_file = $this->getFilename(); + $contents = $this->getName() . "\nJust a test ;)\n"; + $this->assertNotFalse( + file_put_contents(Vfs::PREFIX . $test_file, $contents), + "Could not write file $test_file" + ); + + // Check contents are unchanged + $this->assertEquals( + $contents, file_get_contents(Vfs::PREFIX . $test_file), + "Read file contents do not match what was written" + ); + } + + /** + * Simple delete of a file + */ + public function testDelete() : void + { + $this->files[] = $test_file = $this->getFilename(); + + // Check that the file is not there + $pre_start = Vfs::stat($test_file); + $this->assertEquals(null,$pre_start, + "File '$test_file' was there before we started, check clean up" + ); + + // Write + $contents = $this->getName() . "\nJust a test ;)\n"; + $this->assertNotFalse( + file_put_contents(Vfs::PREFIX . $test_file, $contents), + "Could not write file $test_file" + ); + + $start = Vfs::stat($test_file); + $this->assertNotNull( + $start, + "File '$test_file' was not what we expected to find after writing" + ); + + Vfs::unlink($test_file); + + $post = Vfs::stat($test_file); + $this->assertEquals(null,$post, + "File '$test_file' was there after deleting" + ); + } +} \ No newline at end of file