HEX
Server: Apache/2.4.41 (Ubuntu)
System: Linux vm8 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64
User: afleverb (1000)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: //var/www/drakkar_site/wp-content/plugins/woocommerce-mercadopago/src/IO/Downloader.php
<?php

namespace MercadoPago\Woocommerce\IO;

use Exception;
use MercadoPago\Woocommerce\Entities\Files\Log as LogFile;
use MercadoPago\Woocommerce\Libraries\Logs\Logs;
use MercadoPago\Woocommerce\Helpers\Form;
use MercadoPago\Woocommerce\Helpers\CurrentUser;
use ZipArchive;

if (!defined('ABSPATH')) {
    exit;
}

class Downloader
{
    private Logs $logs;

    public array $pluginLogs;

    private CurrentUser $currentUser;

    public function __construct(Logs $logs, CurrentUser $currentUser)
    {
        $this->currentUser  = $currentUser;
        $this->logs     = $logs;
        $this->pluginLogs = $this->getNameOfFileLogs();
    }


    /**
     * Get log files names order by date
     *
     * @return array of logs from plugin
     */
    private function getNameOfFileLogs(): array
    {
        $selectedLogFiles = array();
        try {
            $logDirectory = WP_CONTENT_DIR . '/uploads/wc-logs/';
            if (file_exists($logDirectory)) {
                $logFiles = scandir($logDirectory);

                foreach ($logFiles as $file) {
                    if ($file == '.' || $file == '..') {
                        continue;
                    }

                    if (strpos($file, 'mercadopago') !== false || strpos($file, 'MercadoPago') !== false || strpos($file, 'fatal-errors') !== false) {
                        preg_match('/^(.*?)-(\d{4}-\d{2}-\d{2})/', $file, $matches);
                        $logFile = new LogFile();
                        $logFile->fileFullName = $file;
                        $logFile->fileName = $matches[1];
                        $logFile->fileDate = $matches[2];
                        $selectedLogFiles[] = $logFile;
                    }
                }
            }

            usort($selectedLogFiles, function ($a, $b) {
                return strtotime($b->fileDate) - strtotime($a->fileDate);
            });
        } catch (Exception $e) {
            $this->logs->file->warning(
                "Mercado pago gave error to get log files names: {$e->getMessage()}",
                __CLASS__
            );
        }
        return $selectedLogFiles;
    }

    /**
     * Handles log downloads.
     *
     * @return void
     * @throws Exception
     */
    public function downloadLog(): void
    {
        if (isset($_GET['files'])) {
            $selectedFiles = Form::sanitizedGetData('files');
            $numFiles = count($selectedFiles);
            if ($numFiles === 1) {
                $this->singleFileDownload($selectedFiles);
            } elseif ($numFiles > 1) {
                $this->multipleFileDownload($selectedFiles);
            }
        }
    }

    /**
     * downloads a single file
     *
     * @param array $selectedFile
     *
     * @return void
     * @throws Exception
     */
    private function singleFileDownload(array $selectedFile): void
    {
        $filename = reset($selectedFile);

        if (!$this->validatesDownloadSecurity($filename)) {
            throw new Exception('attempt to download the file ' . $filename . 'on ' .  __METHOD__);
        }

        $file_path = WP_CONTENT_DIR . '/uploads/wc-logs/' . $filename;

        if (file_exists($file_path) && is_readable($file_path)) {
            header('Content-Disposition: attachment; filename="' . $filename . '"');
            header('Content-Type: application/octet-stream');
            header('Content-Length: ' . filesize($file_path));
            readfile($file_path);
            exit;
        } else {
            throw new Exception('error to download log file ' . __METHOD__);
        }
    }

    /**
     * downloads multiple files
     *
     * @param array $selectedFiles
     *
     * @return void
     * @throws Exception
     */
    private function multipleFileDownload(array $selectedFiles): void
    {
        $zip = new ZipArchive();
        $temp_file = tempnam(sys_get_temp_dir(), 'logs_');

        if ($zip->open($temp_file, ZipArchive::CREATE) === true) {
            foreach ($selectedFiles as $filename) {
                if (!$this->validatesDownloadSecurity($filename)) {
                    continue;
                }

                $file_path = WP_CONTENT_DIR . '/uploads/wc-logs/' . $filename;

                if (file_exists($file_path) && is_readable($file_path)) {
                    $zip->addFile($file_path, $filename);
                }
            }
            $zip->close();

            header('Content-Disposition: attachment; filename="mercado-pago-logs.zip"');
            header('Content-Type: application/zip');
            header('Content-Length: ' . filesize($temp_file));
            readfile($temp_file);
            unlink($temp_file);
            exit;
        } else {
            throw new Exception('error to download log files ' . __METHOD__);
        }
    }

    /**
     * Validates a filename and user permissions to prevent path traversal attempts and ensure expected format.
     *
     * @param string $filename The filename to be validated
     *
     * @return bool True if the filename is valid, false otherwise
     */
    private function validatesDownloadSecurity(string $filename): bool
    {
        $this->currentUser->validateUserNeededPermissions();

        return $this->hasAllowedExtension($filename) &&
            $this->hasNoDisallowedCharacters($filename) &&
            $this->containsExpectedTerms($filename);
    }

    private function hasAllowedExtension(string $filename): bool
    {
        $allowed_pattern = '/\.log$/';
        return  (bool)preg_match($allowed_pattern, $filename);
    }

    private function hasNoDisallowedCharacters(string $filename): bool
    {
        $disallowed = array('..', '/', '\\', '.php', '.ini', '.exe', '.bat', '.sh', '.js', '.py', '.pl', '.sql', '.mdb', '.sqlite', '.zip', '.tar', '.gz', '.htaccess');
        return empty(array_intersect($disallowed, array($filename)));
    }

    private function containsExpectedTerms(string $filename): bool
    {
        $allowed_pattern = '/mercadopago|MercadoPago|fatal-errors/';
        return (bool)preg_match($allowed_pattern, $filename);
    }
}