754 lines
26 KiB
PHP
754 lines
26 KiB
PHP
|
<?php
|
|||
|
|
|||
|
/*
|
|||
|
* This program is free software; you can redistribute it and/or
|
|||
|
* modify it under the terms of the GNU General Public License
|
|||
|
* as published by the Free Software Foundation; under version 2
|
|||
|
* of the License (non-upgradable).
|
|||
|
*
|
|||
|
* This program is distributed in the hope that it will be useful,
|
|||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
* GNU General Public License for more details.
|
|||
|
*
|
|||
|
* You should have received a copy of the GNU General Public License
|
|||
|
* along with this program; if not, write to the Free Software
|
|||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|||
|
*
|
|||
|
* Copyright (c) 2008-2010 (original work) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER);
|
|||
|
* 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV);
|
|||
|
*
|
|||
|
*/
|
|||
|
|
|||
|
/**
|
|||
|
* Utility class that focuses on files.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @package tao
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
use oat\oatbox\filesystem\File;
|
|||
|
|
|||
|
class tao_helpers_File extends helpers_File
|
|||
|
{
|
|||
|
public const MIME_SVG = 'image/svg+xml';
|
|||
|
|
|||
|
/**
|
|||
|
* Check if the path in parameter can be securly used into the application.
|
|||
|
* (check the cross directory injection, the null byte injection, etc.)
|
|||
|
* Use it when the path may be build from a user variable
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @param string $path The path to check.
|
|||
|
* @param boolean $traversalSafe (optional, default is false) Check if the path is traversal safe.
|
|||
|
* @return boolean States if the path is secure or not.
|
|||
|
*/
|
|||
|
public static function securityCheck($path, $traversalSafe = false)
|
|||
|
{
|
|||
|
$returnValue = true;
|
|||
|
|
|||
|
//security check: detect directory traversal (deny the ../)
|
|||
|
if ($traversalSafe) {
|
|||
|
if (preg_match("/\.\.\//", $path)) {
|
|||
|
$returnValue = false;
|
|||
|
common_Logger::w('directory traversal detected in ' . $path);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//security check: detect the null byte poison by finding the null char injection
|
|||
|
if ($returnValue) {
|
|||
|
for ($i = 0; $i < strlen($path); $i++) {
|
|||
|
if (ord($path[$i]) === 0) {
|
|||
|
$returnValue = false;
|
|||
|
common_Logger::w('null char injection detected in ' . $path);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (bool) $returnValue;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Use this method to cleanly concat components of a path. It will remove extra slashes/backslashes.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @param array $paths The path components to concatenate.
|
|||
|
* @return string The concatenated path.
|
|||
|
*/
|
|||
|
public static function concat($paths)
|
|||
|
{
|
|||
|
$returnValue = (string) '';
|
|||
|
|
|||
|
foreach ($paths as $path) {
|
|||
|
if (!preg_match("/\/$/", $returnValue) && !preg_match("/^\//", $path) && !empty($returnValue)) {
|
|||
|
$returnValue .= '/';
|
|||
|
}
|
|||
|
$returnValue .= $path;
|
|||
|
}
|
|||
|
$returnValue = str_replace('//', '/', $returnValue);
|
|||
|
|
|||
|
return (string) $returnValue;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Remove a file. If the recursive parameter is set to true, the target file
|
|||
|
* can be a directory that contains data.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @param string $path The path to the file you want to remove.
|
|||
|
* @param boolean $recursive (optional, default is false) Remove file content recursively (only if the path points to a directory).
|
|||
|
* @return boolean Return true if the file is correctly removed, false otherwise.
|
|||
|
*/
|
|||
|
public static function remove($path, $recursive = false)
|
|||
|
{
|
|||
|
$returnValue = (bool) false;
|
|||
|
|
|||
|
if ($recursive) {
|
|||
|
$returnValue = helpers_File::remove($path);
|
|||
|
} elseif (is_file($path)) {
|
|||
|
$returnValue = @unlink($path);
|
|||
|
}
|
|||
|
// else fail silently
|
|||
|
|
|||
|
return (bool) $returnValue;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Move file from source to destination.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @param string $source A path to the source file.
|
|||
|
* @param string $destination A path to the destination file.
|
|||
|
* @return boolean Returns true if the file was successfully moved, false otherwise.
|
|||
|
*/
|
|||
|
public static function move($source, $destination)
|
|||
|
{
|
|||
|
$returnValue = (bool) false;
|
|||
|
|
|||
|
if (is_dir($source)) {
|
|||
|
if (!file_exists($destination)) {
|
|||
|
mkdir($destination, 0777, true);
|
|||
|
}
|
|||
|
$error = false;
|
|||
|
foreach (scandir($source) as $file) {
|
|||
|
if ($file != '.' && $file != '..') {
|
|||
|
if (is_dir($source . '/' . $file)) {
|
|||
|
if (!self::move($source . '/' . $file, $destination . '/' . $file, true)) {
|
|||
|
$error = true;
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (!self::copy($source . '/' . $file, $destination . '/' . $file, true)) {
|
|||
|
$error = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (!$error) {
|
|||
|
$returnValue = true;
|
|||
|
}
|
|||
|
self::remove($source, true);
|
|||
|
} else {
|
|||
|
if (file_exists($source) && file_exists($destination)) {
|
|||
|
$returnValue = rename($source, $destination);
|
|||
|
} else {
|
|||
|
if (self::copy($source, $destination, true)) {
|
|||
|
$returnValue = self::remove($source);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (bool) $returnValue;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve mime-types that are recognized by the TAO platform.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @return array An associative array of mime-types where keys are the extension related to the mime-type. Values of the array are mime-types.
|
|||
|
*/
|
|||
|
public static function getMimeTypeList()
|
|||
|
{
|
|||
|
$returnValue = [
|
|||
|
|
|||
|
'txt' => 'text/plain',
|
|||
|
'htm' => 'text/html',
|
|||
|
'html' => 'text/html',
|
|||
|
'xhtml' => 'application/xhtml+xml',
|
|||
|
'php' => 'text/html',
|
|||
|
'css' => 'text/css',
|
|||
|
'js' => 'application/javascript',
|
|||
|
'json' => 'application/json',
|
|||
|
'xml' => 'text/xml',
|
|||
|
'rdf' => 'text/xml',
|
|||
|
'swf' => 'application/x-shockwave-flash',
|
|||
|
'flv' => 'video/x-flv',
|
|||
|
'csv' => 'text/csv',
|
|||
|
'rtx' => 'text/richtext',
|
|||
|
'rtf' => 'text/rtf',
|
|||
|
|
|||
|
// images
|
|||
|
'png' => 'image/png',
|
|||
|
'jpe' => 'image/jpeg',
|
|||
|
'jpeg' => 'image/jpeg',
|
|||
|
'jpg' => 'image/jpeg',
|
|||
|
'gif' => 'image/gif',
|
|||
|
'bmp' => 'image/bmp',
|
|||
|
'ico' => 'image/vnd.microsoft.icon',
|
|||
|
'tiff' => 'image/tiff',
|
|||
|
'tif' => 'image/tiff',
|
|||
|
'svg' => self::MIME_SVG,
|
|||
|
'svgz' => self::MIME_SVG,
|
|||
|
|
|||
|
// archives
|
|||
|
'zip' => 'application/zip',
|
|||
|
'rar' => 'application/x-rar-compressed',
|
|||
|
'exe' => 'application/x-msdownload',
|
|||
|
'msi' => 'application/x-msdownload',
|
|||
|
'cab' => 'application/vnd.ms-cab-compressed',
|
|||
|
|
|||
|
// audio/video
|
|||
|
'mp3' => 'audio/mpeg',
|
|||
|
'oga' => 'audio/ogg',
|
|||
|
'ogg' => 'audio/ogg',
|
|||
|
'aac' => 'audio/aac',
|
|||
|
'qt' => 'video/quicktime',
|
|||
|
'mov' => 'video/quicktime',
|
|||
|
'mp4' => 'video/mp4',//(H.264 + AAC) for ie8, etc.
|
|||
|
'webm' => 'video/webm',//(VP8 + Vorbis) for ie9, ff, chrome, android, opera
|
|||
|
'ogv' => 'video/ogg',//ff, chrome, opera
|
|||
|
|
|||
|
// adobe
|
|||
|
'pdf' => 'application/pdf',
|
|||
|
'psd' => 'image/vnd.adobe.photoshop',
|
|||
|
'ai' => 'application/postscript',
|
|||
|
'eps' => 'application/postscript',
|
|||
|
'ps' => 'application/postscript',
|
|||
|
|
|||
|
// ms office
|
|||
|
'doc' => 'application/msword',
|
|||
|
'rtf' => 'application/rtf',
|
|||
|
'xls' => 'application/vnd.ms-excel',
|
|||
|
'ppt' => 'application/vnd.ms-powerpoint',
|
|||
|
|
|||
|
// open office
|
|||
|
'odt' => 'application/vnd.oasis.opendocument.text',
|
|||
|
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
|
|||
|
|
|||
|
// fonts
|
|||
|
'woff' => 'application/x-font-woff',
|
|||
|
'eot' => 'application/vnd.ms-fontobject',
|
|||
|
'ttf' => 'application/x-font-ttf'
|
|||
|
];
|
|||
|
|
|||
|
return (array) $returnValue;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve file extensions usually associated to a given mime-type.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @param string $mimeType A mime-type which is recognized by the platform.
|
|||
|
* @return string The extension usually associated to the mime-type. If it could not be retrieved, an empty string is returned.
|
|||
|
*/
|
|||
|
public static function getExtention($mimeType)
|
|||
|
{
|
|||
|
$returnValue = (string) '';
|
|||
|
|
|||
|
foreach (self::getMimeTypeList() as $key => $value) {
|
|||
|
if ($value == trim($mimeType)) {
|
|||
|
$returnValue = $key;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return (string) $returnValue;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve file extensions of a file
|
|||
|
*
|
|||
|
* @param string $path the path of the file we want to get the extension
|
|||
|
* @return string The extension of the parameter file
|
|||
|
*/
|
|||
|
public static function getFileExtention($path)
|
|||
|
{
|
|||
|
|
|||
|
$ext = pathinfo($path, PATHINFO_EXTENSION);
|
|||
|
|
|||
|
if ($ext === '') {
|
|||
|
$splitedPath = explode('.', $path);
|
|||
|
$ext = end($splitedPath);
|
|||
|
}
|
|||
|
|
|||
|
return $ext;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the mime-type of the file in parameter.
|
|||
|
* different methods are used regarding the configuration of the server.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @param string $path
|
|||
|
* @param boolean $ext If set to true, the extension of the file will be used to retrieve the mime-type. If now extension can be found, 'text/plain' is returned by the method.
|
|||
|
* @return string The associated mime-type.
|
|||
|
*/
|
|||
|
public static function getMimeType($path, $ext = false)
|
|||
|
{
|
|||
|
$mime_types = self::getMimeTypeList();
|
|||
|
|
|||
|
if (false == $ext) {
|
|||
|
$ext = pathinfo($path, PATHINFO_EXTENSION);
|
|||
|
|
|||
|
if (array_key_exists($ext, $mime_types)) {
|
|||
|
$mimetype = $mime_types[$ext];
|
|||
|
} else {
|
|||
|
$mimetype = '';
|
|||
|
}
|
|||
|
|
|||
|
if (!in_array($ext, ['css', 'ogg', 'mp3', 'svg', 'svgz'])) {
|
|||
|
if (file_exists($path)) {
|
|||
|
if (function_exists('finfo_open')) {
|
|||
|
$finfo = finfo_open(FILEINFO_MIME);
|
|||
|
$mimetype = finfo_file($finfo, $path);
|
|||
|
finfo_close($finfo);
|
|||
|
} elseif (function_exists('mime_content_type')) {
|
|||
|
$mimetype = mime_content_type($path);
|
|||
|
}
|
|||
|
if (!empty($mimetype)) {
|
|||
|
if (preg_match("/; charset/", $mimetype)) {
|
|||
|
$mimetypeInfos = explode(';', $mimetype);
|
|||
|
$mimetype = $mimetypeInfos[0];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
// find out the mime-type from the extension of the file.
|
|||
|
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
|||
|
if (array_key_exists($ext, $mime_types)) {
|
|||
|
$mimetype = $mime_types[$ext];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// If no mime-type found ...
|
|||
|
if (empty($mimetype)) {
|
|||
|
$mimetype = 'application/octet-stream';
|
|||
|
}
|
|||
|
|
|||
|
return (string) $mimetype;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* creates a directory in the system's temp dir.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @return string The path to the created folder.
|
|||
|
*/
|
|||
|
public static function createTempDir()
|
|||
|
{
|
|||
|
do {
|
|||
|
$folder = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "tmp" . mt_rand() . DIRECTORY_SEPARATOR;
|
|||
|
} while (file_exists($folder));
|
|||
|
mkdir($folder);
|
|||
|
return $folder;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* deletes a directory and its content.
|
|||
|
*
|
|||
|
* @author Lionel Lecaque, <lionel@taotesting.com>
|
|||
|
* @param string directory absolute path of the directory
|
|||
|
* @return boolean true if the directory and its content were deleted, false otherwise.
|
|||
|
*/
|
|||
|
public static function delTree($directory)
|
|||
|
{
|
|||
|
|
|||
|
$files = array_diff(scandir($directory), ['.','..']);
|
|||
|
foreach ($files as $file) {
|
|||
|
$abspath = $directory . DIRECTORY_SEPARATOR . $file;
|
|||
|
if (is_dir($abspath)) {
|
|||
|
self::delTree($abspath);
|
|||
|
} else {
|
|||
|
unlink($abspath);
|
|||
|
}
|
|||
|
}
|
|||
|
return rmdir($directory);
|
|||
|
}
|
|||
|
|
|||
|
public static function isIdentical($path1, $path2)
|
|||
|
{
|
|||
|
return self::md5_dir($path1) == self::md5_dir($path2);
|
|||
|
}
|
|||
|
|
|||
|
public static function md5_dir($path)
|
|||
|
{
|
|||
|
if (is_file($path)) {
|
|||
|
$md5 = md5_file($path);
|
|||
|
} elseif (is_dir($path)) {
|
|||
|
$filemd5s = [];
|
|||
|
// using scandir to get files in a fixed order
|
|||
|
$files = scandir($path);
|
|||
|
sort($files);
|
|||
|
foreach ($files as $basename) {
|
|||
|
if ($basename != '.' && $basename != '..') {
|
|||
|
//$fileInfo->getFilename()
|
|||
|
$filemd5s[] = $basename . self::md5_dir(self::concat([$path, $basename]));
|
|||
|
}
|
|||
|
}
|
|||
|
$md5 = md5(implode('', $filemd5s));
|
|||
|
} else {
|
|||
|
throw new common_Exception(__FUNCTION__ . ' called on non file or directory "' . $path . '"');
|
|||
|
}
|
|||
|
return $md5;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Create a zip of a directory or file
|
|||
|
*
|
|||
|
* @param string $src path to the files to zip
|
|||
|
* @param bool $withEmptyDir
|
|||
|
* @return string path to the zip file
|
|||
|
* @throws common_Exception if unable to create the zip
|
|||
|
*/
|
|||
|
public static function createZip($src, $withEmptyDir = false)
|
|||
|
{
|
|||
|
$zipArchive = new \ZipArchive();
|
|||
|
$path = self::createTempDir() . 'file.zip';
|
|||
|
if ($zipArchive->open($path, \ZipArchive::CREATE) !== true) {
|
|||
|
throw new common_Exception('Unable to create zipfile ' . $path);
|
|||
|
}
|
|||
|
self::addFilesToZip($zipArchive, $src, DIRECTORY_SEPARATOR, $withEmptyDir);
|
|||
|
$zipArchive->close();
|
|||
|
return $path;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Add files or folders (and their content) to the Zip Archive that will contain all the files to the current export session.
|
|||
|
* For instance, if you want to copy the file 'taoItems/data/i123/item.xml' as 'myitem.xml' to your archive call addFile('path_to_item_location/item.xml', 'myitem.xml').
|
|||
|
* As a result, you will get a file entry in the final ZIP archive at '/i123/myitem.xml'.
|
|||
|
*
|
|||
|
* @param ZipArchive $zipArchive the archive to add to
|
|||
|
* @param string $src | StreamInterface The path to the source file or folder to copy into the ZIP Archive.
|
|||
|
* @param $dest
|
|||
|
* @param bool $withEmptyDir
|
|||
|
* @return integer The amount of files that were transfered from TAO to the ZIP archive within the method call.
|
|||
|
*/
|
|||
|
public static function addFilesToZip(ZipArchive $zipArchive, $src, $dest, $withEmptyDir = false)
|
|||
|
{
|
|||
|
$returnValue = null;
|
|||
|
|
|||
|
$done = 0;
|
|||
|
|
|||
|
if ($src instanceof \Psr\Http\Message\StreamInterface) {
|
|||
|
if ($zipArchive->addFromString(ltrim($dest, "/\\"), $src->getContents())) {
|
|||
|
$done++;
|
|||
|
}
|
|||
|
} elseif (is_resource($src)) {
|
|||
|
fseek($src, 0);
|
|||
|
$content = stream_get_contents($src);
|
|||
|
if ($zipArchive->addFromString(ltrim($dest, "/\\"), $content)) {
|
|||
|
$done++;
|
|||
|
}
|
|||
|
} elseif (is_dir($src)) {
|
|||
|
// Go deeper in folder hierarchy !
|
|||
|
$src = rtrim($src, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
|||
|
$dest = rtrim($dest, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
|||
|
|
|||
|
if ($withEmptyDir) {
|
|||
|
$zipArchive->addEmptyDir($dest);
|
|||
|
}
|
|||
|
|
|||
|
// Recursively copy.
|
|||
|
$content = scandir($src);
|
|||
|
|
|||
|
foreach ($content as $file) {
|
|||
|
// avoid . , .. , .svn etc ...
|
|||
|
if (!preg_match("/^\./", $file)) {
|
|||
|
$done += self::addFilesToZip($zipArchive, $src . $file, $dest . $file, $withEmptyDir);
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
// Simply copy the file. Beware of leading slashes
|
|||
|
if ($zipArchive->addFile($src, ltrim($dest, DIRECTORY_SEPARATOR))) {
|
|||
|
$done++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$returnValue = $done;
|
|||
|
|
|||
|
return $returnValue;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Unzip archive file
|
|||
|
*
|
|||
|
* @param string|File $archiveFile
|
|||
|
* @return string path to temporary directory zipfile was extracted to
|
|||
|
*
|
|||
|
* @throws \common_Exception
|
|||
|
*/
|
|||
|
public static function extractArchive($archiveFile)
|
|||
|
{
|
|||
|
if ($archiveFile instanceof File) {
|
|||
|
if (!$archiveFile->exists()) {
|
|||
|
throw new \common_Exception('Unable to open archive ' . '/' . $archiveFile->getPrefix());
|
|||
|
}
|
|||
|
$tmpDir = static::createTempDir();
|
|||
|
$tmpFilePath = $tmpDir . uniqid($archiveFile->getBasename(), true) . '.zip';
|
|||
|
$tmpFile = fopen($tmpFilePath, 'w');
|
|||
|
$originalPackage = $archiveFile->readStream();
|
|||
|
stream_copy_to_stream($originalPackage, $tmpFile);
|
|||
|
fclose($originalPackage);
|
|||
|
fclose($tmpFile);
|
|||
|
$archiveFile = $tmpFilePath;
|
|||
|
}
|
|||
|
|
|||
|
$archiveObj = new \ZipArchive();
|
|||
|
$archiveHandle = $archiveObj->open($archiveFile);
|
|||
|
|
|||
|
if (true !== $archiveHandle) {
|
|||
|
throw new \common_Exception('Unable to open archive ' . $archiveFile);
|
|||
|
}
|
|||
|
|
|||
|
if (static::checkWhetherArchiveIsBomb($archiveObj)) {
|
|||
|
throw new \common_Exception(sprintf('Source "%s" seems to be a ZIP bomb', $archiveFile));
|
|||
|
}
|
|||
|
|
|||
|
$archiveDir = static::createTempDir();
|
|||
|
if (!$archiveObj->extractTo($archiveDir)) {
|
|||
|
$archiveObj->close();
|
|||
|
throw new \common_Exception('Unable to extract to ' . $archiveDir);
|
|||
|
}
|
|||
|
$archiveObj->close();
|
|||
|
|
|||
|
if (isset($tmpFilePath) && file_exists($tmpFilePath)) {
|
|||
|
unlink($tmpFilePath);
|
|||
|
}
|
|||
|
if (isset($tmpDir) && file_exists($tmpDir)) {
|
|||
|
rmdir($tmpDir);
|
|||
|
}
|
|||
|
|
|||
|
return $archiveDir;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Rename in Zip
|
|||
|
*
|
|||
|
* Rename an item in a ZIP archive. Works for files and directories.
|
|||
|
*
|
|||
|
* In case of renaming directories, the return value of this method will be the amount of files
|
|||
|
* affected by the directory renaming.
|
|||
|
*
|
|||
|
* @param ZipArchive $zipArchive An open ZipArchive object.
|
|||
|
* @param string $oldname
|
|||
|
* @param string $newname
|
|||
|
* @return int The amount of renamed entries.
|
|||
|
*/
|
|||
|
public static function renameInZip(ZipArchive $zipArchive, $oldname, $newname)
|
|||
|
{
|
|||
|
$i = 0;
|
|||
|
$renameCount = 0;
|
|||
|
|
|||
|
while (($entryName = $zipArchive->getNameIndex($i)) || ($statIndex = $zipArchive->statIndex($i, ZipArchive::FL_UNCHANGED))) {
|
|||
|
if ($entryName) {
|
|||
|
$newEntryName = str_replace($oldname, $newname, $entryName);
|
|||
|
if ($zipArchive->renameIndex($i, $newEntryName)) {
|
|||
|
$renameCount++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$i++;
|
|||
|
}
|
|||
|
|
|||
|
return $renameCount;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Helps prevent decompression attacks.
|
|||
|
* Since this method checks archive file size, it needs filename property to be set,
|
|||
|
* so ZipArchive object should be already opened.
|
|||
|
*
|
|||
|
* @param \ZipArchive $archive
|
|||
|
* @param int $minCompressionRatioToBeBomb archive content size / archive size
|
|||
|
* @return bool
|
|||
|
* @throws common_Exception
|
|||
|
*
|
|||
|
* @link https://en.wikipedia.org/wiki/Zip_bomb
|
|||
|
*/
|
|||
|
public static function checkWhetherArchiveIsBomb(\ZipArchive $archive, $minCompressionRatioToBeBomb = 200)
|
|||
|
{
|
|||
|
if (!$archive->filename) {
|
|||
|
throw new common_Exception('ZIP archive should be opened before checking for a ZIP bomb');
|
|||
|
}
|
|||
|
|
|||
|
$contentSize = 0;
|
|||
|
for ($fileIndex = 0; $fileIndex < $archive->numFiles; $fileIndex++) {
|
|||
|
$stats = $archive->statIndex($fileIndex);
|
|||
|
$contentSize += $stats['size'];
|
|||
|
}
|
|||
|
|
|||
|
$archiveFileSize = filesize($archive->filename);
|
|||
|
|
|||
|
return $archiveFileSize * $minCompressionRatioToBeBomb < $contentSize;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Exclude from Zip
|
|||
|
*
|
|||
|
* Exclude entries matching $pattern from a ZIP Archive.
|
|||
|
*
|
|||
|
* @param ZipArchive $zipArchive An open ZipArchive object.
|
|||
|
* @param string $pattern A PCRE pattern.
|
|||
|
* @return int The amount of excluded entries.
|
|||
|
*/
|
|||
|
public static function excludeFromZip(ZipArchive $zipArchive, $pattern)
|
|||
|
{
|
|||
|
$i = 0;
|
|||
|
$exclusionCount = 0;
|
|||
|
|
|||
|
while (($entryName = $zipArchive->getNameIndex($i)) || ($statIndex = $zipArchive->statIndex($i, ZipArchive::FL_UNCHANGED))) {
|
|||
|
if ($entryName) {
|
|||
|
// Not previously removed index.
|
|||
|
if (preg_match($pattern, $entryName) === 1 && $zipArchive->deleteIndex($i)) {
|
|||
|
$exclusionCount++;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$i++;
|
|||
|
}
|
|||
|
|
|||
|
return $exclusionCount;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get All Zip Names
|
|||
|
*
|
|||
|
* Retrieve all ZIP name entries in a ZIP archive. In others words, all the paths in the
|
|||
|
* archive having an entry.
|
|||
|
*
|
|||
|
* @param ZipArchive $zipArchive An open ZipArchive object.
|
|||
|
* @return array An array of strings.
|
|||
|
*/
|
|||
|
public static function getAllZipNames(ZipArchive $zipArchive)
|
|||
|
{
|
|||
|
$i = 0;
|
|||
|
$entries = [];
|
|||
|
|
|||
|
while (($entryName = $zipArchive->getNameIndex($i)) || ($statIndex = $zipArchive->statIndex($i, ZipArchive::FL_UNCHANGED))) {
|
|||
|
if ($entryName) {
|
|||
|
$entries[] = $entryName;
|
|||
|
}
|
|||
|
|
|||
|
$i++;
|
|||
|
}
|
|||
|
|
|||
|
return $entries;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Gets the local path to a publicly available resource
|
|||
|
* no verification if the file should be accessible
|
|||
|
*
|
|||
|
* @param string $url
|
|||
|
* @throws common_Exception
|
|||
|
* @return string
|
|||
|
*/
|
|||
|
public static function getPathFromUrl($url)
|
|||
|
{
|
|||
|
if (substr($url, 0, strlen(ROOT_URL)) != ROOT_URL) {
|
|||
|
throw new common_Exception($url . ' does not lie within the tao instalation path');
|
|||
|
}
|
|||
|
$subUrl = substr($url, strlen(ROOT_URL));
|
|||
|
$parts = [];
|
|||
|
foreach (explode('/', $subUrl) as $directory) {
|
|||
|
$parts[] = urldecode($directory);
|
|||
|
}
|
|||
|
$path = ROOT_PATH . implode(DIRECTORY_SEPARATOR, $parts);
|
|||
|
if (self::securityCheck($path)) {
|
|||
|
return $path;
|
|||
|
} else {
|
|||
|
throw new common_Exception($url . ' is not secure');
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get a safe filename for a proposed filename.
|
|||
|
*
|
|||
|
* If directory is specified it will return a filename which is
|
|||
|
* safe to not overwritte an existing file. This function is not injective.
|
|||
|
*
|
|||
|
* @param string $fileName
|
|||
|
* @param string $directory
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*/
|
|||
|
public static function getSafeFileName($fileName, $directory = null)
|
|||
|
{
|
|||
|
$lastDot = strrpos($fileName, '.');
|
|||
|
$file = $lastDot ? substr($fileName, 0, $lastDot) : $fileName;
|
|||
|
$ending = $lastDot ? substr($fileName, $lastDot + 1) : '';
|
|||
|
$safeName = self::removeSpecChars($file);
|
|||
|
$safeEnding = empty($ending)
|
|||
|
? ''
|
|||
|
: '.' . self::removeSpecChars($ending);
|
|||
|
|
|||
|
if ($directory != null && file_exists($directory . $safeName . $safeEnding)) {
|
|||
|
$count = 1;
|
|||
|
while (file_exists($directory . $safeName . '_' . $count . $safeEnding)) {
|
|||
|
$count++;
|
|||
|
}
|
|||
|
$safeName = $safeName . '_' . $count;
|
|||
|
}
|
|||
|
|
|||
|
return $safeName . $safeEnding;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Remove special characters for safe filenames
|
|||
|
*
|
|||
|
* @author Dieter Raber
|
|||
|
*
|
|||
|
* @param string $string
|
|||
|
* @param string $repl
|
|||
|
* @param string $lower
|
|||
|
*
|
|||
|
* @return string
|
|||
|
*/
|
|||
|
private static function removeSpecChars($string, $repl = '-', $lower = true)
|
|||
|
{
|
|||
|
$spec_chars = [
|
|||
|
'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'Ae', 'Å' => 'A','Æ' => 'A', 'Ç' => 'C',
|
|||
|
'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I',
|
|||
|
'Ï' => 'I', 'Ð' => 'E', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O',
|
|||
|
'Ö' => 'Oe', 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U','Û' => 'U', 'Ü' => 'Ue', 'Ý' => 'Y',
|
|||
|
'Þ' => 'T', 'ß' => 'ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'ae',
|
|||
|
'å' => 'a', 'æ' => 'ae', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
|
|||
|
'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'e', 'ñ' => 'n', 'ò' => 'o',
|
|||
|
'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'oe', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u',
|
|||
|
'û' => 'u', 'ü' => 'ue', 'ý' => 'y', 'þ' => 't', 'ÿ' => 'y', ' ' => $repl, '?' => $repl,
|
|||
|
'\'' => $repl, '.' => $repl, '/' => $repl, '&' => $repl, ')' => $repl, '(' => $repl,
|
|||
|
'[' => $repl, ']' => $repl, '_' => $repl, ',' => $repl, ':' => $repl, '-' => $repl,
|
|||
|
'!' => $repl, '"' => $repl, '`' => $repl, '°' => $repl, '%' => $repl, ' ' => $repl,
|
|||
|
' ' => $repl, '{' => $repl, '}' => $repl, '#' => $repl, '’' => $repl
|
|||
|
];
|
|||
|
$string = strtr($string, $spec_chars);
|
|||
|
$string = trim(preg_replace("~[^a-z0-9]+~i", $repl, $string), $repl);
|
|||
|
return $lower ? strtolower($string) : $string;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Check if the directory is empty
|
|||
|
*
|
|||
|
* @param string $directory
|
|||
|
* @return boolean
|
|||
|
*/
|
|||
|
public static function isDirEmpty($directory)
|
|||
|
{
|
|||
|
$path = self::concat([$directory, '*']);
|
|||
|
return (count(glob($path, GLOB_NOSORT)) === 0 );
|
|||
|
}
|
|||
|
}
|