shell bypass 403
<?php
/**
* @package akeebabackup
* @copyright Copyright (c)2006-2024 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
/**
* @package Akeeba\Component\AkeebaBackup\Administrator\Helper
* @subpackage
*
* @copyright A copyright
* @license A "Slug" license name e.g. GPL2
*/
namespace Akeeba\Component\AkeebaBackup\Administrator\Helper;
use Akeeba\Engine\Factory;
use Joomla\CMS\Factory as JoomlaFactory;
use Joomla\Database\DatabaseDriver;
use Joomla\Database\DatabaseInterface;
defined('_JEXEC') || die;
/**
* Workarounds for Joomla 5.0+ custom public folder
*
* @since 9.8.1
*/
class JoomlaPublicFolder
{
/**
* Key in the `#__akeeba_common` table to use for the public folder storage.
*
* @since 9.8.1
*/
private const KEY = 'JPATH_PUBLIC';
/**
* Internal cache of the public folder location
*
* @var string|null
* @since 9.8.1
*/
private static ?string $publicPath = null;
private static bool $hasCustomPublic = false;
public static function init(): void
{
$app = JoomlaFactory::getApplication();
if ($app->isClient('site') || $app->isClient('administrator') || $app->isClient('api'))
{
self::$hasCustomPublic = defined('JPATH_PUBLIC') && JPATH_PUBLIC !== JPATH_ROOT;
}
else
{
$public = self::getPublicFolder();
self::$hasCustomPublic = $public !== JPATH_ROOT;
}
self::savePublicFolder();
self::createPublicRootSymlinks();
}
/**
* Creates the symlinks in the public directory we need for restoration to work.
*
* It creates the symlinks:
* - `installation`. Allows the restoration script to execute after the initial extraction.
* - `administrator/components/com_akeebabackup/restore.php` (Pro version). Allows the integrated restoration to
* extract the backup archive.
*
* @since 9.8.1
*/
public static function createPublicRootSymlinks(): void
{
if (!self::$hasCustomPublic)
{
return;
}
$public = self::getPublicFolder();
// Create a symlink to the installation directory, allowing the restoration to actually execute
if (!@is_link($public . '/installation'))
{
var_dump(JPATH_INSTALLATION, $public . '/installation');
@symlink(JPATH_INSTALLATION, $public . '/installation');
}
// Create a symlink to the restore.php file. Required for the extraction to work.
if (
!@file_exists($public . '/administrator/components/com_akeebabackup/restore.php')
&& file_exists(JPATH_ADMINISTRATOR . '/components/com_akeebabackup/restore.php')
)
{
@mkdir($public . '/administrator/components/com_akeebabackup', 0755, true);
@symlink(
JPATH_ADMINISTRATOR . '/components/com_akeebabackup/restore.php',
$public . '/administrator/components/com_akeebabackup/restore.php',
);
}
}
/**
* Do we have automatic inclusion of the custom public folder under Joomla 5 or later?
*
* This returns false in the following cases:
* - Joomla 4, which does not have JPATH_PUBLIC
* - Custom site root override enabled (I don't know which site it is backing up!)
* - The public root is JPATH_ROOT
*
* @return bool
*
* @since 9.8.1
*/
public static function hasCustomPublicFolderAutoIncluded(): bool
{
if (!self::$hasCustomPublic)
{
return false;
}
if (Factory::getConfiguration()->get('akeeba.platform.override_root', 0))
{
return false;
}
$publicDir = Factory::getFilesystemTools()->TranslateWinPath(JoomlaPublicFolder::getPublicFolder());
$rootDir = Factory::getFilesystemTools()->TranslateWinPath(JPATH_ROOT);
return $publicDir !== $rootDir;
}
/**
* Store JPATH_PUBLIC in the database.
*
* We observed that JPATH_PUBLIC always returns JPATH_ROOT under CLI. Therefore, we need a solid way to get the
* correct JPATH_PUBLIC even if Joomla! core developers have forgotten about the _fifth_ official Joomla!
* application...
*
* @since 9.8.1
*/
public static function savePublicFolder(): void
{
// If it's Joomla! 4 (or a bad edit in defines.php) bail out fast.
if (!self::$hasCustomPublic)
{
// We set this here to avoid doing an unnecessary database query.
self::$publicPath = JPATH_ROOT;
return;
}
// Bail out if we are under the CLI application. We can never have the correct JPATH_PUBLIC there.
if (JoomlaFactory::getApplication()->isClient('cli'))
{
// DO NOT SET self::$publicPath HERE! We want to fall back to a database query as needed.
return;
}
$currentPublic = self::getPublicFolder(true);
// Nothing to update. Bail out.
if ($currentPublic === JPATH_PUBLIC)
{
return;
}
// Update our internal variable to speed things up.
self::$publicPath = JPATH_PUBLIC;
// Remove an existing entry from the database.
/** @var DatabaseDriver $db */
$key = self::KEY;
$db = JoomlaFactory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->delete($db->quoteName('#__akeeba_common'))
->where($db->quoteName('key') . ' = :key')
->bind(':key', $key);
try
{
$db->setQuery($query)->execute();
}
catch (\Exception $e)
{
return;
}
// Save the new entry to the database.
$o = (object) [
'key' => self::KEY,
'value' => JPATH_PUBLIC,
];
try
{
$db->insertObject('#__akeeba_common', $o);
}
catch (\Exception $e)
{
return;
}
}
/**
* Returns the correct JPATH_PUBLIC folder, even under CLI.
*
* @param bool $nullIfUnset Should I return NULL if the database key is not yet set?
*
* @return string|null
*
* @since 9.8.1
*/
public static function getPublicFolder(bool $nullIfUnset = false): ?string
{
return self::$publicPath ??= call_user_func(
function () use ($nullIfUnset) {
$key = self::KEY;
try
{
/** @var DatabaseDriver $db */
$db = JoomlaFactory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true)
->select($db->quoteName('value'))
->from($db->quoteName('#__akeeba_common'))
->where($db->quoteName('key') . ' = :key')
->bind(':key', $key);
$result = $db->setQuery($query)->loadResult();
}
catch (\Exception $e)
{
$result = null;
}
return $result ?? ($nullIfUnset ? null : JPATH_ROOT);
}
);
}
}