Files
server/usr/share/psa-pear/pear/php/Horde/Vfs/Musql.php
2026-01-07 20:52:11 +01:00

466 lines
17 KiB
PHP

<?php
/**
* Multi User VFS implementation for Horde's database abstraction layer.
*
* Required values for $params:
* - db: A Horde_Db_Adapter object.
*
* Optional values:
* - table: (string) The name of the vfs table in 'database'. Defaults to
* 'horde_muvfs'.
*
* Known Issues:
* Delete is not recusive, so files and folders that used to be in a folder
* that gets deleted live forever in the database, or re-appear when the folder
* is recreated.
* Rename has the same issue, so files are lost if a folder is renamed.
*
* The table structure for the VFS can be created with the horde-db-migrate
* script from the Horde_Db package.
*
* Copyright 2002-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (LGPL). If you
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
*
* @author Chuck Hagenbuch <chuck@horde.org>
* @author Mike Cochrane <mike@graftonhall.co.nz>
* @package Vfs
*/
class Horde_Vfs_Musql extends Horde_Vfs_Sql
{
/* Permission for read access. */
const FLAG_READ = 1;
/* Permission for read access. */
const FLAG_WRITE = 2;
/**
* List of permissions and if they can be changed in this VFS
*
* @var array
*/
protected $_permissions = array(
'owner' => array(
'read' => false,
'write' => false,
'execute' => false
),
'group' => array(
'read' => false,
'write' => false,
'execute' => false
),
'all' => array(
'read' => true,
'write' => true,
'execute' => false
)
);
/**
* Stores a file in the VFS from raw data.
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string|resource $data The data as a string or stream resource.
* Resources allowed @since 2.4.0
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function writeData($path, $name, $data, $autocreate = false)
{
/* Make sure we have write access to this and all parent paths. */
if ($path != '') {
$paths = explode('/', $path);
$path_name = array_pop($paths);
if (!$this->isFolder(implode('/', $paths), $path_name)) {
if (!$autocreate) {
throw new Horde_Vfs_Exception(sprintf('Folder "%s" does not exist'), $path);
} else {
$this->autocreatePath($path);
}
}
$paths[] = $path_name;
$previous = '';
foreach ($paths as $thispath) {
$sql = sprintf('SELECT vfs_owner, vfs_perms FROM %s
WHERE vfs_path = ? AND vfs_name= ?',
$this->_params['table']);
try {
$results = $this->_db->select($sql, array($previous, $thispath));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if (!is_array($results) || count($results) < 1) {
throw new Horde_Vfs_Exception('Unable to create VFS file.');
}
$allowed = false;
foreach ($results as $result) {
if ($result['vfs_owner'] == $this->_params['user'] ||
$result['vfs_perm'] & self::FLAG_WRITE) {
$allowed = true;
break;
}
}
if (!$allowed) {
throw new Horde_Vfs_Exception('Access denied creating VFS file.');
}
$previous = $thispath;
}
}
return parent::writeData($path, $name, $data, $autocreate);
}
/**
* Deletes a file from the VFS.
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
*
* @throws Horde_Vfs_Exception
*/
public function deleteFile($path, $name)
{
$sql = sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
WHERE vfs_path = ? AND vfs_name= ? AND vfs_type = ?',
$this->_params['table']);
try {
$fileList = $this->_db->select($sql, array($path, $name, self::FILE));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if (!is_array($fileList) || count($fileList) < 1) {
throw new Horde_Vfs_Exception('Unable to delete VFS file.');
}
/* There may be one or more files with the same name but the user may
* not have read access to them, so doesn't see them. So we have to
* delete the one they have access to. */
foreach ($fileList as $file) {
if ($file['vfs_owner'] == $this->_params['user'] ||
$file['vfs_perms'] & self::FLAG_WRITE) {
$sql = sprintf('DELETE FROM %s WHERE vfs_id = ?',
$this->_params['table']);
try {
$result = $this->_db->delete($sql, array($file['vfs_id']));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if ($result == 0) {
throw new Horde_Vfs_Exception('Unable to delete VFS file.');
}
return;
}
}
// FIXME: 'Access Denied deleting file %s/%s'
throw new Horde_Vfs_Exception('Unable to delete VFS file.');
}
/**
* Renames a file or folder in the VFS.
*
* @param string $oldpath The old path to the file.
* @param string $oldname The old filename.
* @param string $newpath The new path of the file.
* @param string $newname The new filename.
*
* @throws Horde_Vfs_Exception
*/
public function rename($oldpath, $oldname, $newpath, $newname)
{
$sql = sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
WHERE vfs_path = ? AND vfs_name= ?',
$this->_params['table']);
try {
$fileList = $this->_db->select($sql, array($oldpath, $oldname));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if (!is_array($fileList) || count($fileList) < 1) {
throw new Horde_Vfs_Exception('Unable to rename VFS file.');
}
if (strpos($newpath, '/') === false) {
$parent = '';
$path = $newpath;
} else {
list($parent, $path) = explode('/', $newpath, 2);
}
if (!$this->isFolder($parent, $path)) {
$this->autocreatePath($newpath);
}
/* There may be one or more files with the same name but the user may
* not have read access to them, so doesn't see them. So we have to
* rename the one they have access to. */
foreach ($fileList as $file) {
if ($file['vfs_owner'] == $this->_params['user'] ||
$file['vfs_perms'] & self::FLAG_WRITE) {
$sql = sprintf('UPDATE %s SET vfs_path = ?, vfs_name = ?, vfs_modified = ?
WHERE vfs_id = ?',
$this->_params['table']);
try {
$this->_db->update(
$sql,
array($newpath, $newname, time(), $file['vfs_id']));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
}
}
throw new Horde_Vfs_Exception(sprintf('Unable to rename VFS file %s/%s.', $oldpath, $oldname));
}
/**
* Creates a folder on the VFS.
*
* @param string $path Holds the path of directory to create folder.
* @param string $name Holds the name of the new folder.
*
* @throws Horde_Vfs_Exception
*/
public function createFolder($path, $name)
{
/* Make sure we have write access to this and all parent paths. */
if (strlen($path)) {
$paths = explode('/', $path);
$previous = '';
foreach ($paths as $thispath) {
$sql = sprintf('SELECT vfs_owner, vfs_perms FROM %s
WHERE vfs_path = ? AND vfs_name= ?',
$this->_params['table']);
try {
$results = $this->_db->select($sql, array($previous, $thispath));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if (!is_array($results) || count($results) < 1) {
throw new Horde_Vfs_Exception('Unable to create VFS directory.');
}
$allowed = false;
foreach ($results as $result) {
if ($result['vfs_owner'] == $this->_params['user'] ||
$result['vfs_perms'] & self::FLAG_WRITE) {
$allowed = true;
break;
}
}
if (!$allowed) {
throw new Horde_Vfs_Exception('Access denied creating VFS directory.');
}
$previous = $thispath;
}
}
$sql = sprintf('INSERT INTO %s
(vfs_type, vfs_path, vfs_name, vfs_modified, vfs_owner, vfs_perms)
VALUES (?, ?, ?, ?, ?, ?)',
$this->_params['table']);
try {
$this->_db->insert(
$sql,
array(self::FOLDER, $path, $name, time(), $this->_params['user'], 0));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
}
/**
* Deletes a folder from the VFS.
*
* @param string $path The path to delete the folder from.
* @param string $name The foldername to use.
* @param boolean $recursive Force a recursive delete?
*
* @throws Horde_Vfs_Exception
*/
public function deleteFolder($path, $name, $recursive = false)
{
if ($recursive) {
$this->emptyFolder($path . '/' . $name);
} else {
$list = $this->listFolder($path . '/' . $name);
if (count($list)) {
throw new Horde_Vfs_Exception(sprintf('Unable to delete %s, the directory is not empty', $path . '/' . $name));
}
}
$sql = sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
WHERE vfs_path = ? AND vfs_name= ? AND vfs_type = ?',
$this->_params['table']);
try {
$fileList = $this->_db->select($sql, array($path, $name, self::FOLDER));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if (!is_array($fileList) || count($fileList) < 1) {
throw new Horde_Vfs_Exception('Unable to delete VFS directory.');
}
/* There may be one or more folders with the same name but as the user
* may not have read access to them, they don't see them. So we have
* to delete the one they have access to */
foreach ($fileList as $file) {
if ($file['vfs_owner'] == $this->_params['user'] ||
$file['vfs_perms'] & self::FLAG_WRITE) {
$sql = sprintf('DELETE FROM %s WHERE vfs_id = ?',
$this->_params['table']);
try {
$result = $this->_db->delete($sql, array($file['vfs_id']));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if ($result == 0) {
throw new Horde_Vfs_Exception('Unable to delete VFS directory.');
}
return $result;
}
}
// FIXME: 'Access Denied deleting folder %s/%s'
throw new Horde_Vfs_Exception('Unable to delete VFS directory.');
}
/**
* Returns an an unsorted file list of the specified directory.
*
* @param string $path The path of the directory.
* @param mixed $filter String/hash to filter file/dirname on.
* @param boolean $dotfiles Show dotfiles?
* @param boolean $dironly Show only directories?
*
* @return array File list.
* @throws Horde_Vfs_Exception
*/
protected function _listFolder($path, $filter = null, $dotfiles = true,
$dironly = false)
{
$length_op = $this->_getFileSizeOp();
$sql = sprintf('SELECT vfs_name, vfs_type, vfs_modified, vfs_owner, vfs_perms, %s(vfs_data) length FROM %s
WHERE vfs_path = ? AND (vfs_owner = ? OR vfs_perms \&\& ?)',
$length_op, $this->_params['table']);
try {
$fileList = $this->_db->select(
$sql,
array($path, $this->_params['user'], self::FLAG_READ));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
$files = array();
foreach ($fileList as $line) {
// Filter out dotfiles if they aren't wanted.
if (!$dotfiles && substr($line['vfs_name'], 0, 1) == '.') {
continue;
}
$file['name'] = stripslashes($line['vfs_name']);
if ($line['vfs_type'] == self::FILE) {
$name = explode('.', $line['vfs_name']);
if (count($name) == 1) {
$file['type'] = '**none';
} else {
$file['type'] = Horde_String::lower($name[count($name) - 1]);
}
$file['size'] = $line['length'];
} elseif ($line['vfs_type'] == self::FOLDER) {
$file['type'] = '**dir';
$file['size'] = -1;
}
$file['date'] = $line['vfs_modified'];
$file['owner'] = $line['vfs_owner'];
$line['vfs_perms'] = intval($line['vfs_perms']);
$file['perms'] = ($line['vfs_type'] == self::FOLDER) ? 'd' : '-';
$file['perms'] .= 'rw-';
$file['perms'] .= ($line['vfs_perms'] & self::FLAG_READ) ? 'r' : '-';
$file['perms'] .= ($line['vfs_perms'] & self::FLAG_WRITE) ? 'w' : '-';
$file['perms'] .= '-';
$file['group'] = '';
// Filtering.
if ($this->_filterMatch($filter, $file['name'])) {
unset($file);
continue;
}
if ($dironly && $file['type'] !== '**dir') {
unset($file);
continue;
}
$files[$file['name']] = $file;
unset($file);
}
return $files;
}
/**
* Changes permissions for an Item on the VFS.
*
* @param string $path The path of directory of the item.
* @param string $name The name of the item.
* @param string $permission The permission to set in octal notation.
*
* @throws Horde_Vfs_Exception
*/
public function changePermissions($path, $name, $permission)
{
$val = intval(substr($permission, -1));
$perm = 0;
$perm |= ($val & 4) ? self::FLAG_READ : 0;
$perm |= ($val & 2) ? self::FLAG_WRITE : 0;
$sql = sprintf('SELECT vfs_id, vfs_owner, vfs_perms FROM %s
WHERE vfs_path = ? AND vfs_name= ?',
$this->_params['table']);
try {
$fileList = $this->_db->select($sql, array($path, $name));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
if (!is_array($fileList) || count($fileList) < 1) {
throw new Horde_Vfs_Exception('Unable to rename VFS file.');
}
/* There may be one or more files with the same name but the user may
* not have read access to them, so doesn't see them. So we have to
* chmod the one they have access to. */
foreach ($fileList as $file) {
if ($file['vfs_owner'] == $this->_params['user'] ||
$file['vfs_perms'] & self::FLAG_WRITE) {
$sql = sprintf('UPDATE %s SET vfs_perms = ?
WHERE vfs_id = ?',
$this->_params['table']);
try {
$this->_db->update($sql, array($perm, $file['vfs_id']));
} catch (Horde_Db_Exception $e) {
throw new Horde_Vfs_Exception($e);
}
}
}
throw new Horde_Vfs_Exception(sprintf('Unable to change permission for VFS file %s/%s.', $path, $name));
}
}