Refactored size checks for attachments and check base64 upload length

This commit is contained in:
Jan Böhmer 2026-07-09 19:33:11 +02:00
parent a356b94c34
commit 76a36386fc
5 changed files with 33 additions and 68 deletions

View file

@ -74,7 +74,7 @@ class ToolsController extends AbstractController
'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'), 'detailed_error_pages' => $this->getParameter('partdb.error_pages.show_help'),
'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'), 'error_page_admin_email' => $this->getParameter('partdb.error_pages.admin_email'),
'configured_max_file_size' => $settings->system->attachments->maxFileSize, 'configured_max_file_size' => $settings->system->attachments->maxFileSize,
'effective_max_file_size' => $attachmentSubmitHandler->getMaximumAllowedUploadSize(), 'effective_max_file_size' => $attachmentSubmitHandler->getMaximumEffectiveUploadSize(),
'saml_enabled' => $this->getParameter('partdb.saml.enabled'), 'saml_enabled' => $this->getParameter('partdb.saml.enabled'),
//PHP section //PHP section

View file

@ -209,7 +209,7 @@ class AttachmentFormType extends AbstractType
public function finishView(FormView $view, FormInterface $form, array $options): void public function finishView(FormView $view, FormInterface $form, array $options): void
{ {
$view->vars['max_upload_size'] = $this->submitHandler->getMaximumAllowedUploadSize(); $view->vars['max_upload_size'] = $this->submitHandler->getMaximumEffectiveUploadSize();
} }
public function getBlockPrefix(): string public function getBlockPrefix(): string

View file

@ -218,6 +218,14 @@ class AttachmentSubmitHandler
//If no file was uploaded, but we have base64 encoded data, create a file from it //If no file was uploaded, but we have base64 encoded data, create a file from it
if (!$file && $upload->data !== null) { if (!$file && $upload->data !== null) {
if (strlen($upload->data) > $this->getMaximumUserConfiguredUploadSize() * 4 / 3) { //Base64 encoding increases the size of the data by 4/3, so we have to check for that
throw new RuntimeException(
sprintf(
'The given base64 data is too big! Maximum size is %.1f MB!',
$this->getMaximumUserConfiguredUploadSize() / 1000 / 1000
));
}
$file = new UploadedBase64EncodedFile(new Base64EncodedFile($upload->data), $upload->filename ?? 'base64'); $file = new UploadedBase64EncodedFile(new Base64EncodedFile($upload->data), $upload->filename ?? 'base64');
} }
@ -226,6 +234,15 @@ class AttachmentSubmitHandler
//When a file is given then upload it, otherwise check if we need to download the URL //When a file is given then upload it, otherwise check if we need to download the URL
if ($file instanceof UploadedFile) { if ($file instanceof UploadedFile) {
//Check the file size, to avoid uploading too big files.
//The file is not necessarily validated as it can also come from an Base64 source
if ($file->getSize() > $this->getMaximumUserConfiguredUploadSize()) {
throw new RuntimeException(
sprintf(
'The uploaded file is too big! Maximum size is %.1f MB!',
$this->getMaximumUserConfiguredUploadSize() / 1000 / 1000
));
}
$this->upload($attachment, $file, $secure_attachment); $this->upload($attachment, $file, $secure_attachment);
} elseif ($upload->downloadUrl && $attachment->hasExternal()) { } elseif ($upload->downloadUrl && $attachment->hasExternal()) {
@ -402,7 +419,7 @@ class AttachmentSubmitHandler
$fileHandler = fopen($tmp_path, 'wb'); $fileHandler = fopen($tmp_path, 'wb');
$bytesDownloaded = 0; $bytesDownloaded = 0;
$maxSize = $this->attachmentsSettings->getMaxFileSizeInMegabytes() * 1024 * 1024; //Convert to bytes $maxSize = $this->getMaximumUserConfiguredUploadSize(); //We use the maximum user configured size here, PHPs limits dont apply
//Write the downloaded data to file //Write the downloaded data to file
foreach ($this->httpClient->stream($response) as $chunk) { foreach ($this->httpClient->stream($response) as $chunk) {
@ -418,8 +435,8 @@ class AttachmentSubmitHandler
throw new AttachmentDownloadException( throw new AttachmentDownloadException(
sprintf( sprintf(
'The downloaded file is too big! Maximum size is %d MB!', 'The downloaded file is too big! Maximum size is %.1f MB!',
$this->settings->getMaxFileSizeInMegabytes() $maxSize / 1000 / 1000
)); ));
} }
@ -527,10 +544,10 @@ class AttachmentSubmitHandler
} }
/* /*
* Returns the maximum allowed upload size in bytes. * Returns the maximum effective upload size in bytes.
* This is the minimum value of Part-DB max_file_size, and php.ini's post_max_size and upload_max_filesize. * This is the minimum value of Part-DB max_file_size, and php.ini's post_max_size and upload_max_filesize.
*/ */
public function getMaximumAllowedUploadSize(): int public function getMaximumEffectiveUploadSize(): int
{ {
if ($this->max_upload_size_bytes) { if ($this->max_upload_size_bytes) {
return $this->max_upload_size_bytes; return $this->max_upload_size_bytes;
@ -545,6 +562,15 @@ class AttachmentSubmitHandler
return $this->max_upload_size_bytes; return $this->max_upload_size_bytes;
} }
/**
* Returns the maximum user configured upload size in bytes.
* @return int
*/
public function getMaximumUserConfiguredUploadSize(): int
{
return $this->parseFileSizeString($this->settings->maxFileSize);
}
/** /**
* Sanitizes the given SVG file, if the attachment is an internal SVG file. * Sanitizes the given SVG file, if the attachment is an internal SVG file.
* @param Attachment $attachment * @param Attachment $attachment

View file

@ -65,18 +65,4 @@ class AttachmentsSettings
envVar: "bool:ATTACHMENT_SHOW_HTML_FILES", envVarMode: EnvVarMode::OVERWRITE envVar: "bool:ATTACHMENT_SHOW_HTML_FILES", envVarMode: EnvVarMode::OVERWRITE
)] )]
public bool $showHTMLAttachments = false; public bool $showHTMLAttachments = false;
public function getMaxFileSizeInMegabytes(): int
{
$size = $this->maxFileSize;
$unit = strtoupper(substr($size, -1));
$value = (int) rtrim($size, 'KMG');
return match ($unit) {
'K' => (int) ceil($value / 1024),
'M' => $value,
'G' => $value * 1024,
default => $value, // No unit specified, assume megabytes
};
}
} }

View file

@ -1,47 +0,0 @@
<?php
/*
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2026 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Tests\Settings\SystemSettings;
use App\Settings\SystemSettings\AttachmentsSettings;
use App\Tests\SettingsTestHelper;
use PHPUnit\Framework\TestCase;
class AttachmentsSettingsTest extends TestCase
{
public function testGetMaxFileSizeInMegabytes(): void
{
$settings = SettingsTestHelper::createSettingsDummy(AttachmentsSettings::class);
$settings->maxFileSize = '100M';
$this->assertEquals(100, $settings->getMaxFileSizeInMegabytes());
$settings->maxFileSize = '1G';
$this->assertEquals(1024, $settings->getMaxFileSizeInMegabytes());
//We round up to the next megabyte if the file size is smaller than 1 MB
$settings->maxFileSize = '500K';
$this->assertEquals(1, $settings->getMaxFileSizeInMegabytes());
$settings->maxFileSize = '13.5M';
$this->assertEquals(13, $settings->getMaxFileSizeInMegabytes());
}
}