mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-12-10 04:59:30 +00:00
Merge branch 'master' into settings-bundle
This commit is contained in:
commit
442457f11b
131 changed files with 12759 additions and 6750 deletions
|
|
@ -44,35 +44,31 @@ class AttachmentManager
|
|||
*
|
||||
* @param Attachment $attachment The attachment for which the file should be generated
|
||||
*
|
||||
* @return SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is external or has
|
||||
* @return SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is only external or has
|
||||
* invalid file.
|
||||
*/
|
||||
public function attachmentToFile(Attachment $attachment): ?SplFileInfo
|
||||
{
|
||||
if ($attachment->isExternal() || !$this->isFileExisting($attachment)) {
|
||||
if (!$this->isInternalFileExisting($attachment)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SplFileInfo($this->toAbsoluteFilePath($attachment));
|
||||
return new SplFileInfo($this->toAbsoluteInternalFilePath($attachment));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute filepath of the attachment. Null is returned, if the attachment is externally saved,
|
||||
* or is not existing.
|
||||
* Returns the absolute filepath to the internal copy of the attachment. Null is returned, if the attachment is
|
||||
* only externally saved, or is not existing.
|
||||
*
|
||||
* @param Attachment $attachment The attachment for which the filepath should be determined
|
||||
*/
|
||||
public function toAbsoluteFilePath(Attachment $attachment): ?string
|
||||
public function toAbsoluteInternalFilePath(Attachment $attachment): ?string
|
||||
{
|
||||
if ($attachment->getPath() === '') {
|
||||
if (!$attachment->hasInternal()){
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($attachment->isExternal()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$path = $this->pathResolver->placeholderToRealPath($attachment->getPath());
|
||||
$path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
||||
|
||||
//realpath does not work with null as argument
|
||||
if (null === $path) {
|
||||
|
|
@ -89,8 +85,8 @@ class AttachmentManager
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if the file in this attachement is existing. This works for files on the HDD, and for URLs
|
||||
* (it's not checked if the ressource behind the URL is really existing, so for every external attachment true is returned).
|
||||
* Checks if the file in this attachment is existing. This works for files on the HDD, and for URLs
|
||||
* (it's not checked if the resource behind the URL is really existing, so for every external attachment true is returned).
|
||||
*
|
||||
* @param Attachment $attachment The attachment for which the existence should be checked
|
||||
*
|
||||
|
|
@ -98,15 +94,23 @@ class AttachmentManager
|
|||
*/
|
||||
public function isFileExisting(Attachment $attachment): bool
|
||||
{
|
||||
if ($attachment->getPath() === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($attachment->isExternal()) {
|
||||
if($attachment->hasExternal()){
|
||||
return true;
|
||||
}
|
||||
return $this->isInternalFileExisting($attachment);
|
||||
}
|
||||
|
||||
$absolute_path = $this->toAbsoluteFilePath($attachment);
|
||||
/**
|
||||
* Checks if the internal file in this attachment is existing. Returns false if the attachment doesn't have an
|
||||
* internal file.
|
||||
*
|
||||
* @param Attachment $attachment The attachment for which the existence should be checked
|
||||
*
|
||||
* @return bool true if the file is existing
|
||||
*/
|
||||
public function isInternalFileExisting(Attachment $attachment): bool
|
||||
{
|
||||
$absolute_path = $this->toAbsoluteInternalFilePath($attachment);
|
||||
|
||||
if (null === $absolute_path) {
|
||||
return false;
|
||||
|
|
@ -117,21 +121,17 @@ class AttachmentManager
|
|||
|
||||
/**
|
||||
* Returns the filesize of the attachments in bytes.
|
||||
* For external attachments or not existing attachments, null is returned.
|
||||
* For purely external attachments or inexistent attachments, null is returned.
|
||||
*
|
||||
* @param Attachment $attachment the filesize for which the filesize should be calculated
|
||||
*/
|
||||
public function getFileSize(Attachment $attachment): ?int
|
||||
{
|
||||
if ($attachment->isExternal()) {
|
||||
if (!$this->isInternalFileExisting($attachment)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$this->isFileExisting($attachment)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tmp = filesize($this->toAbsoluteFilePath($attachment));
|
||||
$tmp = filesize($this->toAbsoluteInternalFilePath($attachment));
|
||||
|
||||
return false !== $tmp ? $tmp : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,12 +115,16 @@ class AttachmentPathResolver
|
|||
* Converts an relative placeholder filepath (with %MEDIA% or older %BASE%) to an absolute filepath on disk.
|
||||
* The directory separator is always /. Relative pathes are not realy possible (.. is striped).
|
||||
*
|
||||
* @param string $placeholder_path the filepath with placeholder for which the real path should be determined
|
||||
* @param string|null $placeholder_path the filepath with placeholder for which the real path should be determined
|
||||
*
|
||||
* @return string|null The absolute real path of the file, or null if the placeholder path is invalid
|
||||
*/
|
||||
public function placeholderToRealPath(string $placeholder_path): ?string
|
||||
public function placeholderToRealPath(?string $placeholder_path): ?string
|
||||
{
|
||||
if (null === $placeholder_path) {
|
||||
return null;
|
||||
}
|
||||
|
||||
//The new attachments use %MEDIA% as placeholders, which is the directory set in media_directory
|
||||
//Older path entries are given via %BASE% which was the project root
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class AttachmentReverseSearch
|
|||
$repo = $this->em->getRepository(Attachment::class);
|
||||
|
||||
return $repo->findBy([
|
||||
'path' => [$relative_path_new, $relative_path_old],
|
||||
'internal_path' => [$relative_path_new, $relative_path_old],
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class AttachmentSubmitHandler
|
|||
protected MimeTypesInterface $mimeTypes,
|
||||
protected FileTypeFilterTools $filterTools,
|
||||
protected AttachmentsSettings $settings,
|
||||
protected readonly SVGSanitizer $SVGSanitizer,
|
||||
)
|
||||
{
|
||||
//The mapping used to determine which folder will be used for an attachment type
|
||||
|
|
@ -209,13 +210,16 @@ class AttachmentSubmitHandler
|
|||
if ($file instanceof UploadedFile) {
|
||||
|
||||
$this->upload($attachment, $file, $secure_attachment);
|
||||
} elseif ($upload->downloadUrl && $attachment->isExternal()) {
|
||||
} elseif ($upload->downloadUrl && $attachment->hasExternal()) {
|
||||
$this->downloadURL($attachment, $secure_attachment);
|
||||
}
|
||||
|
||||
//Move the attachment files to secure location (and back) if needed
|
||||
$this->moveFile($attachment, $secure_attachment);
|
||||
|
||||
//Sanitize the SVG if needed
|
||||
$this->sanitizeSVGAttachment($attachment);
|
||||
|
||||
//Rename blacklisted (unsecure) files to a better extension
|
||||
$this->renameBlacklistedExtensions($attachment);
|
||||
|
||||
|
|
@ -246,12 +250,12 @@ class AttachmentSubmitHandler
|
|||
protected function renameBlacklistedExtensions(Attachment $attachment): Attachment
|
||||
{
|
||||
//We can not do anything on builtins or external ressources
|
||||
if ($attachment->isBuiltIn() || $attachment->isExternal()) {
|
||||
if ($attachment->isBuiltIn() || !$attachment->hasInternal()) {
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
//Determine the old filepath
|
||||
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getPath());
|
||||
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
||||
if ($old_path === null || $old_path === '' || !file_exists($old_path)) {
|
||||
return $attachment;
|
||||
}
|
||||
|
|
@ -269,7 +273,7 @@ class AttachmentSubmitHandler
|
|||
$fs->rename($old_path, $new_path);
|
||||
|
||||
//Update the attachment
|
||||
$attachment->setPath($this->pathResolver->realPathToPlaceholder($new_path));
|
||||
$attachment->setInternalPath($this->pathResolver->realPathToPlaceholder($new_path));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -277,17 +281,17 @@ class AttachmentSubmitHandler
|
|||
}
|
||||
|
||||
/**
|
||||
* Move the given attachment to secure location (or back to public folder) if needed.
|
||||
* Move the internal copy of the given attachment to a secure location (or back to public folder) if needed.
|
||||
*
|
||||
* @param Attachment $attachment the attachment for which the file should be moved
|
||||
* @param bool $secure_location this value determines, if the attachment is moved to the secure or public folder
|
||||
*
|
||||
* @return Attachment The attachment with the updated filepath
|
||||
* @return Attachment The attachment with the updated internal filepath
|
||||
*/
|
||||
protected function moveFile(Attachment $attachment, bool $secure_location): Attachment
|
||||
{
|
||||
//We can not do anything on builtins or external ressources
|
||||
if ($attachment->isBuiltIn() || $attachment->isExternal()) {
|
||||
if ($attachment->isBuiltIn() || !$attachment->hasInternal()) {
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
|
|
@ -297,7 +301,7 @@ class AttachmentSubmitHandler
|
|||
}
|
||||
|
||||
//Determine the old filepath
|
||||
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getPath());
|
||||
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
||||
if (!file_exists($old_path)) {
|
||||
return $attachment;
|
||||
}
|
||||
|
|
@ -321,7 +325,7 @@ class AttachmentSubmitHandler
|
|||
|
||||
//Save info to attachment entity
|
||||
$new_path = $this->pathResolver->realPathToPlaceholder($new_path);
|
||||
$attachment->setPath($new_path);
|
||||
$attachment->setInternalPath($new_path);
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
|
@ -331,7 +335,7 @@ class AttachmentSubmitHandler
|
|||
*
|
||||
* @param bool $secureAttachment True if the file should be moved to the secure attachment storage
|
||||
*
|
||||
* @return Attachment The attachment with the new filepath
|
||||
* @return Attachment The attachment with the downloaded copy
|
||||
*/
|
||||
protected function downloadURL(Attachment $attachment, bool $secureAttachment): Attachment
|
||||
{
|
||||
|
|
@ -340,16 +344,35 @@ class AttachmentSubmitHandler
|
|||
throw new RuntimeException('Download of attachments is not allowed!');
|
||||
}
|
||||
|
||||
$url = $attachment->getURL();
|
||||
$url = $attachment->getExternalPath();
|
||||
|
||||
$fs = new Filesystem();
|
||||
$attachment_folder = $this->generateAttachmentPath($attachment, $secureAttachment);
|
||||
$tmp_path = $attachment_folder.DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'tmp');
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->request('GET', $url, [
|
||||
$opts = [
|
||||
'buffer' => false,
|
||||
]);
|
||||
//Use user-agent and other headers to make the server think we are a browser
|
||||
'headers' => [
|
||||
"sec-ch-ua" => "\"Not(A:Brand\";v=\"99\", \"Google Chrome\";v=\"133\", \"Chromium\";v=\"133\"",
|
||||
"sec-ch-ua-mobile" => "?0",
|
||||
"sec-ch-ua-platform" => "\"Windows\"",
|
||||
"user-agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
|
||||
"sec-fetch-site" => "none",
|
||||
"sec-fetch-mode" => "navigate",
|
||||
],
|
||||
|
||||
];
|
||||
$response = $this->httpClient->request('GET', $url, $opts);
|
||||
//Digikey wants TLSv1.3, so try again with that if we get a 403
|
||||
if ($response->getStatusCode() === 403) {
|
||||
$opts['crypto_method'] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
|
||||
$response = $this->httpClient->request('GET', $url, $opts);
|
||||
}
|
||||
# if you have these changes and downloads still fail, check if it's due to an unknown certificate. Curl by
|
||||
# default uses the systems ca store and that doesn't contain all the intermediate certificates needed to
|
||||
# verify the leafs
|
||||
|
||||
if (200 !== $response->getStatusCode()) {
|
||||
throw new AttachmentDownloadException('Status code: '.$response->getStatusCode());
|
||||
|
|
@ -401,7 +424,7 @@ class AttachmentSubmitHandler
|
|||
//Make our file path relative to %BASE%
|
||||
$new_path = $this->pathResolver->realPathToPlaceholder($new_path);
|
||||
//Save the path to the attachment
|
||||
$attachment->setPath($new_path);
|
||||
$attachment->setInternalPath($new_path);
|
||||
} catch (TransportExceptionInterface) {
|
||||
throw new AttachmentDownloadException('Transport error!');
|
||||
}
|
||||
|
|
@ -429,7 +452,9 @@ class AttachmentSubmitHandler
|
|||
//Make our file path relative to %BASE%
|
||||
$file_path = $this->pathResolver->realPathToPlaceholder($file_path);
|
||||
//Save the path to the attachment
|
||||
$attachment->setPath($file_path);
|
||||
$attachment->setInternalPath($file_path);
|
||||
//reset any external paths the attachment might have had
|
||||
$attachment->setExternalPath(null);
|
||||
//And save original filename
|
||||
$attachment->setFilename($file->getClientOriginalName());
|
||||
|
||||
|
|
@ -479,4 +504,32 @@ class AttachmentSubmitHandler
|
|||
|
||||
return $this->max_upload_size_bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given SVG file, if the attachment is an internal SVG file.
|
||||
* @param Attachment $attachment
|
||||
* @return Attachment
|
||||
*/
|
||||
public function sanitizeSVGAttachment(Attachment $attachment): Attachment
|
||||
{
|
||||
//We can not do anything on builtins or external ressources
|
||||
if ($attachment->isBuiltIn() || !$attachment->hasInternal()) {
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
//Resolve the path to the file
|
||||
$path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
||||
|
||||
//Check if the file exists
|
||||
if (!file_exists($path)) {
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
//Check if the file is an SVG
|
||||
if ($attachment->getExtension() === "svg") {
|
||||
$this->SVGSanitizer->sanitizeFile($path);
|
||||
}
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,9 +92,9 @@ class AttachmentURLGenerator
|
|||
* Returns a URL under which the attachment file can be viewed.
|
||||
* @return string|null The URL or null if the attachment file is not existing
|
||||
*/
|
||||
public function getViewURL(Attachment $attachment): ?string
|
||||
public function getInternalViewURL(Attachment $attachment): ?string
|
||||
{
|
||||
$absolute_path = $this->attachmentHelper->toAbsoluteFilePath($attachment);
|
||||
$absolute_path = $this->attachmentHelper->toAbsoluteInternalFilePath($attachment);
|
||||
if (null === $absolute_path) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -111,6 +111,7 @@ class AttachmentURLGenerator
|
|||
|
||||
/**
|
||||
* Returns a URL to a thumbnail of the attachment file.
|
||||
* For external files the original URL is returned.
|
||||
* @return string|null The URL or null if the attachment file is not existing
|
||||
*/
|
||||
public function getThumbnailURL(Attachment $attachment, string $filter_name = 'thumbnail_sm'): ?string
|
||||
|
|
@ -119,11 +120,14 @@ class AttachmentURLGenerator
|
|||
throw new InvalidArgumentException('Thumbnail creation only works for picture attachments!');
|
||||
}
|
||||
|
||||
if ($attachment->isExternal() && ($attachment->getURL() !== null && $attachment->getURL() !== '')) {
|
||||
return $attachment->getURL();
|
||||
if (!$attachment->hasInternal()){
|
||||
if($attachment->hasExternal()) {
|
||||
return $attachment->getExternalPath();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$absolute_path = $this->attachmentHelper->toAbsoluteFilePath($attachment);
|
||||
$absolute_path = $this->attachmentHelper->toAbsoluteInternalFilePath($attachment);
|
||||
if (null === $absolute_path) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -137,7 +141,7 @@ class AttachmentURLGenerator
|
|||
//GD can not work with SVG, so serve it directly...
|
||||
//We can not use getExtension here, because it uses the original filename and not the real extension
|
||||
//Instead we use the logic, which is also used to determine if the attachment is a picture
|
||||
$extension = pathinfo(parse_url($attachment->getPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
||||
$extension = pathinfo(parse_url($attachment->getInternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
||||
if ('svg' === $extension) {
|
||||
return $this->assets->getUrl($asset_path);
|
||||
}
|
||||
|
|
@ -157,7 +161,7 @@ class AttachmentURLGenerator
|
|||
/**
|
||||
* Returns a download link to the file associated with the attachment.
|
||||
*/
|
||||
public function getDownloadURL(Attachment $attachment): string
|
||||
public function getInternalDownloadURL(Attachment $attachment): string
|
||||
{
|
||||
//Redirect always to download controller, which sets the correct headers for downloading:
|
||||
return $this->urlGenerator->generate('attachment_download', ['id' => $attachment->getID()]);
|
||||
|
|
|
|||
58
src/Services/Attachments/SVGSanitizer.php
Normal file
58
src/Services/Attachments/SVGSanitizer.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2025 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/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Services\Attachments;
|
||||
|
||||
use Rhukster\DomSanitizer\DOMSanitizer;
|
||||
|
||||
class SVGSanitizer
|
||||
{
|
||||
|
||||
/**
|
||||
* Sanitizes the given SVG string by removing any potentially harmful content (like inline scripts).
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public function sanitizeString(string $input): string
|
||||
{
|
||||
return (new DOMSanitizer(DOMSanitizer::SVG))->sanitize($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given SVG file by removing any potentially harmful content (like inline scripts).
|
||||
* The sanitized content is written back to the file.
|
||||
* @param string $filepath
|
||||
*/
|
||||
public function sanitizeFile(string $filepath): void
|
||||
{
|
||||
//Open the file and read the content
|
||||
$content = file_get_contents($filepath);
|
||||
if ($content === false) {
|
||||
throw new \RuntimeException('Could not read file: ' . $filepath);
|
||||
}
|
||||
//Sanitize the content
|
||||
$sanitizedContent = $this->sanitizeString($content);
|
||||
//Write the sanitized content back to the file
|
||||
file_put_contents($filepath, $sanitizedContent);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue