From dcc431d28cb85825496dc5f55dd9642985392862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 19 Jul 2026 14:03:14 +0200 Subject: [PATCH] Properly HTML escape fields for label template placeholders Fixes issue #1446 --- .../LabelSystem/Barcodes/BarcodeHelper.php | 6 ++--- .../AbstractDBElementProvider.php | 6 ++--- .../PlaceholderProviders/BarcodeProvider.php | 8 +++---- .../PlaceholderProviders/GlobalProviders.php | 12 +++++----- .../NamedElementProvider.php | 2 +- .../PlaceholderProviders/PartLotProvider.php | 16 ++++++------- .../PlaceholderProviders/PartProvider.php | 24 +++++++++---------- .../PlaceholderProviderInterface.php | 1 + .../StorelocationProvider.php | 4 ++-- .../StructuralDBElementProvider.php | 6 ++--- 10 files changed, 43 insertions(+), 42 deletions(-) diff --git a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php index c9fe64f3..53e52662 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeHelper.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeHelper.php @@ -67,8 +67,8 @@ class BarcodeHelper { $svg = $this->barcodeAsSVG($content, $type); $base64 = $this->dataUri($svg, 'image/svg+xml'); - $alt_text ??= $content; - + $alt_text ??= htmlspecialchars($content); + return ''.$alt_text.''; } @@ -94,4 +94,4 @@ class BarcodeHelper return $repr; } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php index 081b3e91..0059a3fc 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php @@ -44,9 +44,9 @@ namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\Base\AbstractDBElement; use App\Services\ElementTypeNameGenerator; -final class AbstractDBElementProvider implements PlaceholderProviderInterface +final readonly class AbstractDBElementProvider implements PlaceholderProviderInterface { - public function __construct(private readonly ElementTypeNameGenerator $elementTypeNameGenerator) + public function __construct(private ElementTypeNameGenerator $elementTypeNameGenerator) { } @@ -54,7 +54,7 @@ final class AbstractDBElementProvider implements PlaceholderProviderInterface { if ($label_target instanceof AbstractDBElement) { if ('[[TYPE]]' === $placeholder) { - return $this->elementTypeNameGenerator->getLocalizedTypeLabel($label_target); + return $this->elementTypeNameGenerator->typeLabel($label_target); } if ('[[ID]]' === $placeholder) { diff --git a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php index 9719917a..745a8227 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php @@ -31,11 +31,11 @@ use App\Services\LabelSystem\LabelBarcodeGenerator; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; use Com\Tecnick\Barcode\Exception; -final class BarcodeProvider implements PlaceholderProviderInterface +final readonly class BarcodeProvider implements PlaceholderProviderInterface { - public function __construct(private readonly LabelBarcodeGenerator $barcodeGenerator, - private readonly BarcodeContentGenerator $barcodeContentGenerator, - private readonly BarcodeHelper $barcodeHelper) + public function __construct(private LabelBarcodeGenerator $barcodeGenerator, + private BarcodeContentGenerator $barcodeContentGenerator, + private BarcodeHelper $barcodeHelper) { } diff --git a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php index f14a5863..38b59bb4 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php +++ b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php @@ -53,11 +53,11 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; * Provides Placeholders for infos about global infos like Installation name or datetimes. * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\GlobalProvidersTest */ -final class GlobalProviders implements PlaceholderProviderInterface +final readonly class GlobalProviders implements PlaceholderProviderInterface { public function __construct( - private readonly Security $security, - private readonly UrlGeneratorInterface $url_generator, + private Security $security, + private UrlGeneratorInterface $url_generator, private CustomizationSettings $customizationSettings, ) { @@ -66,13 +66,13 @@ final class GlobalProviders implements PlaceholderProviderInterface public function replace(string $placeholder, object $label_target, array $options = []): ?string { if ('[[INSTALL_NAME]]' === $placeholder) { - return $this->customizationSettings->instanceName; + return htmlspecialchars($this->customizationSettings->instanceName); } $user = $this->security->getUser(); if ('[[USERNAME]]' === $placeholder) { if ($user instanceof User) { - return $user->getName(); + return htmlspecialchars($user->getName()); } return 'anonymous'; @@ -80,7 +80,7 @@ final class GlobalProviders implements PlaceholderProviderInterface if ('[[USERNAME_FULL]]' === $placeholder) { if ($user instanceof User) { - return $user->getFullName(true); + return htmlspecialchars($user->getFullName(true)); } return 'anonymous'; diff --git a/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php index d8d38120..519f5081 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php @@ -51,7 +51,7 @@ final class NamedElementProvider implements PlaceholderProviderInterface public function replace(string $placeholder, object $label_target, array $options = []): ?string { if ($label_target instanceof NamedElementInterface && '[[NAME]]' === $placeholder) { - return $label_target->getName(); + return htmlspecialchars($label_target->getName()); } return null; diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php index 946b4892..7dee615b 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php @@ -52,9 +52,9 @@ use Locale; /** * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\PartLotProviderTest */ -final class PartLotProvider implements PlaceholderProviderInterface +final readonly class PartLotProvider implements PlaceholderProviderInterface { - public function __construct(private readonly LabelTextReplacer $labelTextReplacer, private readonly AmountFormatter $amountFormatter) + public function __construct(private LabelTextReplacer $labelTextReplacer, private AmountFormatter $amountFormatter) { } @@ -66,11 +66,11 @@ final class PartLotProvider implements PlaceholderProviderInterface } if ('[[LOT_NAME]]' === $placeholder) { - return $label_target->getName(); + return htmlspecialchars($label_target->getName()); } if ('[[LOT_COMMENT]]' === $placeholder) { - return $label_target->getComment(); + return htmlspecialchars($label_target->getComment()); } if ('[[EXPIRATION_DATE]]' === $placeholder) { @@ -95,19 +95,19 @@ final class PartLotProvider implements PlaceholderProviderInterface } if ('[[LOCATION]]' === $placeholder) { - return $label_target->getStorageLocation() instanceof StorageLocation ? $label_target->getStorageLocation()->getName() : ''; + return $label_target->getStorageLocation() instanceof StorageLocation ? htmlspecialchars($label_target->getStorageLocation()->getName()) : ''; } if ('[[LOCATION_FULL]]' === $placeholder) { - return $label_target->getStorageLocation() instanceof StorageLocation ? $label_target->getStorageLocation()->getFullPath() : ''; + return $label_target->getStorageLocation() instanceof StorageLocation ? htmlspecialchars($label_target->getStorageLocation()->getFullPath()) : ''; } if ('[[OWNER]]' === $placeholder) { - return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getFullName() : ''; + return $label_target->getOwner() instanceof User ? htmlspecialchars($label_target->getOwner()->getFullName()) : ''; } if ('[[OWNER_USERNAME]]' === $placeholder) { - return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getUsername() : ''; + return $label_target->getOwner() instanceof User ? htmlspecialchars($label_target->getOwner()->getUsername()) : ''; } return $this->labelTextReplacer->handlePlaceholder($placeholder, $label_target->getPart()); diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php index 7d9e4db5..857aa1ff 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php @@ -54,11 +54,11 @@ use Symfony\Contracts\Translation\TranslatorInterface; /** * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\PartProviderTest */ -final class PartProvider implements PlaceholderProviderInterface +final readonly class PartProvider implements PlaceholderProviderInterface { - private readonly MarkdownConverter $inlineConverter; + private MarkdownConverter $inlineConverter; - public function __construct(private readonly SIFormatter $siFormatter, private readonly TranslatorInterface $translator) + public function __construct(private SIFormatter $siFormatter, private TranslatorInterface $translator) { $environment = new Environment(); $environment->addExtension(new InlinesOnlyExtension()); @@ -72,27 +72,27 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[CATEGORY]]' === $placeholder) { - return $part->getCategory() instanceof Category ? $part->getCategory()->getName() : ''; + return $part->getCategory() instanceof Category ? htmlspecialchars($part->getCategory()->getName()) : ''; } if ('[[CATEGORY_FULL]]' === $placeholder) { - return $part->getCategory() instanceof Category ? $part->getCategory()->getFullPath() : ''; + return $part->getCategory() instanceof Category ? htmlspecialchars($part->getCategory()->getFullPath()) : ''; } if ('[[MANUFACTURER]]' === $placeholder) { - return $part->getManufacturer() instanceof Manufacturer ? $part->getManufacturer()->getName() : ''; + return $part->getManufacturer() instanceof Manufacturer ? htmlspecialchars($part->getManufacturer()->getName()) : ''; } if ('[[MANUFACTURER_FULL]]' === $placeholder) { - return $part->getManufacturer() instanceof Manufacturer ? $part->getManufacturer()->getFullPath() : ''; + return $part->getManufacturer() instanceof Manufacturer ? htmlspecialchars($part->getManufacturer()->getFullPath()) : ''; } if ('[[FOOTPRINT]]' === $placeholder) { - return $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getName() : ''; + return $part->getFootprint() instanceof Footprint ? htmlspecialchars($part->getFootprint()->getName()) : ''; } if ('[[FOOTPRINT_FULL]]' === $placeholder) { - return $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getFullPath() : ''; + return $part->getFootprint() instanceof Footprint ? htmlspecialchars($part->getFootprint()->getFullPath()) : ''; } if ('[[MASS]]' === $placeholder) { @@ -100,15 +100,15 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[MPN]]' === $placeholder) { - return $part->getManufacturerProductNumber(); + return htmlspecialchars($part->getManufacturerProductNumber()); } if ('[[IPN]]' === $placeholder) { - return $part->getIpn() ?? ''; + return $part->getIpn() ? htmlspecialchars($part->getIpn()) : ''; } if ('[[TAGS]]' === $placeholder) { - return $part->getTags(); + return htmlspecialchars($part->getTags()); } if ('[[M_STATUS]]' === $placeholder) { diff --git a/src/Services/LabelSystem/PlaceholderProviders/PlaceholderProviderInterface.php b/src/Services/LabelSystem/PlaceholderProviders/PlaceholderProviderInterface.php index f5b1fc3b..3c1857d6 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PlaceholderProviderInterface.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PlaceholderProviderInterface.php @@ -46,6 +46,7 @@ interface PlaceholderProviderInterface /** * Determines the real value of this placeholder. * If the placeholder is not supported, null must be returned. + * The placeholder provider has to handle HTML escaping, as the output of this function will be used directly in the label template. * * @param string $placeholder The placeholder (e.g. "%%PLACEHOLDER%%") that should be replaced * @param object $label_target The object that is targeted by the label diff --git a/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php index 4b4d8dcd..de791b94 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php @@ -31,11 +31,11 @@ class StorelocationProvider implements PlaceholderProviderInterface { if ($label_target instanceof StorageLocation) { if ('[[OWNER]]' === $placeholder) { - return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getFullName() : ''; + return $label_target->getOwner() instanceof User ? htmlspecialchars($label_target->getOwner()->getFullName()) : ''; } if ('[[OWNER_USERNAME]]' === $placeholder) { - return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getUsername() : ''; + return $label_target->getOwner() instanceof User ? htmlspecialchars($label_target->getOwner()->getUsername()) : ''; } } diff --git a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php index f37f5901..c8c49cfe 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php @@ -55,13 +55,13 @@ final class StructuralDBElementProvider implements PlaceholderProviderInterface return strip_tags((string) $label_target->getComment()); } if ('[[FULL_PATH]]' === $placeholder) { - return $label_target->getFullPath(); + return htmlspecialchars($label_target->getFullPath()); } if ('[[PARENT]]' === $placeholder) { - return $label_target->getParent() instanceof AbstractStructuralDBElement ? $label_target->getParent()->getName() : ''; + return $label_target->getParent() instanceof AbstractStructuralDBElement ? htmlspecialchars($label_target->getParent()->getName()) : ''; } if ('[[PARENT_FULL_PATH]]' === $placeholder) { - return $label_target->getParent() instanceof AbstractStructuralDBElement ? $label_target->getParent()->getFullPath() : ''; + return $label_target->getParent() instanceof AbstractStructuralDBElement ? htmlspecialchars($label_target->getParent()->getFullPath()) : ''; } }