fix: Remove orphaned root-level webservices plugin files #87
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteBackup
|
||||
* @subpackage plg_webservices_mokosuitebackup
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
* @package MokoSuiteBackup
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
-->
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Web Services - MokoSuiteBackup</name>
|
||||
<version>01.27.03</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<description>PLG_WEBSERVICES_MOKOJOOMBACKUP_DESCRIPTION</description>
|
||||
|
||||
<namespace path="src">Joomla\Plugin\WebServices\MokoSuiteBackup</namespace>
|
||||
|
||||
<files>
|
||||
<filename plugin="mokosuitebackup">mokosuitebackup.php</filename>
|
||||
<folder>services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
|
||||
<languages>
|
||||
<language tag="en-GB">language/en-GB/plg_webservices_mokosuitebackup.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_webservices_mokosuitebackup.sys.ini</language>
|
||||
</languages>
|
||||
</extension>
|
||||
@@ -19,6 +19,18 @@
|
||||
<option value="fail">COM_MOKOJOOMBACKUP_STATUS_FAIL</option>
|
||||
<option value="pending">COM_MOKOJOOMBACKUP_STATUS_PENDING</option>
|
||||
</field>
|
||||
<field
|
||||
name="backup_type"
|
||||
type="list"
|
||||
label="COM_MOKOJOOMBACKUP_FIELD_BACKUP_TYPE"
|
||||
onchange="this.form.submit();"
|
||||
>
|
||||
<option value="">COM_MOKOJOOMBACKUP_FILTER_TYPE_ALL</option>
|
||||
<option value="full">COM_MOKOJOOMBACKUP_TYPE_FULL</option>
|
||||
<option value="database">COM_MOKOJOOMBACKUP_TYPE_DATABASE</option>
|
||||
<option value="files">COM_MOKOJOOMBACKUP_TYPE_FILES</option>
|
||||
<option value="differential">COM_MOKOJOOMBACKUP_TYPE_DIFFERENTIAL</option>
|
||||
</field>
|
||||
</fields>
|
||||
|
||||
<fields name="list">
|
||||
|
||||
@@ -167,6 +167,7 @@ COM_MOKOJOOMBACKUP_STATUS_PENDING="Pending"
|
||||
COM_MOKOJOOMBACKUP_FILTER_SEARCH="Search"
|
||||
COM_MOKOJOOMBACKUP_FILTER_STATUS="Status"
|
||||
COM_MOKOJOOMBACKUP_FILTER_STATUS_ALL="- Select Status -"
|
||||
COM_MOKOJOOMBACKUP_FILTER_TYPE_ALL="- Select Type -"
|
||||
|
||||
; Tabs and fieldsets
|
||||
COM_MOKOJOOMBACKUP_TAB_GENERAL="General"
|
||||
|
||||
@@ -303,6 +303,20 @@ function actionExtract(array $data): array
|
||||
$zip->setPassword($password);
|
||||
}
|
||||
|
||||
// Validate all entries before extraction (path traversal protection)
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$entryName = $zip->getNameIndex($i);
|
||||
|
||||
if ($entryName === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains($entryName, '../') || str_contains($entryName, '..\\') || str_starts_with($entryName, '/') || str_starts_with($entryName, '\\')) {
|
||||
$zip->close();
|
||||
throw new RuntimeException('Archive contains unsafe path: ' . $entryName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$zip->extractTo(RESTORE_DIR)) {
|
||||
$zip->close();
|
||||
throw new RuntimeException(
|
||||
|
||||
@@ -191,6 +191,20 @@ class RestoreEngine
|
||||
$this->log('Decryption password set');
|
||||
}
|
||||
|
||||
// Validate all entries before extraction (path traversal protection)
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$entryName = $zip->getNameIndex($i);
|
||||
|
||||
if ($entryName === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains($entryName, '../') || str_contains($entryName, '..\\') || str_starts_with($entryName, '/') || str_starts_with($entryName, '\\')) {
|
||||
$zip->close();
|
||||
throw new \RuntimeException('Archive contains unsafe path: ' . $entryName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$zip->extractTo($this->stagingDir)) {
|
||||
$zip->close();
|
||||
|
||||
@@ -210,6 +224,18 @@ class RestoreEngine
|
||||
private function extractTarGz(string $archivePath): void
|
||||
{
|
||||
$phar = new \PharData($archivePath);
|
||||
|
||||
// Validate all entries before extraction (path traversal protection)
|
||||
foreach (new \RecursiveIteratorIterator($phar) as $entry) {
|
||||
$entryName = $entry->getPathname();
|
||||
// PharData paths are prefixed with phar:// — extract the relative part
|
||||
$relative = substr($entryName, strlen('phar://' . $archivePath) + 1);
|
||||
|
||||
if (str_contains($relative, '../') || str_contains($relative, '..\\') || str_starts_with($relative, '/') || str_starts_with($relative, '\\')) {
|
||||
throw new \RuntimeException('Archive contains unsafe path: ' . $relative);
|
||||
}
|
||||
}
|
||||
|
||||
$phar->extractTo($this->stagingDir, null, true);
|
||||
$this->log('Extracted tar.gz archive');
|
||||
}
|
||||
|
||||
@@ -61,6 +61,13 @@ class BackupsModel extends ListModel
|
||||
$query->where($db->quoteName('a.profile_id') . ' = ' . (int) $profileId);
|
||||
}
|
||||
|
||||
// Filter by backup type
|
||||
$backupType = $this->getState('filter.backup_type');
|
||||
|
||||
if (!empty($backupType)) {
|
||||
$query->where($db->quoteName('a.backup_type') . ' = ' . $db->quote($backupType));
|
||||
}
|
||||
|
||||
// Filter by search
|
||||
$search = $this->getState('filter.search');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user