| Current Path : /home/wuectly/www/03cbe/ |
| Current File : /home/wuectly/www/03cbe/File.php.tar |
home/wuectly/www/libraries/src/Filesystem/File.php 0000604 00000037010 15245572101 0016272 0 ustar 00 <?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2006 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Filesystem;
defined('JPATH_PLATFORM') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Filesystem\Wrapper\PathWrapper;
use Joomla\CMS\Filesystem\Wrapper\FolderWrapper;
use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Client\FtpClient;
/**
* A File handling class
*
* @since 1.7.0
*/
class File
{
/**
* Gets the extension of a file name
*
* @param string $file The file name
*
* @return string The file extension
*
* @since 1.7.0
*/
public static function getExt($file)
{
// String manipulation should be faster than pathinfo() on newer PHP versions.
$dot = strrpos($file, '.');
if ($dot === false)
{
return '';
}
$ext = substr($file, $dot + 1);
// Extension cannot contain slashes.
if (strpos($ext, '/') !== false || (DIRECTORY_SEPARATOR === '\\' && strpos($ext, '\\') !== false))
{
return '';
}
return $ext;
}
/**
* Strips the last extension off of a file name
*
* @param string $file The file name
*
* @return string The file name without the extension
*
* @since 1.7.0
*/
public static function stripExt($file)
{
return preg_replace('#\.[^.]*$#', '', $file);
}
/**
* Makes file name safe to use
*
* @param string $file The name of the file [not full path]
*
* @return string The sanitised string
*
* @since 1.7.0
*/
public static function makeSafe($file)
{
// Remove any trailing dots, as those aren't ever valid file names.
$file = rtrim($file, '.');
$regex = array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#');
return trim(preg_replace($regex, '', $file));
}
/**
* Copies a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function copy($src, $dest, $path = null, $useStreams = false)
{
$pathObject = new PathWrapper;
// Prepend a base path if it exists
if ($path)
{
$src = $pathObject->clean($path . '/' . $src);
$dest = $pathObject->clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_FIND_COPY', $src), Log::WARNING, 'jerror');
return false;
}
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->copy($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_STREAMS', $src, $dest, $stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
// If the parent folder doesn't exist we must create it
if (!file_exists(dirname($dest)))
{
$folderObject = new FolderWrapper;
$folderObject->create(dirname($dest));
}
// Translate the destination path for the FTP account
$dest = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
if (!$ftp->store($src, $dest))
{
// FTP connector throws an error
return false;
}
$ret = true;
}
else
{
if (!@ copy($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_COPY_FAILED_ERR01', $src, $dest), Log::WARNING, 'jerror');
return false;
}
$ret = true;
}
return $ret;
}
}
/**
* Delete a file or array of files
*
* @param mixed $file The file name or an array of file names
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function delete($file)
{
$FTPOptions = ClientHelper::getCredentials('ftp');
$pathObject = new PathWrapper;
if (is_array($file))
{
$files = $file;
}
else
{
$files[] = $file;
}
// Do NOT use ftp if it is not enabled
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
}
foreach ($files as $file)
{
$file = $pathObject->clean($file);
if (!is_file($file))
{
continue;
}
// Try making the file writable first. If it's read-only, it can't be deleted
// on Windows, even if the parent folder is writable
@chmod($file, 0777);
// In case of restricted permissions we zap it one way or the other
// as long as the owner is either the webserver or the ftp
if (@unlink($file))
{
// Do nothing
}
elseif ($FTPOptions['enabled'] == 1)
{
$file = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
if (!$ftp->delete($file))
{
// FTP connector throws an error
return false;
}
}
else
{
$filename = basename($file);
Log::add(Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', $filename), Log::WARNING, 'jerror');
return false;
}
}
return true;
}
/**
* Moves a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function move($src, $dest, $path = '', $useStreams = false)
{
$pathObject = new PathWrapper;
if ($path)
{
$src = $pathObject->clean($path . '/' . $src);
$dest = $pathObject->clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src))
{
Log::add(Text::_('JLIB_FILESYSTEM_CANNOT_FIND_SOURCE_FILE'), Log::WARNING, 'jerror');
return false;
}
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->move($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_MOVE_STREAMS', $stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
// Translate path for the FTP account
$src = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
$dest = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
// Use FTP rename to simulate move
if (!$ftp->rename($src, $dest))
{
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
return false;
}
}
else
{
if (!@ rename($src, $dest))
{
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');
return false;
}
}
return true;
}
}
/**
* Read the contents of a file
*
* @param string $filename The full file path
* @param boolean $incpath Use include path
* @param integer $amount Amount of file to read
* @param integer $chunksize Size of chunks to read
* @param integer $offset Offset of the file
*
* @return mixed Returns file contents or boolean False if failed
*
* @since 1.7.0
* @deprecated 4.0 - Use the native file_get_contents() instead.
*/
public static function read($filename, $incpath = false, $amount = 0, $chunksize = 8192, $offset = 0)
{
Log::add(__METHOD__ . ' is deprecated. Use native file_get_contents() syntax.', Log::WARNING, 'deprecated');
$data = null;
if ($amount && $chunksize > $amount)
{
$chunksize = $amount;
}
if (false === $fh = fopen($filename, 'rb', $incpath))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_READ_UNABLE_TO_OPEN_FILE', $filename), Log::WARNING, 'jerror');
return false;
}
clearstatcache();
if ($offset)
{
fseek($fh, $offset);
}
if ($fsize = @ filesize($filename))
{
if ($amount && $fsize > $amount)
{
$data = fread($fh, $amount);
}
else
{
$data = fread($fh, $fsize);
}
}
else
{
$data = '';
/*
* While it's:
* 1: Not the end of the file AND
* 2a: No Max Amount set OR
* 2b: The length of the data is less than the max amount we want
*/
while (!feof($fh) && (!$amount || strlen($data) < $amount))
{
$data .= fread($fh, $chunksize);
}
}
fclose($fh);
return $data;
}
/**
* Write contents to a file
*
* @param string $file The full file path
* @param string $buffer The buffer to write
* @param boolean $useStreams Use streams
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function write($file, $buffer, $useStreams = false)
{
@set_time_limit(ini_get('max_execution_time'));
// If the destination directory doesn't exist we need to create it
if (!file_exists(dirname($file)))
{
$folderObject = new FolderWrapper;
if ($folderObject->create(dirname($file)) == false)
{
return false;
}
}
if ($useStreams)
{
$stream = Factory::getStream();
// Beef up the chunk size to a meg
$stream->set('chunksize', (1024 * 1024));
if (!$stream->writeFile($file, $buffer))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', $file, $stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
$pathObject = new PathWrapper;
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
// Translate path for the FTP account and use FTP write buffer to file
$file = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
$ret = $ftp->write($file, $buffer);
}
else
{
$file = $pathObject->clean($file);
$ret = is_int(file_put_contents($file, $buffer)) ? true : false;
}
return $ret;
}
}
/**
* Append contents to a file
*
* @param string $file The full file path
* @param string $buffer The buffer to write
* @param boolean $useStreams Use streams
*
* @return boolean True on success
*
* @since 3.6.0
*/
public static function append($file, $buffer, $useStreams = false)
{
@set_time_limit(ini_get('max_execution_time'));
// If the file doesn't exist, just write instead of append
if (!file_exists($file))
{
return self::write($file, $buffer, $useStreams);
}
if ($useStreams)
{
$stream = Factory::getStream();
// Beef up the chunk size to a meg
$stream->set('chunksize', (1024 * 1024));
if ($stream->open($file, 'ab') && $stream->write($buffer) && $stream->close())
{
return true;
}
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', $file, $stream->getError()), Log::WARNING, 'jerror');
return false;
}
else
{
// Initialise variables.
$FTPOptions = ClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
// Translate path for the FTP account and use FTP write buffer to file
$file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
$ret = $ftp->append($file, $buffer);
}
else
{
$file = Path::clean($file);
$ret = is_int(file_put_contents($file, $buffer, FILE_APPEND));
}
return $ret;
}
}
/**
* Moves an uploaded file to a destination folder
*
* @param string $src The name of the php (temporary) uploaded file
* @param string $dest The path (including filename) to move the uploaded file to
* @param boolean $useStreams True to use streams
* @param boolean $allowUnsafe Allow the upload of unsafe files
* @param boolean $safeFileOptions Options to \JFilterInput::isSafeFile
*
* @return boolean True on success
*
* @since 1.7.0
*/
public static function upload($src, $dest, $useStreams = false, $allowUnsafe = false, $safeFileOptions = array())
{
if (!$allowUnsafe)
{
$descriptor = array(
'tmp_name' => $src,
'name' => basename($dest),
'type' => '',
'error' => '',
'size' => '',
);
$isSafe = \JFilterInput::isSafeFile($descriptor, $safeFileOptions);
if (!$isSafe)
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR03', $dest), Log::WARNING, 'jerror');
return false;
}
}
// Ensure that the path is valid and clean
$pathObject = new PathWrapper;
$dest = $pathObject->clean($dest);
// Create the destination directory if it does not exist
$baseDir = dirname($dest);
if (!file_exists($baseDir))
{
$folderObject = new FolderWrapper;
$folderObject->create($baseDir);
}
if ($useStreams)
{
$stream = Factory::getStream();
if (!$stream->upload($src, $dest))
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_UPLOAD', $stream->getError()), Log::WARNING, 'jerror');
return false;
}
return true;
}
else
{
$FTPOptions = ClientHelper::getCredentials('ftp');
$ret = false;
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
// Translate path for the FTP account
$dest = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');
// Copy the file to the destination directory
if (is_uploaded_file($src) && $ftp->store($src, $dest))
{
unlink($src);
$ret = true;
}
else
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
}
}
else
{
if (is_writeable($baseDir) && move_uploaded_file($src, $dest))
{
// Short circuit to prevent file permission errors
if ($pathObject->setPermissions($dest))
{
$ret = true;
}
else
{
Log::add(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01'), Log::WARNING, 'jerror');
}
}
else
{
Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
}
}
return $ret;
}
}
/**
* Wrapper for the standard file_exists function
*
* @param string $file File path
*
* @return boolean True if path is a file
*
* @since 1.7.0
*/
public static function exists($file)
{
$pathObject = new PathWrapper;
return is_file($pathObject->clean($file));
}
/**
* Returns the name, without any path.
*
* @param string $file File path
*
* @return string filename
*
* @since 1.7.0
* @deprecated 4.0 - Use basename() instead.
*/
public static function getName($file)
{
Log::add(__METHOD__ . ' is deprecated. Use native basename() syntax.', Log::WARNING, 'deprecated');
// Convert back slashes to forward slashes
$file = str_replace('\\', '/', $file);
$slash = strrpos($file, '/');
if ($slash !== false)
{
return substr($file, $slash + 1);
}
else
{
return $file;
}
}
}
home/wuectly/www/libraries/regularlabs/src/File.php 0000604 00000027333 15245607103 0016461 0 ustar 00 <?php
/**
* @package Regular Labs Library
* @version 21.4.10972
*
* @author Peter van Westen <info@regularlabs.com>
* @link http://www.regularlabs.com
* @copyright Copyright © 2021 Regular Labs All Rights Reserved
* @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Client\ClientHelper as JClientHelper;
use Joomla\CMS\Client\FtpClient as JFtpClient;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Filesystem\Folder as JFolder;
use Joomla\CMS\Filesystem\Path as JPath;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Log\Log as JLog;
use Joomla\CMS\Uri\Uri as JUri;
/**
* Class File
* @package RegularLabs\Library
*/
class File
{
/**
* Find a matching media file in the different possible extension media folders for given type
*
* @param string $type (css/js/...)
* @param string $file
*
* @return bool|string
*/
public static function getMediaFile($type, $file)
{
// If http is present in filename
if (strpos($file, 'http') === 0 || strpos($file, '//') === 0)
{
return $file;
}
$files = [];
// Detect debug mode
if (JFactory::getConfig()->get('debug') || JFactory::getApplication()->input->get('debug'))
{
$files[] = str_replace(['.min.', '-min.'], '.', $file);
}
$files[] = $file;
/*
* Loop on 1 or 2 files and break on first find.
* Add the content of the MD5SUM file located in the same folder to url to ensure cache browser refresh
* This MD5SUM file must represent the signature of the folder content
*/
foreach ($files as $check_file)
{
$file_found = self::findMediaFileByFile($check_file, $type);
if ( ! $file_found)
{
continue;
}
return $file_found;
}
return false;
}
/**
* Find a matching media file in the different possible extension media folders for given type
*
* @param string $file
* @param string $type (css/js/...)
*
* @return bool|string
*/
private static function findMediaFileByFile($file, $type)
{
$template = JFactory::getApplication()->getTemplate();
// If the file is in the template folder
$file_found = self::getFileUrl('/templates/' . $template . '/' . $type . '/' . $file);
if ($file_found)
{
return $file_found;
}
// Try to deal with system files in the media folder
if (strpos($file, '/') === false)
{
$file_found = self::getFileUrl('/media/system/' . $type . '/' . $file);
if ( ! $file_found)
{
return false;
}
return $file_found;
}
$paths = [];
// If the file contains any /: it can be in a media extension subfolder
// Divide the file extracting the extension as the first part before /
list($extension, $file) = explode('/', $file, 2);
$paths[] = '/media/' . $extension . '/' . $type;
$paths[] = '/templates/' . $template . '/' . $type . '/system';
$paths[] = '/media/system/' . $type;
foreach ($paths as $path)
{
$file_found = self::getFileUrl($path . '/' . $file);
if ( ! $file_found)
{
continue;
}
return $file_found;
}
return false;
}
/**
* Get the url for the file
*
* @param string $path
*
* @return bool|string
*/
private static function getFileUrl($path)
{
if ( ! file_exists(JPATH_ROOT . $path))
{
return false;
}
return JUri::root(true) . $path;
}
/**
* Delete a file or array of files
*
* @param mixed $file The file name or an array of file names
* @param boolean $show_messages Whether or not to show error messages
* @param int $min_age_in_minutes Minimum last modified age in minutes
*
* @return boolean True on success
*
* @since 11.1
*/
public static function delete($file, $show_messages = false, $min_age_in_minutes = 0)
{
$FTPOptions = JClientHelper::getCredentials('ftp');
$pathObject = new JPath;
$files = is_array($file) ? $file : [$file];
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = JFtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
}
foreach ($files as $file)
{
$file = $pathObject->clean($file);
if ( ! is_file($file))
{
continue;
}
if ($min_age_in_minutes && floor((time() - filemtime($file)) / 60) < $min_age_in_minutes)
{
continue;
}
// Try making the file writable first. If it's read-only, it can't be deleted
// on Windows, even if the parent folder is writable
@chmod($file, 0777);
if ($FTPOptions['enabled'] == 1)
{
$file = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
if ( ! $ftp->delete($file))
{
// FTP connector throws an error
return false;
}
}
// Try the unlink twice in case something was blocking it on first try
if ( ! @unlink($file) && ! @unlink($file))
{
$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), JLog::WARNING, 'jerror');
return false;
}
}
return true;
}
/**
* Delete a folder.
*
* @param string $path The path to the folder to delete.
* @param boolean $show_messages Whether or not to show error messages
* @param int $min_age_in_minutes Minimum last modified age in minutes
*
* @return boolean True on success.
*/
public static function deleteFolder($path, $show_messages = false, $min_age_in_minutes = 0)
{
@set_time_limit(ini_get('max_execution_time'));
$pathObject = new JPath;
if ( ! $path)
{
$show_messages && JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), JLog::WARNING, 'jerror');
return false;
}
// Check to make sure the path valid and clean
$path = $pathObject->clean($path);
if ( ! is_dir($path))
{
$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');
return false;
}
// Remove all the files in folder if they exist; disable all filtering
$files = JFolder::files($path, '.', false, true, [], []);
if ( ! empty($files))
{
if (self::delete($files, $show_messages, $min_age_in_minutes) !== true)
{
// JFile::delete throws an error
return false;
}
}
// Remove sub-folders of folder; disable all filtering
$folders = JFolder::folders($path, '.', false, true, [], []);
foreach ($folders as $folder)
{
if (is_link($folder))
{
// Don't descend into linked directories, just delete the link.
if (self::delete($folder, $show_messages, $min_age_in_minutes) !== true)
{
return false;
}
continue;
}
if ( ! self::deleteFolder($folder, $show_messages, $min_age_in_minutes))
{
return false;
}
}
// Skip if folder is not empty yet
if ( ! empty(JFolder::files($path, '.', false, true, [], []))
|| ! empty(JFolder::folders($path, '.', false, true, [], [])))
{
return true;
}
if (@rmdir($path))
{
return true;
}
$FTPOptions = JClientHelper::getCredentials('ftp');
if ($FTPOptions['enabled'] == 1)
{
// Connect the FTP client
$ftp = JFtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
// Translate path and delete
$path = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
// FTP connector throws an error
return $ftp->delete($path);
}
if ( ! @rmdir($path))
{
$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), JLog::WARNING, 'jerror');
return false;
}
return true;
}
public static function trimFolder($folder)
{
return trim(str_replace(['\\', '//'], '/', $folder), '/');
}
public static function isInternal($url)
{
return ! self::isExternal($url);
}
public static function isExternal($url)
{
if (strpos($url, '://') === false)
{
return false;
}
// hostname: give preference to SERVER_NAME, because this includes subdomains
$hostname = ($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];
return ! (strpos(RegEx::replace('^.*?://', '', $url), $hostname) === 0);
}
// some/url/to/a/file.ext
// > some/url/to/a
public static function getDirName($url)
{
$url = StringHelper::normalize($url);
return rtrim(dirname($url), '/');
}
// some/url/to/a/file.ext
// > file.ext
public static function getBaseName($url, $lowercase = false)
{
$url = StringHelper::normalize($url);
$basename = ltrim(basename($url), '/');
$parts = explode('?', $basename);
$basename = $parts[0];
if ($lowercase)
{
$basename = strtolower($basename);
}
return $basename;
}
// some/url/to/a/file.ext
// > file
public static function getFileName($url, $lowercase = false)
{
$url = StringHelper::normalize($url);
$info = pathinfo($url);
$filename = isset($info['filename']) ? $info['filename'] : $url;
if ($lowercase)
{
$filename = strtolower($filename);
}
return $filename;
}
// some/url/to/a/file.ext
// > ext
public static function getExtension($url)
{
$info = pathinfo($url);
if ( ! isset($info['extension']))
{
return '';
}
$ext = explode('?', $info['extension']);
return strtolower($ext[0]);
}
public static function isImage($url)
{
return self::isMedia($url, self::getFileTypes('images'));
}
public static function isVideo($url)
{
return self::isMedia($url, self::getFileTypes('videos'));
}
public static function isExternalVideo($url)
{
return (strpos($url, 'youtu.be') !== false
|| strpos($url, 'youtube.com') !== false
|| strpos($url, 'vimeo.com') !== false
);
}
public static function isDocument($url)
{
return self::isMedia($url, self::getFileTypes('documents'));
}
public static function isMedia($url, $filetypes = [])
{
$filetype = self::getExtension($url);
if ( ! $filetype)
{
return false;
}
if ( ! is_array($filetypes))
{
$filetypes = [$filetypes];
}
if (count($filetypes) == 1 && strpos($filetypes[0], ',') !== false)
{
$filetypes = ArrayHelper::toArray($filetypes[0]);
}
$filetypes = ! empty($filetypes) ? $filetypes : self::getFileTypes();
return in_array($filetype, $filetypes);
}
public static function getFileTypes($type = 'images')
{
switch ($type)
{
case 'image':
case 'images':
return [
'bmp',
'flif',
'gif',
'jpe',
'jpeg',
'jpg',
'png',
'tiff',
'eps',
];
case 'audio':
return [
'aif',
'aiff',
'mp3',
'wav',
];
case 'video':
case 'videos':
return [
'3g2',
'3gp',
'avi',
'divx',
'f4v',
'flv',
'm4v',
'mov',
'mp4',
'mpe',
'mpeg',
'mpg',
'ogv',
'swf',
'webm',
'wmv',
];
case 'document':
case 'documents':
return [
'doc',
'docm',
'docx',
'dotm',
'dotx',
'odb',
'odc',
'odf',
'odg',
'odi',
'odm',
'odp',
'ods',
'odt',
'onepkg',
'onetmp',
'onetoc',
'onetoc2',
'otg',
'oth',
'otp',
'ots',
'ott',
'oxt',
'pdf',
'potm',
'potx',
'ppam',
'pps',
'ppsm',
'ppsx',
'ppt',
'pptm',
'pptx',
'rtf',
'sldm',
'sldx',
'thmx',
'xla',
'xlam',
'xlc',
'xld',
'xll',
'xlm',
'xls',
'xlsb',
'xlsm',
'xlsx',
'xlt',
'xltm',
'xltx',
'xlw',
];
case 'other':
case 'others':
return [
'css',
'csv',
'js',
'json',
'tar',
'txt',
'xml',
'zip',
];
default:
case 'all':
return array_merge(
self::getFileTypes('images'),
self::getFileTypes('audio'),
self::getFileTypes('videos'),
self::getFileTypes('documents'),
self::getFileTypes('other')
);
}
}
}
home/wuectly/www/libraries/vendor/joomla/filesystem/src/File.php 0000604 00000015417 15245653313 0021124 0 ustar 00 <?php
/**
* Part of the Joomla Framework Filesystem Package
*
* @copyright Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE
*/
namespace Joomla\Filesystem;
use Joomla\Filesystem\Exception\FilesystemException;
/**
* A File handling class
*
* @since 1.0
*/
class File
{
/**
* Strips the last extension off of a file name
*
* @param string $file The file name
*
* @return string The file name without the extension
*
* @since 1.0
*/
public static function stripExt($file)
{
return preg_replace('#\.[^.]*$#', '', $file);
}
/**
* Makes the file name safe to use
*
* @param string $file The name of the file [not full path]
* @param array $stripChars Array of regex (by default will remove any leading periods)
*
* @return string The sanitised string
*
* @since 1.0
*/
public static function makeSafe($file, array $stripChars = array('#^\.#'))
{
$regex = array_merge(array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#'), $stripChars);
$file = preg_replace($regex, '', $file);
// Remove any trailing dots, as those aren't ever valid file names.
$file = rtrim($file, '.');
return $file;
}
/**
* Copies a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
* @throws \UnexpectedValueException
*/
public static function copy($src, $dest, $path = null, $useStreams = false)
{
// Prepend a base path if it exists
if ($path)
{
$src = Path::clean($path . '/' . $src);
$dest = Path::clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src))
{
throw new \UnexpectedValueException(
sprintf(
"%s: Cannot find or read file: %s",
__METHOD__,
Path::removeRoot($src)
)
);
}
if ($useStreams)
{
$stream = Stream::getStream();
if (!$stream->copy($src, $dest, null, false))
{
throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()));
}
return true;
}
if (!@ copy($src, $dest))
{
throw new FilesystemException(__METHOD__ . ': Copy failed.');
}
return true;
}
/**
* Delete a file or array of files
*
* @param mixed $file The file name or an array of file names
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
*/
public static function delete($file)
{
$files = (array) $file;
foreach ($files as $file)
{
$file = Path::clean($file);
$filename = basename($file);
if (!Path::canChmod($file))
{
throw new FilesystemException(__METHOD__ . ': Failed deleting inaccessible file ' . $filename);
}
// Try making the file writable first. If it's read-only, it can't be deleted
// on Windows, even if the parent folder is writable
@chmod($file, 0777);
// In case of restricted permissions we zap it one way or the other
// as long as the owner is either the webserver or the ftp
if (!@ unlink($file))
{
throw new FilesystemException(__METHOD__ . ': Failed deleting ' . $filename);
}
}
return true;
}
/**
* Moves a file
*
* @param string $src The path to the source file
* @param string $dest The path to the destination file
* @param string $path An optional base path to prefix to the file names
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
*/
public static function move($src, $dest, $path = '', $useStreams = false)
{
if ($path)
{
$src = Path::clean($path . '/' . $src);
$dest = Path::clean($path . '/' . $dest);
}
// Check src path
if (!is_readable($src))
{
return 'Cannot find source file.';
}
if ($useStreams)
{
$stream = Stream::getStream();
if (!$stream->move($src, $dest, null, false))
{
throw new FilesystemException(__METHOD__ . ': ' . $stream->getError());
}
return true;
}
if (!@ rename($src, $dest))
{
throw new FilesystemException(__METHOD__ . ': Rename failed.');
}
return true;
}
/**
* Write contents to a file
*
* @param string $file The full file path
* @param string $buffer The buffer to write
* @param boolean $useStreams Use streams
* @param boolean $appendToFile Append to the file and not overwrite it.
*
* @return boolean True on success
*
* @since 1.0
*/
public static function write($file, &$buffer, $useStreams = false, $appendToFile = false)
{
@set_time_limit(ini_get('max_execution_time'));
// If the destination directory doesn't exist we need to create it
if (!file_exists(\dirname($file)))
{
Folder::create(\dirname($file));
}
if ($useStreams)
{
$stream = Stream::getStream();
// Beef up the chunk size to a meg
$stream->set('chunksize', (1024 * 1024));
$stream->writeFile($file, $buffer, $appendToFile);
return true;
}
$file = Path::clean($file);
// Set the required flag to only append to the file and not overwrite it
if ($appendToFile === true)
{
return \is_int(file_put_contents($file, $buffer, \FILE_APPEND));
}
return \is_int(file_put_contents($file, $buffer));
}
/**
* Moves an uploaded file to a destination folder
*
* @param string $src The name of the php (temporary) uploaded file
* @param string $dest The path (including filename) to move the uploaded file to
* @param boolean $useStreams True to use streams
*
* @return boolean True on success
*
* @since 1.0
* @throws FilesystemException
*/
public static function upload($src, $dest, $useStreams = false)
{
// Ensure that the path is valid and clean
$dest = Path::clean($dest);
// Create the destination directory if it does not exist
$baseDir = \dirname($dest);
if (!is_dir($baseDir))
{
Folder::create($baseDir);
}
if ($useStreams)
{
$stream = Stream::getStream();
if (!$stream->upload($src, $dest, null, false))
{
throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()));
}
return true;
}
if (is_writable($baseDir) && move_uploaded_file($src, $dest))
{
// Short circuit to prevent file permission errors
if (Path::setPermissions($dest))
{
return true;
}
throw new FilesystemException(__METHOD__ . ': Failed to change file permissions.');
}
throw new FilesystemException(__METHOD__ . ': Failed to move file.');
}
}
home/wuectly/www/libraries/vendor/paragonie/sodium_compat/src/File.php 0000604 00000147501 15245670444 0022273 0 ustar 00 <?php
if (class_exists('ParagonIE_Sodium_File', false)) {
return;
}
/**
* Class ParagonIE_Sodium_File
*/
class ParagonIE_Sodium_File extends ParagonIE_Sodium_Core_Util
{
/* PHP's default buffer size is 8192 for fread()/fwrite(). */
const BUFFER_SIZE = 8192;
/**
* Box a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box(), but produces
* the same result.
*
* @param string $inputFile Absolute path to a file on the filesystem
* @param string $outputFile Absolute path to a file on the filesystem
* @param string $nonce Number to be used only once
* @param string $keyPair ECDH secret key and ECDH public key concatenated
*
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function box($inputFile, $outputFile, $nonce, $keyPair)
{
/* Type checks: */
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
}
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
}
if (!is_string($nonce)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
}
/* Input validation: */
if (!is_string($keyPair)) {
throw new TypeError('Argument 4 must be a string, ' . gettype($keyPair) . ' given.');
}
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_BOX_NONCEBYTES bytes');
}
if (self::strlen($keyPair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
}
/** @var int $size */
$size = filesize($inputFile);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
fclose($ifp);
throw new SodiumException('Could not open output file for writing');
}
$res = self::box_encrypt($ifp, $ofp, $size, $nonce, $keyPair);
fclose($ifp);
fclose($ofp);
return $res;
}
/**
* Open a boxed file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box_open(), but produces
* the same result.
*
* Warning: Does not protect against TOCTOU attacks. You should
* just load the file into memory and use crypto_box_open() if
* you are worried about those.
*
* @param string $inputFile
* @param string $outputFile
* @param string $nonce
* @param string $keypair
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function box_open($inputFile, $outputFile, $nonce, $keypair)
{
/* Type checks: */
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
}
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
}
if (!is_string($nonce)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
}
if (!is_string($keypair)) {
throw new TypeError('Argument 4 must be a string, ' . gettype($keypair) . ' given.');
}
/* Input validation: */
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_BOX_NONCEBYTES bytes');
}
if (self::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
}
/** @var int $size */
$size = filesize($inputFile);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
fclose($ifp);
throw new SodiumException('Could not open output file for writing');
}
$res = self::box_decrypt($ifp, $ofp, $size, $nonce, $keypair);
fclose($ifp);
fclose($ofp);
try {
ParagonIE_Sodium_Compat::memzero($nonce);
ParagonIE_Sodium_Compat::memzero($ephKeypair);
} catch (SodiumException $ex) {
if (isset($ephKeypair)) {
unset($ephKeypair);
}
}
return $res;
}
/**
* Seal a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box_seal(), but produces
* the same result.
*
* @param string $inputFile Absolute path to a file on the filesystem
* @param string $outputFile Absolute path to a file on the filesystem
* @param string $publicKey ECDH public key
*
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function box_seal($inputFile, $outputFile, $publicKey)
{
/* Type checks: */
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
}
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
}
if (!is_string($publicKey)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
}
/* Input validation: */
if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES bytes');
}
/** @var int $size */
$size = filesize($inputFile);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
fclose($ifp);
throw new SodiumException('Could not open output file for writing');
}
/** @var string $ephKeypair */
$ephKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair();
/** @var string $msgKeypair */
$msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
ParagonIE_Sodium_Compat::crypto_box_secretkey($ephKeypair),
$publicKey
);
/** @var string $ephemeralPK */
$ephemeralPK = ParagonIE_Sodium_Compat::crypto_box_publickey($ephKeypair);
/** @var string $nonce */
$nonce = ParagonIE_Sodium_Compat::crypto_generichash(
$ephemeralPK . $publicKey,
'',
24
);
/** @var int $firstWrite */
$firstWrite = fwrite(
$ofp,
$ephemeralPK,
ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES
);
if (!is_int($firstWrite)) {
fclose($ifp);
fclose($ofp);
ParagonIE_Sodium_Compat::memzero($ephKeypair);
throw new SodiumException('Could not write to output file');
}
if ($firstWrite !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
ParagonIE_Sodium_Compat::memzero($ephKeypair);
fclose($ifp);
fclose($ofp);
throw new SodiumException('Error writing public key to output file');
}
$res = self::box_encrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
fclose($ifp);
fclose($ofp);
try {
ParagonIE_Sodium_Compat::memzero($nonce);
ParagonIE_Sodium_Compat::memzero($ephKeypair);
} catch (SodiumException $ex) {
/** @psalm-suppress PossiblyUndefinedVariable */
unset($ephKeypair);
}
return $res;
}
/**
* Open a sealed file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_box_seal_open(), but produces
* the same result.
*
* Warning: Does not protect against TOCTOU attacks. You should
* just load the file into memory and use crypto_box_seal_open() if
* you are worried about those.
*
* @param string $inputFile
* @param string $outputFile
* @param string $ecdhKeypair
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair)
{
/* Type checks: */
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
}
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
}
if (!is_string($ecdhKeypair)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($ecdhKeypair) . ' given.');
}
/* Input validation: */
if (self::strlen($ecdhKeypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
}
$publicKey = ParagonIE_Sodium_Compat::crypto_box_publickey($ecdhKeypair);
/** @var int $size */
$size = filesize($inputFile);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
fclose($ifp);
throw new SodiumException('Could not open output file for writing');
}
$ephemeralPK = fread($ifp, ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES);
if (!is_string($ephemeralPK)) {
throw new SodiumException('Could not read input file');
}
if (self::strlen($ephemeralPK) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
fclose($ifp);
fclose($ofp);
throw new SodiumException('Could not read public key from sealed file');
}
$nonce = ParagonIE_Sodium_Compat::crypto_generichash(
$ephemeralPK . $publicKey,
'',
24
);
$msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
ParagonIE_Sodium_Compat::crypto_box_secretkey($ecdhKeypair),
$ephemeralPK
);
$res = self::box_decrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
fclose($ifp);
fclose($ofp);
try {
ParagonIE_Sodium_Compat::memzero($nonce);
ParagonIE_Sodium_Compat::memzero($ephKeypair);
} catch (SodiumException $ex) {
if (isset($ephKeypair)) {
unset($ephKeypair);
}
}
return $res;
}
/**
* Calculate the BLAKE2b hash of a file.
*
* @param string $filePath Absolute path to a file on the filesystem
* @param string|null $key BLAKE2b key
* @param int $outputLength Length of hash output
*
* @return string BLAKE2b hash
* @throws SodiumException
* @throws TypeError
* @psalm-suppress FailedTypeResolution
*/
public static function generichash($filePath, $key = '', $outputLength = 32)
{
/* Type checks: */
if (!is_string($filePath)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
}
if (!is_string($key)) {
if (is_null($key)) {
$key = '';
} else {
throw new TypeError('Argument 2 must be a string, ' . gettype($key) . ' given.');
}
}
if (!is_int($outputLength)) {
if (!is_numeric($outputLength)) {
throw new TypeError('Argument 3 must be an integer, ' . gettype($outputLength) . ' given.');
}
$outputLength = (int) $outputLength;
}
/* Input validation: */
if (!empty($key)) {
if (self::strlen($key) < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
throw new TypeError('Argument 2 must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes');
}
if (self::strlen($key) > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
throw new TypeError('Argument 2 must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes');
}
}
if ($outputLength < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN) {
throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MIN');
}
if ($outputLength > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX) {
throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MAX');
}
/** @var int $size */
$size = filesize($filePath);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $fp */
$fp = fopen($filePath, 'rb');
if (!is_resource($fp)) {
throw new SodiumException('Could not open input file for reading');
}
$ctx = ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outputLength);
while ($size > 0) {
$blockSize = $size > 64
? 64
: $size;
$read = fread($fp, $blockSize);
if (!is_string($read)) {
throw new SodiumException('Could not read input file');
}
ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $read);
$size -= $blockSize;
}
fclose($fp);
return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
}
/**
* Encrypt a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_secretbox(), but produces
* the same result.
*
* @param string $inputFile Absolute path to a file on the filesystem
* @param string $outputFile Absolute path to a file on the filesystem
* @param string $nonce Number to be used only once
* @param string $key Encryption key
*
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function secretbox($inputFile, $outputFile, $nonce, $key)
{
/* Type checks: */
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..');
}
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
}
if (!is_string($nonce)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
}
/* Input validation: */
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
}
if (!is_string($key)) {
throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
}
if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_KEYBYTES bytes');
}
/** @var int $size */
$size = filesize($inputFile);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
fclose($ifp);
throw new SodiumException('Could not open output file for writing');
}
$res = self::secretbox_encrypt($ifp, $ofp, $size, $nonce, $key);
fclose($ifp);
fclose($ofp);
return $res;
}
/**
* Seal a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_secretbox_open(), but produces
* the same result.
*
* Warning: Does not protect against TOCTOU attacks. You should
* just load the file into memory and use crypto_secretbox_open() if
* you are worried about those.
*
* @param string $inputFile
* @param string $outputFile
* @param string $nonce
* @param string $key
* @return bool
* @throws SodiumException
* @throws TypeError
*/
public static function secretbox_open($inputFile, $outputFile, $nonce, $key)
{
/* Type checks: */
if (!is_string($inputFile)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
}
if (!is_string($outputFile)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
}
if (!is_string($nonce)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
}
if (!is_string($key)) {
throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
}
/* Input validation: */
if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
}
if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
throw new TypeError('Argument 4 must be CRYPTO_SECRETBOXBOX_KEYBYTES bytes');
}
/** @var int $size */
$size = filesize($inputFile);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $ifp */
$ifp = fopen($inputFile, 'rb');
if (!is_resource($ifp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var resource $ofp */
$ofp = fopen($outputFile, 'wb');
if (!is_resource($ofp)) {
fclose($ifp);
throw new SodiumException('Could not open output file for writing');
}
$res = self::secretbox_decrypt($ifp, $ofp, $size, $nonce, $key);
fclose($ifp);
fclose($ofp);
try {
ParagonIE_Sodium_Compat::memzero($key);
} catch (SodiumException $ex) {
/** @psalm-suppress PossiblyUndefinedVariable */
unset($key);
}
return $res;
}
/**
* Sign a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
* the same result.
*
* @param string $filePath Absolute path to a file on the filesystem
* @param string $secretKey Secret signing key
*
* @return string Ed25519 signature
* @throws SodiumException
* @throws TypeError
*/
public static function sign($filePath, $secretKey)
{
/* Type checks: */
if (!is_string($filePath)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
}
if (!is_string($secretKey)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($secretKey) . ' given.');
}
/* Input validation: */
if (self::strlen($secretKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES) {
throw new TypeError('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES bytes');
}
if (PHP_INT_SIZE === 4) {
return self::sign_core32($filePath, $secretKey);
}
/** @var int $size */
$size = filesize($filePath);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $fp */
$fp = fopen($filePath, 'rb');
if (!is_resource($fp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var string $az */
$az = hash('sha512', self::substr($secretKey, 0, 32), true);
$az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
$az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($az, 32, 32));
/** @var resource $hs */
$hs = self::updateHashWithFile($hs, $fp, $size);
/** @var string $nonceHash */
$nonceHash = hash_final($hs, true);
/** @var string $pk */
$pk = self::substr($secretKey, 32, 32);
/** @var string $nonce */
$nonce = ParagonIE_Sodium_Core_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
/** @var string $sig */
$sig = ParagonIE_Sodium_Core_Ed25519::ge_p3_tobytes(
ParagonIE_Sodium_Core_Ed25519::ge_scalarmult_base($nonce)
);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($sig, 0, 32));
self::hash_update($hs, self::substr($pk, 0, 32));
/** @var resource $hs */
$hs = self::updateHashWithFile($hs, $fp, $size);
/** @var string $hramHash */
$hramHash = hash_final($hs, true);
/** @var string $hram */
$hram = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hramHash);
/** @var string $sigAfter */
$sigAfter = ParagonIE_Sodium_Core_Ed25519::sc_muladd($hram, $az, $nonce);
/** @var string $sig */
$sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
try {
ParagonIE_Sodium_Compat::memzero($az);
} catch (SodiumException $ex) {
$az = null;
}
fclose($fp);
return $sig;
}
/**
* Verify a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
* produces the same result.
*
* @param string $sig Ed25519 signature
* @param string $filePath Absolute path to a file on the filesystem
* @param string $publicKey Signing public key
*
* @return bool
* @throws SodiumException
* @throws TypeError
* @throws Exception
*/
public static function verify($sig, $filePath, $publicKey)
{
/* Type checks: */
if (!is_string($sig)) {
throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.');
}
if (!is_string($filePath)) {
throw new TypeError('Argument 2 must be a string, ' . gettype($filePath) . ' given.');
}
if (!is_string($publicKey)) {
throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
}
/* Input validation: */
if (self::strlen($sig) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES) {
throw new TypeError('Argument 1 must be CRYPTO_SIGN_BYTES bytes');
}
if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES) {
throw new TypeError('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES bytes');
}
if (self::strlen($sig) < 64) {
throw new SodiumException('Signature is too short');
}
if (PHP_INT_SIZE === 4) {
return self::verify_core32($sig, $filePath, $publicKey);
}
/* Security checks */
if (
(ParagonIE_Sodium_Core_Ed25519::chrToInt($sig[63]) & 240)
&&
ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))
) {
throw new SodiumException('S < L - Invalid signature');
}
if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
throw new SodiumException('Signature is on too small of an order');
}
if ((self::chrToInt($sig[63]) & 224) !== 0) {
throw new SodiumException('Invalid signature');
}
$d = 0;
for ($i = 0; $i < 32; ++$i) {
$d |= self::chrToInt($publicKey[$i]);
}
if ($d === 0) {
throw new SodiumException('All zero public key');
}
/** @var int $size */
$size = filesize($filePath);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var resource $fp */
$fp = fopen($filePath, 'rb');
if (!is_resource($fp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
$orig = ParagonIE_Sodium_Compat::$fastMult;
// Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
ParagonIE_Sodium_Compat::$fastMult = true;
/** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
$A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($sig, 0, 32));
self::hash_update($hs, self::substr($publicKey, 0, 32));
/** @var resource $hs */
$hs = self::updateHashWithFile($hs, $fp, $size);
/** @var string $hDigest */
$hDigest = hash_final($hs, true);
/** @var string $h */
$h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
/** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
$R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
$h,
$A,
self::substr($sig, 32)
);
/** @var string $rcheck */
$rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);
// Close the file handle
fclose($fp);
// Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
ParagonIE_Sodium_Compat::$fastMult = $orig;
return self::verify_32($rcheck, self::substr($sig, 0, 32));
}
/**
* @param resource $ifp
* @param resource $ofp
* @param int $mlen
* @param string $nonce
* @param string $boxKeypair
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
{
if (PHP_INT_SIZE === 4) {
return self::secretbox_encrypt(
$ifp,
$ofp,
$mlen,
$nonce,
ParagonIE_Sodium_Crypto32::box_beforenm(
ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
)
);
}
return self::secretbox_encrypt(
$ifp,
$ofp,
$mlen,
$nonce,
ParagonIE_Sodium_Crypto::box_beforenm(
ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
)
);
}
/**
* @param resource $ifp
* @param resource $ofp
* @param int $mlen
* @param string $nonce
* @param string $boxKeypair
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
{
if (PHP_INT_SIZE === 4) {
return self::secretbox_decrypt(
$ifp,
$ofp,
$mlen,
$nonce,
ParagonIE_Sodium_Crypto32::box_beforenm(
ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
)
);
}
return self::secretbox_decrypt(
$ifp,
$ofp,
$mlen,
$nonce,
ParagonIE_Sodium_Crypto::box_beforenm(
ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
)
);
}
/**
* Encrypt a file
*
* @param resource $ifp
* @param resource $ofp
* @param int $mlen
* @param string $nonce
* @param string $key
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
{
if (PHP_INT_SIZE === 4) {
return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
}
$plaintext = fread($ifp, 32);
if (!is_string($plaintext)) {
throw new SodiumException('Could not read input file');
}
$first32 = self::ftell($ifp);
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
/** @var string $realNonce */
$realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
/** @var string $block0 */
$block0 = str_repeat("\x00", 32);
/** @var int $mlen - Length of the plaintext message */
$mlen0 = $mlen;
if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
$mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
}
$block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
$block0,
$realNonce,
$subkey
);
$state = new ParagonIE_Sodium_Core_Poly1305_State(
ParagonIE_Sodium_Core_Util::substr(
$block0,
0,
ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
)
);
// Pre-write 16 blank bytes for the Poly1305 tag
$start = self::ftell($ofp);
fwrite($ofp, str_repeat("\x00", 16));
/** @var string $c */
$cBlock = ParagonIE_Sodium_Core_Util::substr(
$block0,
ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
);
$state->update($cBlock);
fwrite($ofp, $cBlock);
$mlen -= 32;
/** @var int $iter */
$iter = 1;
/** @var int $incr */
$incr = self::BUFFER_SIZE >> 6;
/*
* Set the cursor to the end of the first half-block. All future bytes will
* generated from salsa20_xor_ic, starting from 1 (second block).
*/
fseek($ifp, $first32, SEEK_SET);
while ($mlen > 0) {
$blockSize = $mlen > self::BUFFER_SIZE
? self::BUFFER_SIZE
: $mlen;
$plaintext = fread($ifp, $blockSize);
if (!is_string($plaintext)) {
throw new SodiumException('Could not read input file');
}
$cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
$plaintext,
$realNonce,
$iter,
$subkey
);
fwrite($ofp, $cBlock, $blockSize);
$state->update($cBlock);
$mlen -= $blockSize;
$iter += $incr;
}
try {
ParagonIE_Sodium_Compat::memzero($block0);
ParagonIE_Sodium_Compat::memzero($subkey);
} catch (SodiumException $ex) {
$block0 = null;
$subkey = null;
}
$end = self::ftell($ofp);
/*
* Write the Poly1305 authentication tag that provides integrity
* over the ciphertext (encrypt-then-MAC)
*/
fseek($ofp, $start, SEEK_SET);
fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
fseek($ofp, $end, SEEK_SET);
unset($state);
return true;
}
/**
* Decrypt a file
*
* @param resource $ifp
* @param resource $ofp
* @param int $mlen
* @param string $nonce
* @param string $key
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
{
if (PHP_INT_SIZE === 4) {
return self::secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
}
$tag = fread($ifp, 16);
if (!is_string($tag)) {
throw new SodiumException('Could not read input file');
}
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);
/** @var string $realNonce */
$realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
64,
ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
$subkey
);
/* Verify the Poly1305 MAC -before- attempting to decrypt! */
$state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
throw new SodiumException('Invalid MAC');
}
/*
* Set the cursor to the end of the first half-block. All future bytes will
* generated from salsa20_xor_ic, starting from 1 (second block).
*/
$first32 = fread($ifp, 32);
if (!is_string($first32)) {
throw new SodiumException('Could not read input file');
}
$first32len = self::strlen($first32);
fwrite(
$ofp,
self::xorStrings(
self::substr($block0, 32, $first32len),
self::substr($first32, 0, $first32len)
)
);
$mlen -= 32;
/** @var int $iter */
$iter = 1;
/** @var int $incr */
$incr = self::BUFFER_SIZE >> 6;
/* Decrypts ciphertext, writes to output file. */
while ($mlen > 0) {
$blockSize = $mlen > self::BUFFER_SIZE
? self::BUFFER_SIZE
: $mlen;
$ciphertext = fread($ifp, $blockSize);
if (!is_string($ciphertext)) {
throw new SodiumException('Could not read input file');
}
$pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
$ciphertext,
$realNonce,
$iter,
$subkey
);
fwrite($ofp, $pBlock, $blockSize);
$mlen -= $blockSize;
$iter += $incr;
}
return true;
}
/**
* @param ParagonIE_Sodium_Core_Poly1305_State $state
* @param resource $ifp
* @param string $tag
* @param int $mlen
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function onetimeauth_verify(
ParagonIE_Sodium_Core_Poly1305_State $state,
$ifp,
$tag = '',
$mlen = 0
) {
/** @var int $pos */
$pos = self::ftell($ifp);
/** @var int $iter */
$iter = 1;
/** @var int $incr */
$incr = self::BUFFER_SIZE >> 6;
while ($mlen > 0) {
$blockSize = $mlen > self::BUFFER_SIZE
? self::BUFFER_SIZE
: $mlen;
$ciphertext = fread($ifp, $blockSize);
if (!is_string($ciphertext)) {
throw new SodiumException('Could not read input file');
}
$state->update($ciphertext);
$mlen -= $blockSize;
$iter += $incr;
}
$res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());
fseek($ifp, $pos, SEEK_SET);
return $res;
}
/**
* Update a hash context with the contents of a file, without
* loading the entire file into memory.
*
* @param resource|HashContext $hash
* @param resource $fp
* @param int $size
* @return resource|object Resource on PHP < 7.2, HashContext object on PHP >= 7.2
* @throws SodiumException
* @throws TypeError
* @psalm-suppress PossiblyInvalidArgument
* PHP 7.2 changes from a resource to an object,
* which causes Psalm to complain about an error.
* @psalm-suppress TypeCoercion
* Ditto.
*/
public static function updateHashWithFile($hash, $fp, $size = 0)
{
/* Type checks: */
if (PHP_VERSION_ID < 70200) {
if (!is_resource($hash)) {
throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
}
} else {
if (!is_object($hash)) {
throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
}
}
if (!is_resource($fp)) {
throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
}
if (!is_int($size)) {
throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
}
/** @var int $originalPosition */
$originalPosition = self::ftell($fp);
// Move file pointer to beginning of file
fseek($fp, 0, SEEK_SET);
for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
/** @var string|bool $message */
$message = fread(
$fp,
($size - $i) > self::BUFFER_SIZE
? $size - $i
: self::BUFFER_SIZE
);
if (!is_string($message)) {
throw new SodiumException('Unexpected error reading from file.');
}
/** @var string $message */
/** @psalm-suppress InvalidArgument */
self::hash_update($hash, $message);
}
// Reset file pointer's position
fseek($fp, $originalPosition, SEEK_SET);
return $hash;
}
/**
* Sign a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
* the same result. (32-bit)
*
* @param string $filePath Absolute path to a file on the filesystem
* @param string $secretKey Secret signing key
*
* @return string Ed25519 signature
* @throws SodiumException
* @throws TypeError
*/
private static function sign_core32($filePath, $secretKey)
{
$size = filesize($filePath);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
$fp = fopen($filePath, 'rb');
if (!is_resource($fp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var string $az */
$az = hash('sha512', self::substr($secretKey, 0, 32), true);
$az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
$az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($az, 32, 32));
/** @var resource $hs */
$hs = self::updateHashWithFile($hs, $fp, $size);
$nonceHash = hash_final($hs, true);
$pk = self::substr($secretKey, 32, 32);
$nonce = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
$sig = ParagonIE_Sodium_Core32_Ed25519::ge_p3_tobytes(
ParagonIE_Sodium_Core32_Ed25519::ge_scalarmult_base($nonce)
);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($sig, 0, 32));
self::hash_update($hs, self::substr($pk, 0, 32));
/** @var resource $hs */
$hs = self::updateHashWithFile($hs, $fp, $size);
$hramHash = hash_final($hs, true);
$hram = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hramHash);
$sigAfter = ParagonIE_Sodium_Core32_Ed25519::sc_muladd($hram, $az, $nonce);
/** @var string $sig */
$sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);
try {
ParagonIE_Sodium_Compat::memzero($az);
} catch (SodiumException $ex) {
$az = null;
}
fclose($fp);
return $sig;
}
/**
*
* Verify a file (rather than a string). Uses less memory than
* ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
* produces the same result. (32-bit)
*
* @param string $sig Ed25519 signature
* @param string $filePath Absolute path to a file on the filesystem
* @param string $publicKey Signing public key
*
* @return bool
* @throws SodiumException
* @throws Exception
*/
public static function verify_core32($sig, $filePath, $publicKey)
{
/* Security checks */
if (ParagonIE_Sodium_Core32_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
throw new SodiumException('S < L - Invalid signature');
}
if (ParagonIE_Sodium_Core32_Ed25519::small_order($sig)) {
throw new SodiumException('Signature is on too small of an order');
}
if ((self::chrToInt($sig[63]) & 224) !== 0) {
throw new SodiumException('Invalid signature');
}
$d = 0;
for ($i = 0; $i < 32; ++$i) {
$d |= self::chrToInt($publicKey[$i]);
}
if ($d === 0) {
throw new SodiumException('All zero public key');
}
/** @var int|bool $size */
$size = filesize($filePath);
if (!is_int($size)) {
throw new SodiumException('Could not obtain the file size');
}
/** @var int $size */
/** @var resource|bool $fp */
$fp = fopen($filePath, 'rb');
if (!is_resource($fp)) {
throw new SodiumException('Could not open input file for reading');
}
/** @var resource $fp */
/** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
$orig = ParagonIE_Sodium_Compat::$fastMult;
// Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
ParagonIE_Sodium_Compat::$fastMult = true;
/** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
$A = ParagonIE_Sodium_Core32_Ed25519::ge_frombytes_negate_vartime($publicKey);
$hs = hash_init('sha512');
self::hash_update($hs, self::substr($sig, 0, 32));
self::hash_update($hs, self::substr($publicKey, 0, 32));
/** @var resource $hs */
$hs = self::updateHashWithFile($hs, $fp, $size);
/** @var string $hDigest */
$hDigest = hash_final($hs, true);
/** @var string $h */
$h = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);
/** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
$R = ParagonIE_Sodium_Core32_Ed25519::ge_double_scalarmult_vartime(
$h,
$A,
self::substr($sig, 32)
);
/** @var string $rcheck */
$rcheck = ParagonIE_Sodium_Core32_Ed25519::ge_tobytes($R);
// Close the file handle
fclose($fp);
// Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
ParagonIE_Sodium_Compat::$fastMult = $orig;
return self::verify_32($rcheck, self::substr($sig, 0, 32));
}
/**
* Encrypt a file (32-bit)
*
* @param resource $ifp
* @param resource $ofp
* @param int $mlen
* @param string $nonce
* @param string $key
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
{
$plaintext = fread($ifp, 32);
if (!is_string($plaintext)) {
throw new SodiumException('Could not read input file');
}
$first32 = self::ftell($ifp);
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
/** @var string $realNonce */
$realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
/** @var string $block0 */
$block0 = str_repeat("\x00", 32);
/** @var int $mlen - Length of the plaintext message */
$mlen0 = $mlen;
if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
$mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
}
$block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
$block0,
$realNonce,
$subkey
);
$state = new ParagonIE_Sodium_Core32_Poly1305_State(
ParagonIE_Sodium_Core32_Util::substr(
$block0,
0,
ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
)
);
// Pre-write 16 blank bytes for the Poly1305 tag
$start = self::ftell($ofp);
fwrite($ofp, str_repeat("\x00", 16));
/** @var string $c */
$cBlock = ParagonIE_Sodium_Core32_Util::substr(
$block0,
ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
);
$state->update($cBlock);
fwrite($ofp, $cBlock);
$mlen -= 32;
/** @var int $iter */
$iter = 1;
/** @var int $incr */
$incr = self::BUFFER_SIZE >> 6;
/*
* Set the cursor to the end of the first half-block. All future bytes will
* generated from salsa20_xor_ic, starting from 1 (second block).
*/
fseek($ifp, $first32, SEEK_SET);
while ($mlen > 0) {
$blockSize = $mlen > self::BUFFER_SIZE
? self::BUFFER_SIZE
: $mlen;
$plaintext = fread($ifp, $blockSize);
if (!is_string($plaintext)) {
throw new SodiumException('Could not read input file');
}
$cBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
$plaintext,
$realNonce,
$iter,
$subkey
);
fwrite($ofp, $cBlock, $blockSize);
$state->update($cBlock);
$mlen -= $blockSize;
$iter += $incr;
}
try {
ParagonIE_Sodium_Compat::memzero($block0);
ParagonIE_Sodium_Compat::memzero($subkey);
} catch (SodiumException $ex) {
$block0 = null;
$subkey = null;
}
$end = self::ftell($ofp);
/*
* Write the Poly1305 authentication tag that provides integrity
* over the ciphertext (encrypt-then-MAC)
*/
fseek($ofp, $start, SEEK_SET);
fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
fseek($ofp, $end, SEEK_SET);
unset($state);
return true;
}
/**
* Decrypt a file (32-bit)
*
* @param resource $ifp
* @param resource $ofp
* @param int $mlen
* @param string $nonce
* @param string $key
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
{
$tag = fread($ifp, 16);
if (!is_string($tag)) {
throw new SodiumException('Could not read input file');
}
/** @var string $subkey */
$subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);
/** @var string $realNonce */
$realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);
/** @var string $block0 */
$block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
64,
ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
$subkey
);
/* Verify the Poly1305 MAC -before- attempting to decrypt! */
$state = new ParagonIE_Sodium_Core32_Poly1305_State(self::substr($block0, 0, 32));
if (!self::onetimeauth_verify_core32($state, $ifp, $tag, $mlen)) {
throw new SodiumException('Invalid MAC');
}
/*
* Set the cursor to the end of the first half-block. All future bytes will
* generated from salsa20_xor_ic, starting from 1 (second block).
*/
$first32 = fread($ifp, 32);
if (!is_string($first32)) {
throw new SodiumException('Could not read input file');
}
$first32len = self::strlen($first32);
fwrite(
$ofp,
self::xorStrings(
self::substr($block0, 32, $first32len),
self::substr($first32, 0, $first32len)
)
);
$mlen -= 32;
/** @var int $iter */
$iter = 1;
/** @var int $incr */
$incr = self::BUFFER_SIZE >> 6;
/* Decrypts ciphertext, writes to output file. */
while ($mlen > 0) {
$blockSize = $mlen > self::BUFFER_SIZE
? self::BUFFER_SIZE
: $mlen;
$ciphertext = fread($ifp, $blockSize);
if (!is_string($ciphertext)) {
throw new SodiumException('Could not read input file');
}
$pBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
$ciphertext,
$realNonce,
$iter,
$subkey
);
fwrite($ofp, $pBlock, $blockSize);
$mlen -= $blockSize;
$iter += $incr;
}
return true;
}
/**
* One-time message authentication for 32-bit systems
*
* @param ParagonIE_Sodium_Core32_Poly1305_State $state
* @param resource $ifp
* @param string $tag
* @param int $mlen
* @return bool
* @throws SodiumException
* @throws TypeError
*/
protected static function onetimeauth_verify_core32(
ParagonIE_Sodium_Core32_Poly1305_State $state,
$ifp,
$tag = '',
$mlen = 0
) {
/** @var int $pos */
$pos = self::ftell($ifp);
while ($mlen > 0) {
$blockSize = $mlen > self::BUFFER_SIZE
? self::BUFFER_SIZE
: $mlen;
$ciphertext = fread($ifp, $blockSize);
if (!is_string($ciphertext)) {
throw new SodiumException('Could not read input file');
}
$state->update($ciphertext);
$mlen -= $blockSize;
}
$res = ParagonIE_Sodium_Core32_Util::verify_16($tag, $state->finish());
fseek($ifp, $pos, SEEK_SET);
return $res;
}
/**
* @param resource $resource
* @return int
* @throws SodiumException
*/
private static function ftell($resource)
{
$return = ftell($resource);
if (!is_int($return)) {
throw new SodiumException('ftell() returned false');
}
return (int) $return;
}
}