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

690 lines
23 KiB
PHP

<?php
/**
* VFS implementation for a filesystem.
*
* 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
* @package Vfs
*/
class Horde_Vfs_File extends Horde_Vfs_Base
{
/**
* List of permissions and if they can be changed in this VFS backend.
*
* @var array
*/
protected $_permissions = array(
'owner' => array(
'read' => true,
'write' => true,
'execute' => true
),
'group' => array(
'read' => true,
'write' => true,
'execute' => true
),
'all' => array(
'read' => true,
'write' => true,
'execute' => true
)
);
/**
* List of features that the VFS driver supports.
*
* @var array
*/
protected $_features = array(
'readByteRange' => true,
);
/**
* Constructs a new Filesystem based VFS object.
*
* @param array $params A hash containing connection parameters. REQUIRED
* parameters:
* - vfsroot: (string) The root path.
* Note: The user that your webserver runs as MUST have
* read/write permission to this directory.
*/
public function __construct($params = array())
{
parent::__construct($params);
if (!empty($this->_params['vfsroot']) &&
((substr($this->_params['vfsroot'], -1) == '/') ||
(substr($this->_params['vfsroot'], -1) == '\\'))) {
$this->_params['vfsroot'] = substr($this->_params['vfsroot'], 0, strlen($this->_params['vfsroot']) - 1);
if (!strlen($this->_params['vfsroot'])) {
$this->_params['vfsroot'] = '/';
}
}
$this->_connect();
}
/**
* Retrieves the filesize from the VFS.
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return integer The file size.
* @throws Horde_Vfs_Exception
*/
public function size($path, $name)
{
if (($size = @filesize($this->_getNativePath($path, $name))) === false) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to check file size of "%s/%s".', $path, $name)));
}
return $size;
}
/**
* Retrieve a file from the VFS.
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return string The file data.
* @throws Horde_Vfs_Exception
*/
public function read($path, $name)
{
if (($data = @file_get_contents($this->_getNativePath($path, $name))) === false) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to open VFS file %s/%s.', $path, $name)));
}
return $data;
}
/**
* Retrieves a file from the VFS as an on-disk local file.
*
* This function provides a file on local disk with the data of a VFS file
* in it. This file <em>cannot</em> be modified! The behavior if you do
* modify it is undefined. It will be removed at the end of the request.
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return string A local filename.
*/
public function readFile($path, $name)
{
return $this->_getNativePath($path, $name);
}
/**
* Open a read-only stream to a file in the VFS.
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
*
* @return resource The stream.
* @throws Horde_Vfs_Exception
*/
public function readStream($path, $name)
{
$stream = @fopen($this->_getNativePath($path, $name), 'rb');
if (!is_resource($stream)) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to open VFS file %s/%s.', $path, $name)));
}
return $stream;
}
/**
* Retrieves a part of a file from the VFS. Particularly useful when
* reading large files which would exceed the PHP memory limits if they
* were stored in a string.
*
* @param string $path The pathname to the file.
* @param string $name The filename to retrieve.
* @param integer $offset The offset of the part. (The new offset will
* be stored in here).
* @param integer $length The length of the part. If the length = -1,
* the whole part after the offset is retrieved.
* If more bytes are given as exists after the
* given offset. Only the available bytes are
* read.
* @param integer $remaining The bytes that are left, after the part that
* is retrieved.
*
* @return string The file data.
* @throws Horde_Vfs_Exception
*/
public function readByteRange($path, $name, &$offset, $length, &$remaining)
{
if ($offset < 0) {
throw new Horde_Vfs_Exception(sprintf('Wrong offset %d while reading VFS file %s/%s.', $offset, $path, $name));
}
// Calculate how many bytes MUST be read, so the remainging
// bytes and the new offset can be calculated correctly.
$file = $this->_getNativePath($path, $name);
$size = filesize ($file);
if (($length == -1) || (($length + $offset) > $size)) {
$length = $size - $offset;
}
if ($remaining < 0) {
$remaining = 0;
}
$fp = @fopen($file, 'rb');
if (!$fp) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to open VFS file %s/%s.', $path, $name)));
}
fseek($fp, $offset);
$data = fread($fp, $length);
$offset = ftell($fp);
$remaining = $size - $offset;
fclose($fp);
return $data;
}
/**
* Store a file in the VFS, with the data copied from a temporary
* file.
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $tmpFile The temporary file containing the data to be
* stored.
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function write($path, $name, $tmpFile, $autocreate = false)
{
if (!@is_dir($this->_getNativePath($path))) {
if ($autocreate) {
$this->autocreatePath($path);
} else {
throw new Horde_Vfs_Exception(sprintf('VFS directory %s does not exist.', $path));
}
}
$this->_checkQuotaWrite('file', $tmpFile, $path, $name);
// Since we already have the data in a file, don't read it
// into PHP's memory at all - just copy() it to the new
// location. We leave it to the caller to clean up the
// temporary file, so we don't use rename().
if (!@copy($tmpFile, $this->_getNativePath($path, $name))) {
throw new Horde_Vfs_Exception(sprintf('Unable to write VFS file %s/%s (copy() failed).', $path, $name));
}
}
/**
* Moves a file in the database and the file system.
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $dest The destination of the file.
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function move($path, $name, $dest, $autocreate = false)
{
$orig = $this->_getNativePath($path, $name);
if (preg_match('|^' . preg_quote($orig) . '/?$|', $dest)) {
throw new Horde_Vfs_Exception(sprintf('Unable to move VFS file %s/%s - destination is within source.', $path, $name));
}
if ($autocreate) {
$this->autocreatePath($dest);
}
foreach ($this->listFolder($dest, false) as $file) {
if ($file['name'] == $name) {
throw new Horde_Vfs_Exception(sprintf('Unable to move VFS file %s/%s - destination exists already.', $path, $name));
}
}
if (!@rename($orig, $this->_getNativePath($dest, $name))) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to move VFS file %s/%s.', $path, $name)));
}
}
/**
* Copies a file through the backend.
*
* @param string $path The path to store the file in.
* @param string $name The filename to use.
* @param string $dest The destination of the file.
* @param boolean $autocreate Automatically create directories?
*
* @throws Horde_Vfs_Exception
*/
public function copy($path, $name, $dest, $autocreate = false)
{
$this->_checkDestination($path, $dest);
if ($autocreate) {
$this->autocreatePath($dest);
}
foreach ($this->listFolder($dest, false) as $file) {
if ($file['name'] == $name) {
throw new Horde_Vfs_Exception('Unable to copy VFS file.');
}
}
$orig = $this->_getNativePath($path, $name);
$this->_checkQuotaWrite('file', $orig, $dest, $name);
if (!@copy($orig, $this->_getNativePath($dest, $name))) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to copy VFS file %s/%s.', $path, $name)));
}
}
/**
* Store 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)
{
if (!@is_dir($this->_getNativePath($path))) {
if ($autocreate) {
$this->autocreatePath($path);
} else {
throw new Horde_Vfs_Exception(sprintf('VFS directory %s does not exist.', $path));
}
}
// Ensure we have either a string or seekable stream.
$data = $this->_ensureSeekable($data);
// Treat an attempt to write an empty file as a touch() call,
// since otherwise the file will not be created at all.
if (!$this->_getDataSize($data)) {
if (@touch($this->_getNativePath($path, $name))) {
return;
}
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to create empty VFS file %s/%s.', $path, $name)));
}
$this->_checkQuotaWrite('string', $data, $path, $name);
// Otherwise we go ahead and try to write out the file.
if (!@file_put_contents($this->_getNativePath($path, $name), $data)) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to write data to VFS file %s/%s.', $path, $name)));
}
}
/**
* Delete 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)
{
$this->_checkQuotaDelete($path, $name);
if (!@unlink($this->_getNativePath($path, $name))) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to delete VFS file %s/%s.', $path, $name)));
}
}
/**
* Delete 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/%s, the directory is not empty', $path, $name));
}
}
if (!@rmdir($this->_getNativePath($path, $name))) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to delete VFS directory %s/%s.', $path, $name)));
}
}
/**
* Creates a folder on the VFS.
*
* @param string $path The path to create the folder in.
* @param string $name The foldername to use.
*
* @throws Horde_Vfs_Exception
*/
public function createFolder($path, $name)
{
if (!@mkdir($this->_getNativePath($path, $name))) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to create VFS directory %s/%s.', $path, $name)));
}
}
/**
* Check if a given pathname is a folder.
*
* @param string $path The path to the folder.
* @param string $name The file/folder name.
*
* @return boolean True if it is a folder, false otherwise.
*/
public function isFolder($path, $name)
{
return @is_dir($this->_getNativePath($path, $name));
}
/**
* Changes permissions for an item in 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)
{
if (!@chmod($this->_getNativePath($path, $name), base_convert($permission, 8, 10))) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to change permission for VFS file %s/%s.', $path, $name)));
}
}
/**
* Returns an unsorted file list of the specified directory.
*
* @param string $path The path of the directory.
* @param string|array $filter Regular expression(s) to filter
* file/directory name 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)
{
$files = array();
$dir = $this->_getNativePath(strlen($path) ? $path : '');
if (!@is_dir($dir)) {
throw new Horde_Vfs_Exception(sprintf('%s is not a VFS directory', $path));
}
$d = @dir($dir);
if (!$d) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to access VFS directory %s.', $path)));
}
while (($entry = $d->read()) !== false) {
// Filter out '.' and '..' entries.
if ($entry == '.' || $entry == '..') {
continue;
}
// Filter out dotfiles if they aren't wanted.
if (!$dotfiles && substr($entry, 0, 1) == '.') {
continue;
}
// File name
$file['name'] = $entry;
$path = $dir . '/' . $entry;
// Unix style file permissions
$file['perms'] = $this->_getUnixPerms(fileperms($path));
// Owner
$file['owner'] = fileowner($path);
if (function_exists('posix_getpwuid')) {
$owner = posix_getpwuid($file['owner']);
$file['owner'] = $owner['name'];
}
// Group
$file['group'] = filegroup($path);
if (function_exists('posix_getgrgid')) {
$group = posix_getgrgid($file['group']);
$file['group'] = $group['name'];
}
// Size
$file['size'] = filesize($path);
// Date
$file['date'] = filemtime($path);
// Type
if (@is_dir($path) && !is_link($path)) {
$file['perms'] = 'd' . $file['perms'];
$file['type'] = '**dir';
$file['size'] = -1;
} elseif (is_link($path)) {
$file['perms'] = 'l' . $file['perms'];
$file['type'] = '**sym';
$file['link'] = readlink($path);
$file['linktype'] = '**none';
if (file_exists($file['link'])) {
if (is_dir($file['link'])) {
$file['linktype'] = '**dir';
} elseif (is_link($file['link'])) {
$file['linktype'] = '**sym';
} elseif (is_file($file['link'])) {
$ext = explode('.', $file['link']);
if (!(count($ext) == 1 || ($ext[0] === '' && count($ext) == 2))) {
$file['linktype'] = Horde_String::lower($ext[count($ext) - 1]);
}
}
} else {
$file['linktype'] = '**broken';
}
} elseif (is_file($path)) {
$file['perms'] = '-' . $file['perms'];
$ext = explode('.', $entry);
if (count($ext) == 1 || (substr($file['name'], 0, 1) === '.' && count($ext) == 2)) {
$file['type'] = '**none';
} else {
$file['type'] = Horde_String::lower($ext[count($ext) - 1]);
}
} else {
$file['type'] = '**none';
if ((fileperms($path) & 0xC000) == 0xC000) {
$file['perms'] = 's' . $file['perms'];
} elseif ((fileperms($path) & 0x6000) == 0x6000) {
$file['perms'] = 'b' . $file['perms'];
} elseif ((fileperms($path) & 0x2000) == 0x2000) {
$file['perms'] = 'c' . $file['perms'];
} elseif ((fileperms($path) & 0x1000) == 0x1000) {
$file['perms'] = 'p' . $file['perms'];
} else {
$file['perms'] = '?' . $file['perms'];
}
}
// Filtering.
if ($this->_filterMatch($filter, $file['name'])) {
unset($file);
continue;
}
if ($dironly && $file['type'] !== '**dir') {
unset($file);
continue;
}
$files[$file['name']] = $file;
unset($file);
}
$d->close();
return $files;
}
/**
* Return Unix style perms.
*
* @param integer $perms The permissions to set.
*
* @return string Unix style perms.
*/
protected function _getUnixPerms($perms)
{
// Determine permissions
$owner['read'] = ($perms & 00400) ? 'r' : '-';
$owner['write'] = ($perms & 00200) ? 'w' : '-';
$owner['execute'] = ($perms & 00100) ? 'x' : '-';
$group['read'] = ($perms & 00040) ? 'r' : '-';
$group['write'] = ($perms & 00020) ? 'w' : '-';
$group['execute'] = ($perms & 00010) ? 'x' : '-';
$world['read'] = ($perms & 00004) ? 'r' : '-';
$world['write'] = ($perms & 00002) ? 'w' : '-';
$world['execute'] = ($perms & 00001) ? 'x' : '-';
// Adjust for SUID, SGID and sticky bit
if ($perms & 0x800) {
$owner['execute'] = ($owner['execute'] == 'x') ? 's' : 'S';
}
if ($perms & 0x400) {
$group['execute'] = ($group['execute'] == 'x') ? 's' : 'S';
}
if ($perms & 0x200) {
$world['execute'] = ($world['execute'] == 'x') ? 't' : 'T';
}
return $owner['read'] . $owner['write'] . $owner['execute'] .
$group['read'] . $group['write'] . $group['execute'] .
$world['read'] . $world['write'] . $world['execute'];
}
/**
* Rename 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)
{
if (!@is_dir($this->_getNativePath($newpath))) {
$this->autocreatePath($newpath);
}
if (!@rename($this->_getNativePath($oldpath, $oldname),
$this->_getNativePath($newpath, $newname))) {
$this->_throwException(new Horde_Vfs_Exception(sprintf('Unable to rename VFS file %s/%s.', $oldpath, $oldname)));
}
}
/**
* Returns if a given file or folder exists in a folder.
*
* @param string $path The path to the folder.
* @param string $name The file or folder name.
*
* @return boolean True if it exists, false otherwise.
*/
public function exists($path, $name)
{
return file_exists($this->_getNativePath($path, $name));
}
/**
* Return a full filename on the native filesystem, from a VFS
* path and name.
*
* @param string $path The VFS file path.
* @param string $name The VFS filename.
*
* @return string The full native filename.
*/
protected function _getNativePath($path = '', $name = '')
{
$name = basename($name);
if (strlen($name)) {
if ($name == '..') {
$name = '';
}
if (substr($name, 0, 1) != '/') {
$name = '/' . $name;
}
}
if (strlen($path)) {
if (isset($this->_params['home']) &&
preg_match('|^~/?(.*)$|', $path, $matches)) {
$path = $this->_params['home'] . '/' . $matches[1];
}
$path = str_replace('..', '', $path);
if (substr($path, 0, 1) == '/') {
return $this->_params['vfsroot'] . rtrim($path, '/') . $name;
} else {
return $this->_params['vfsroot'] . '/' . rtrim($path, '/') . $name;
}
}
return $this->_params['vfsroot'] . $name;
}
/**
* Make sure that the vfsroot is readable.
*
* @throws Horde_Vfs_Exception
*/
protected function _connect()
{
if (!@is_dir($this->_params['vfsroot'])) {
@mkdir($this->_params['vfsroot']);
}
if (!is_readable($this->_params['vfsroot'])) {
throw new Horde_Vfs_Exception('Unable to read the vfsroot directory.');
}
}
/**
* Completes an exception with a detailed error message and throws it.
*
* @param Exception $e The prepared exception.
*
* @throws Exception The exception $e with error details attached.
*/
protected function _throwException(Exception $e)
{
if ($error = error_get_last()) {
$e->details = $error['message'];
}
throw $e;
}
}