Skip to content

Commit f568c62

Browse files
fix(barcode-generator-web): sanitize Data Matrix SVG, prefix preview class, tighten memo deps
Data Matrix code value comes from a Mendix DynamicValue and reaches the DOM via dangerouslySetInnerHTML; sanitize the bwip-js SVG output with DOMPurify before injecting it. Also prefix the preview image class per widget style convention and destructure encodeDataMatrix's params so the useMemo dependency list stays exhaustive without a lint suppression.
1 parent 897c837 commit f568c62

5 files changed

Lines changed: 35 additions & 24 deletions

File tree

packages/pluggableWidgets/barcode-generator-web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"dependencies": {
4646
"@bwip-js/browser": "^4.11.2",
4747
"classnames": "^2.5.1",
48+
"dompurify": "^3.4.11",
4849
"jsbarcode": "^3.12.1",
4950
"qrcode.react": "^4.2.0"
5051
},

packages/pluggableWidgets/barcode-generator-web/src/components/DataMatrix.tsx

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
gs1datamatrix,
66
gs1datamatrixrectangular
77
} from "@bwip-js/browser";
8+
import DOMPurify from "dompurify";
89
import { ReactElement, useMemo, useRef } from "react";
910
import { DownloadButton } from "./DownloadButton";
1011
import { DataMatrixTypeConfig } from "../config/Barcode.config";
@@ -16,25 +17,27 @@ interface DataMatrixRendererProps {
1617
config: DataMatrixTypeConfig;
1718
}
1819

20+
type DataMatrixEncodeParams = Pick<DataMatrixTypeConfig, "codeValue" | "size" | "margin" | "gs1Mode" | "shape">;
21+
1922
/** Selects the bwip-js encoder for the requested GS1 mode and symbol shape. */
20-
function encodeDataMatrix(config: DataMatrixTypeConfig): string {
23+
function encodeDataMatrix({ codeValue, size, margin, gs1Mode, shape }: DataMatrixEncodeParams): string {
2124
const opts = {
22-
text: config.codeValue,
25+
text: codeValue,
2326
// bwip-js scale is in module units; map the pixel size onto a reasonable scale.
24-
scale: Math.max(1, Math.round(config.size / 32)),
25-
paddingwidth: config.margin,
26-
paddingheight: config.margin,
27+
scale: Math.max(1, Math.round(size / 32)),
28+
paddingwidth: margin,
29+
paddingheight: margin,
2730
// GS1 AI syntax uses parentheses; parse must be on for the human-readable form.
28-
parse: config.gs1Mode
31+
parse: gs1Mode
2932
} as const;
3033

31-
if (config.gs1Mode) {
32-
return config.shape === "rectangle"
34+
if (gs1Mode) {
35+
return shape === "rectangle"
3336
? gs1datamatrixrectangular({ ...opts, bcid: "gs1datamatrixrectangular" }, drawingSVG())
3437
: gs1datamatrix({ ...opts, bcid: "gs1datamatrix" }, drawingSVG());
3538
}
3639

37-
return config.shape === "rectangle"
40+
return shape === "rectangle"
3841
? datamatrixrectangular({ ...opts, bcid: "datamatrixrectangular" }, drawingSVG())
3942
: datamatrix({ ...opts, bcid: "datamatrix" }, drawingSVG());
4043
}
@@ -53,7 +56,7 @@ function getSvgPixelSize(svg: string, size: number): { width: number; height: nu
5356

5457
export function DataMatrixRenderer({ config }: DataMatrixRendererProps): ReactElement {
5558
const containerRef = useRef<HTMLDivElement>(null);
56-
const { codeValue, downloadButton, size, gs1Mode } = config;
59+
const { codeValue, downloadButton, size, gs1Mode, shape, margin, logLevel } = config;
5760
const buttonPosition = downloadButton?.buttonPosition ?? "bottom";
5861

5962
const { svg, error } = useMemo<{ svg: string | null; error: boolean }>(() => {
@@ -63,27 +66,26 @@ export function DataMatrixRenderer({ config }: DataMatrixRendererProps): ReactEl
6366

6467
const baseValidation = validateBarcodeValue("DataMatrix", codeValue);
6568
if (!baseValidation.valid) {
66-
printError(`Validation failed for Data Matrix: ${baseValidation.message}`, config.logLevel);
69+
printError(`Validation failed for Data Matrix: ${baseValidation.message}`, logLevel);
6770
return { svg: null, error: true };
6871
}
6972

7073
if (gs1Mode) {
7174
const gs1Validation = validateGs1DataMatrixValue(codeValue);
7275
if (!gs1Validation.valid) {
73-
printError(`GS1 Data Matrix validation failed: ${gs1Validation.message}`, config.logLevel);
76+
printError(`GS1 Data Matrix validation failed: ${gs1Validation.message}`, logLevel);
7477
return { svg: null, error: true };
7578
}
7679
}
7780

7881
try {
79-
return { svg: encodeDataMatrix(config), error: false };
82+
return { svg: encodeDataMatrix({ codeValue, size, margin, gs1Mode, shape }), error: false };
8083
} catch (e) {
8184
const message = e instanceof Error ? e.message : "Error generating Data Matrix";
82-
printError(`Rendering failed: ${message} \nValue: "${codeValue}"`, config.logLevel);
85+
printError(`Rendering failed: ${message} \nValue: "${codeValue}"`, logLevel);
8386
return { svg: null, error: true };
8487
}
85-
// eslint-disable-next-line react-hooks/exhaustive-deps
86-
}, [codeValue, size, gs1Mode, config.shape, config.margin, config.logLevel]);
88+
}, [codeValue, size, gs1Mode, shape, margin, logLevel]);
8789

8890
if (error || !svg) {
8991
return (
@@ -117,7 +119,9 @@ export function DataMatrixRenderer({ config }: DataMatrixRendererProps): ReactEl
117119
ref={containerRef}
118120
className="datamatrix-svg"
119121
style={{ width, height }}
120-
dangerouslySetInnerHTML={{ __html: svg }}
122+
dangerouslySetInnerHTML={{
123+
__html: DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true, svgFilters: true } })
124+
}}
121125
/>
122126
{buttonPosition === "bottom" && button}
123127
</div>

packages/pluggableWidgets/barcode-generator-web/src/components/preview/DataMatrixPreview.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { datamatrix, drawingSVG, gs1datamatrix } from "@bwip-js/browser";
2+
import DOMPurify from "dompurify";
23
import { ReactElement, useMemo } from "react";
34
import { BarcodeGeneratorPreviewProps } from "../../../typings/BarcodeGeneratorProps";
45

@@ -30,9 +31,11 @@ export function DataMatrixPreview(props: DataMatrixPreviewProps): ReactElement {
3031
{restProps.buttonPosition === "top" && downloadButton}
3132
{svg ? (
3233
<div
33-
className="datamatrix-preview-image"
34+
className="barcode-generator-datamatrix-preview-image"
3435
style={{ width: displaySize, height: displaySize }}
35-
dangerouslySetInnerHTML={{ __html: svg }}
36+
dangerouslySetInnerHTML={{
37+
__html: DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true, svgFilters: true } })
38+
}}
3639
/>
3740
) : (
3841
<div className="alert alert-danger" role="alert">

packages/pluggableWidgets/barcode-generator-web/src/ui/BarcodeGenerator.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ $widget-prefix: "barcode-generator";
109109
object-fit: contain;
110110
}
111111

112-
.datamatrix-preview-image {
112+
.barcode-generator-datamatrix-preview-image {
113113
max-width: 100%;
114114

115115
svg {

pnpm-lock.yaml

Lines changed: 7 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)