Properly HTML escape fields for label template placeholders

Fixes issue #1446
This commit is contained in:
Jan Böhmer 2026-07-19 14:03:14 +02:00
parent 5054642d41
commit dcc431d28c
10 changed files with 43 additions and 42 deletions

View file

@ -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 '<img src="'.$base64.'" width="'.$width.'" style="min-height: 25px;" alt="'.$alt_text.'"/>';
}
@ -94,4 +94,4 @@ class BarcodeHelper
return $repr;
}
}
}

View file

@ -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) {

View file

@ -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)
{
}

View file

@ -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';

View file

@ -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;

View file

@ -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());

View file

@ -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) {

View file

@ -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

View file

@ -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()) : '';
}
}

View file

@ -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()) : '';
}
}