fabric 7.1.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/.husky/pre-commit +1 -0
  2. package/CHANGELOG.md +13 -0
  3. package/dist/extensions/cropping_controls/croppingControls.d.ts +12 -8
  4. package/dist/extensions/cropping_controls/croppingControls.d.ts.map +1 -1
  5. package/dist/extensions/cropping_controls/croppingHandlers.d.ts +19 -1
  6. package/dist/extensions/cropping_controls/croppingHandlers.d.ts.map +1 -1
  7. package/dist/extensions/cropping_controls/enterCropMode.d.ts.map +1 -1
  8. package/dist/index.js +189 -160
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/index.min.mjs +1 -1
  13. package/dist/index.min.mjs.map +1 -1
  14. package/dist/index.mjs +189 -160
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/index.node.cjs +189 -160
  17. package/dist/index.node.cjs.map +1 -1
  18. package/dist/index.node.mjs +189 -160
  19. package/dist/index.node.mjs.map +1 -1
  20. package/dist/package.json.min.mjs +1 -1
  21. package/dist/package.json.mjs +1 -1
  22. package/dist/src/EventTypeDefs.d.ts +3 -0
  23. package/dist/src/EventTypeDefs.d.ts.map +1 -1
  24. package/dist/src/Pattern/Pattern.d.ts.map +1 -1
  25. package/dist/src/Pattern/Pattern.min.mjs +1 -1
  26. package/dist/src/Pattern/Pattern.min.mjs.map +1 -1
  27. package/dist/src/Pattern/Pattern.mjs +2 -1
  28. package/dist/src/Pattern/Pattern.mjs.map +1 -1
  29. package/dist/src/Shadow.d.ts +1 -1
  30. package/dist/src/Shadow.d.ts.map +1 -1
  31. package/dist/src/Shadow.min.mjs +1 -1
  32. package/dist/src/Shadow.min.mjs.map +1 -1
  33. package/dist/src/Shadow.mjs +5 -4
  34. package/dist/src/Shadow.mjs.map +1 -1
  35. package/dist/src/canvas/CanvasOptions.d.ts.map +1 -1
  36. package/dist/src/canvas/CanvasOptions.min.mjs.map +1 -1
  37. package/dist/src/canvas/CanvasOptions.mjs.map +1 -1
  38. package/dist/src/canvas/SelectableCanvas.d.ts +2 -0
  39. package/dist/src/canvas/SelectableCanvas.d.ts.map +1 -1
  40. package/dist/src/canvas/SelectableCanvas.min.mjs +1 -1
  41. package/dist/src/canvas/SelectableCanvas.min.mjs.map +1 -1
  42. package/dist/src/canvas/SelectableCanvas.mjs +6 -1
  43. package/dist/src/canvas/SelectableCanvas.mjs.map +1 -1
  44. package/dist/src/canvas/StaticCanvas.d.ts.map +1 -1
  45. package/dist/src/canvas/StaticCanvas.min.mjs +1 -1
  46. package/dist/src/canvas/StaticCanvas.min.mjs.map +1 -1
  47. package/dist/src/canvas/StaticCanvas.mjs +3 -1
  48. package/dist/src/canvas/StaticCanvas.mjs.map +1 -1
  49. package/dist/src/canvas/StaticCanvasOptions.d.ts.map +1 -1
  50. package/dist/src/canvas/StaticCanvasOptions.min.mjs.map +1 -1
  51. package/dist/src/canvas/StaticCanvasOptions.mjs.map +1 -1
  52. package/dist/src/controls/Control.d.ts +9 -1
  53. package/dist/src/controls/Control.d.ts.map +1 -1
  54. package/dist/src/controls/Control.min.mjs +1 -1
  55. package/dist/src/controls/Control.min.mjs.map +1 -1
  56. package/dist/src/controls/Control.mjs +8 -0
  57. package/dist/src/controls/Control.mjs.map +1 -1
  58. package/dist/src/gradient/Gradient.d.ts.map +1 -1
  59. package/dist/src/gradient/Gradient.min.mjs +1 -1
  60. package/dist/src/gradient/Gradient.min.mjs.map +1 -1
  61. package/dist/src/gradient/Gradient.mjs +19 -6
  62. package/dist/src/gradient/Gradient.mjs.map +1 -1
  63. package/dist/src/shapes/Circle.d.ts.map +1 -1
  64. package/dist/src/shapes/Circle.min.mjs +1 -1
  65. package/dist/src/shapes/Circle.min.mjs.map +1 -1
  66. package/dist/src/shapes/Circle.mjs +10 -7
  67. package/dist/src/shapes/Circle.mjs.map +1 -1
  68. package/dist/src/shapes/Ellipse.d.ts.map +1 -1
  69. package/dist/src/shapes/Ellipse.min.mjs +1 -1
  70. package/dist/src/shapes/Ellipse.min.mjs.map +1 -1
  71. package/dist/src/shapes/Ellipse.mjs +2 -1
  72. package/dist/src/shapes/Ellipse.mjs.map +1 -1
  73. package/dist/src/shapes/Group.d.ts.map +1 -1
  74. package/dist/src/shapes/Group.min.mjs +1 -1
  75. package/dist/src/shapes/Group.min.mjs.map +1 -1
  76. package/dist/src/shapes/Group.mjs +2 -1
  77. package/dist/src/shapes/Group.mjs.map +1 -1
  78. package/dist/src/shapes/IText/IText.d.ts.map +1 -1
  79. package/dist/src/shapes/IText/IText.min.mjs.map +1 -1
  80. package/dist/src/shapes/IText/IText.mjs.map +1 -1
  81. package/dist/src/shapes/Image.d.ts +1 -1
  82. package/dist/src/shapes/Image.d.ts.map +1 -1
  83. package/dist/src/shapes/Image.min.mjs +1 -1
  84. package/dist/src/shapes/Image.min.mjs.map +1 -1
  85. package/dist/src/shapes/Image.mjs +4 -3
  86. package/dist/src/shapes/Image.mjs.map +1 -1
  87. package/dist/src/shapes/Line.d.ts.map +1 -1
  88. package/dist/src/shapes/Line.min.mjs +1 -1
  89. package/dist/src/shapes/Line.min.mjs.map +1 -1
  90. package/dist/src/shapes/Line.mjs +6 -10
  91. package/dist/src/shapes/Line.mjs.map +1 -1
  92. package/dist/src/shapes/Object/FabricObjectSVGExportMixin.d.ts.map +1 -1
  93. package/dist/src/shapes/Object/FabricObjectSVGExportMixin.min.mjs +1 -1
  94. package/dist/src/shapes/Object/FabricObjectSVGExportMixin.min.mjs.map +1 -1
  95. package/dist/src/shapes/Object/FabricObjectSVGExportMixin.mjs +5 -4
  96. package/dist/src/shapes/Object/FabricObjectSVGExportMixin.mjs.map +1 -1
  97. package/dist/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
  98. package/dist/src/shapes/Object/InteractiveObject.min.mjs.map +1 -1
  99. package/dist/src/shapes/Object/InteractiveObject.mjs.map +1 -1
  100. package/dist/src/shapes/Object/Object.d.ts.map +1 -1
  101. package/dist/src/shapes/Object/Object.min.mjs +1 -1
  102. package/dist/src/shapes/Object/Object.min.mjs.map +1 -1
  103. package/dist/src/shapes/Object/Object.mjs +3 -0
  104. package/dist/src/shapes/Object/Object.mjs.map +1 -1
  105. package/dist/src/shapes/Object/types/FabricObjectProps.d.ts.map +1 -1
  106. package/dist/src/shapes/Object/types/ObjectProps.d.ts.map +1 -1
  107. package/dist/src/shapes/Path.d.ts.map +1 -1
  108. package/dist/src/shapes/Path.min.mjs.map +1 -1
  109. package/dist/src/shapes/Path.mjs +1 -2
  110. package/dist/src/shapes/Path.mjs.map +1 -1
  111. package/dist/src/shapes/Polyline.d.ts.map +1 -1
  112. package/dist/src/shapes/Polyline.min.mjs +1 -1
  113. package/dist/src/shapes/Polyline.min.mjs.map +1 -1
  114. package/dist/src/shapes/Polyline.mjs +10 -6
  115. package/dist/src/shapes/Polyline.mjs.map +1 -1
  116. package/dist/src/shapes/Rect.d.ts.map +1 -1
  117. package/dist/src/shapes/Rect.min.mjs +1 -1
  118. package/dist/src/shapes/Rect.min.mjs.map +1 -1
  119. package/dist/src/shapes/Rect.mjs +2 -1
  120. package/dist/src/shapes/Rect.mjs.map +1 -1
  121. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  122. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  123. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  124. package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs +1 -1
  125. package/dist/src/shapes/Text/TextSVGExportMixin.min.mjs.map +1 -1
  126. package/dist/src/shapes/Text/TextSVGExportMixin.mjs +5 -5
  127. package/dist/src/shapes/Text/TextSVGExportMixin.mjs.map +1 -1
  128. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  129. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  130. package/dist/src/shapes/Textbox.mjs.map +1 -1
  131. package/dist/src/shapes/Triangle.d.ts.map +1 -1
  132. package/dist/src/shapes/Triangle.min.mjs.map +1 -1
  133. package/dist/src/shapes/Triangle.mjs.map +1 -1
  134. package/dist/src/util/lang_string.d.ts +1 -1
  135. package/dist/src/util/lang_string.d.ts.map +1 -1
  136. package/dist/src/util/lang_string.min.mjs +1 -1
  137. package/dist/src/util/lang_string.min.mjs.map +1 -1
  138. package/dist/src/util/lang_string.mjs +1 -1
  139. package/dist/src/util/lang_string.mjs.map +1 -1
  140. package/dist/src/util/misc/svgParsing.d.ts.map +1 -1
  141. package/dist/src/util/misc/svgParsing.min.mjs +1 -1
  142. package/dist/src/util/misc/svgParsing.min.mjs.map +1 -1
  143. package/dist/src/util/misc/svgParsing.mjs +2 -1
  144. package/dist/src/util/misc/svgParsing.mjs.map +1 -1
  145. package/dist-extensions/cropping_controls/croppingControls.mjs +39 -9
  146. package/dist-extensions/cropping_controls/croppingControls.mjs.map +1 -1
  147. package/dist-extensions/cropping_controls/croppingHandlers.mjs +84 -2
  148. package/dist-extensions/cropping_controls/croppingHandlers.mjs.map +1 -1
  149. package/dist-extensions/cropping_controls/enterCropMode.mjs +7 -2
  150. package/dist-extensions/cropping_controls/enterCropMode.mjs.map +1 -1
  151. package/dist-extensions/extensions/cropping_controls/croppingControls.d.ts +12 -8
  152. package/dist-extensions/extensions/cropping_controls/croppingControls.d.ts.map +1 -1
  153. package/dist-extensions/extensions/cropping_controls/croppingHandlers.d.ts +19 -1
  154. package/dist-extensions/extensions/cropping_controls/croppingHandlers.d.ts.map +1 -1
  155. package/dist-extensions/extensions/cropping_controls/enterCropMode.d.ts.map +1 -1
  156. package/dist-extensions/fabric-extensions.min.js +1 -1
  157. package/dist-extensions/fabric-extensions.min.js.map +1 -1
  158. package/dist-extensions/src/EventTypeDefs.d.ts +3 -0
  159. package/dist-extensions/src/EventTypeDefs.d.ts.map +1 -1
  160. package/dist-extensions/src/Pattern/Pattern.d.ts.map +1 -1
  161. package/dist-extensions/src/Shadow.d.ts +1 -1
  162. package/dist-extensions/src/Shadow.d.ts.map +1 -1
  163. package/dist-extensions/src/canvas/CanvasOptions.d.ts.map +1 -1
  164. package/dist-extensions/src/canvas/SelectableCanvas.d.ts +2 -0
  165. package/dist-extensions/src/canvas/SelectableCanvas.d.ts.map +1 -1
  166. package/dist-extensions/src/canvas/StaticCanvas.d.ts.map +1 -1
  167. package/dist-extensions/src/canvas/StaticCanvasOptions.d.ts.map +1 -1
  168. package/dist-extensions/src/controls/Control.d.ts +9 -1
  169. package/dist-extensions/src/controls/Control.d.ts.map +1 -1
  170. package/dist-extensions/src/gradient/Gradient.d.ts.map +1 -1
  171. package/dist-extensions/src/shapes/Circle.d.ts.map +1 -1
  172. package/dist-extensions/src/shapes/Ellipse.d.ts.map +1 -1
  173. package/dist-extensions/src/shapes/Group.d.ts.map +1 -1
  174. package/dist-extensions/src/shapes/IText/IText.d.ts.map +1 -1
  175. package/dist-extensions/src/shapes/Image.d.ts.map +1 -1
  176. package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
  177. package/dist-extensions/src/shapes/Object/FabricObjectSVGExportMixin.d.ts.map +1 -1
  178. package/dist-extensions/src/shapes/Object/InteractiveObject.d.ts.map +1 -1
  179. package/dist-extensions/src/shapes/Object/Object.d.ts.map +1 -1
  180. package/dist-extensions/src/shapes/Object/types/FabricObjectProps.d.ts.map +1 -1
  181. package/dist-extensions/src/shapes/Object/types/ObjectProps.d.ts.map +1 -1
  182. package/dist-extensions/src/shapes/Path.d.ts +1 -1
  183. package/dist-extensions/src/shapes/Path.d.ts.map +1 -1
  184. package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
  185. package/dist-extensions/src/shapes/Rect.d.ts.map +1 -1
  186. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  187. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  188. package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
  189. package/dist-extensions/src/util/lang_string.d.ts +1 -1
  190. package/dist-extensions/src/util/lang_string.d.ts.map +1 -1
  191. package/dist-extensions/src/util/misc/svgParsing.d.ts.map +1 -1
  192. package/eslint.config.mjs +2 -0
  193. package/extensions/cropping_controls/croppingControls.spec.ts +65 -27
  194. package/extensions/cropping_controls/croppingControls.ts +40 -8
  195. package/extensions/cropping_controls/croppingHandlers.spec.ts +355 -46
  196. package/extensions/cropping_controls/croppingHandlers.ts +123 -0
  197. package/extensions/cropping_controls/enterCropMode.ts +6 -2
  198. package/package.json +17 -8
  199. package/src/ClassRegistry.spec.ts +18 -19
  200. package/src/EventTypeDefs.ts +13 -11
  201. package/src/Pattern/Pattern.spec.ts +12 -0
  202. package/src/Pattern/Pattern.ts +3 -2
  203. package/src/Shadow.ts +9 -8
  204. package/src/brushes/PencilBrush.spec.ts +11 -11
  205. package/src/canvas/Canvas-dispose.spec.ts +8 -7
  206. package/src/canvas/Canvas.spec.ts +27 -29
  207. package/src/canvas/CanvasOptions.ts +2 -1
  208. package/src/canvas/SelectableCanvas.ts +11 -4
  209. package/src/canvas/StaticCanvas.spec.ts +20 -0
  210. package/src/canvas/StaticCanvas.ts +7 -4
  211. package/src/canvas/StaticCanvasOptions.ts +1 -3
  212. package/src/controls/Control.ts +24 -1
  213. package/src/gradient/Gradient.spec.ts +101 -46
  214. package/src/gradient/Gradient.ts +27 -14
  215. package/src/shapes/Circle.spec.ts +10 -39
  216. package/src/shapes/Circle.ts +11 -11
  217. package/src/shapes/Ellipse.spec.ts +8 -37
  218. package/src/shapes/Ellipse.ts +7 -7
  219. package/src/shapes/Group.ts +3 -3
  220. package/src/shapes/IText/IText-click-behavior.spec.ts +36 -49
  221. package/src/shapes/IText/IText.ts +5 -6
  222. package/src/shapes/IText/__snapshots__/ITextBehavior.test.ts.snap +6 -6
  223. package/src/shapes/Image.spec.ts +17 -33
  224. package/src/shapes/Image.ts +15 -11
  225. package/src/shapes/Line.spec.ts +4 -30
  226. package/src/shapes/Line.ts +11 -16
  227. package/src/shapes/Object/FabricObjectSVGExportMixin.ts +11 -4
  228. package/src/shapes/Object/InteractiveObject.ts +4 -4
  229. package/src/shapes/Object/Object.ts +6 -5
  230. package/src/shapes/Object/objectSvgExport.spec.ts +112 -0
  231. package/src/shapes/Object/types/FabricObjectProps.ts +1 -4
  232. package/src/shapes/Object/types/ObjectProps.ts +1 -3
  233. package/src/shapes/Path.spec.ts +4 -27
  234. package/src/shapes/Path.ts +2 -4
  235. package/src/shapes/Polygon.spec.ts +4 -31
  236. package/src/shapes/Polyline.spec.ts +4 -31
  237. package/src/shapes/Polyline.ts +11 -12
  238. package/src/shapes/Rect.spec.ts +25 -33
  239. package/src/shapes/Rect.ts +7 -7
  240. package/src/shapes/Text/Text.spec.ts +3 -32
  241. package/src/shapes/Text/Text.ts +5 -6
  242. package/src/shapes/Text/TextSVGExportMixin.ts +14 -14
  243. package/src/shapes/Text/__snapshots__/Text.spec.ts.snap +1 -1
  244. package/src/shapes/Textbox.spec.ts +5 -5
  245. package/src/shapes/Textbox.ts +6 -5
  246. package/src/shapes/Triangle.ts +4 -4
  247. package/src/shapes/__snapshots__/Image.spec.ts.snap +4 -4
  248. package/src/shapes/__snapshots__/Textbox.spec.ts.snap +5 -5
  249. package/src/util/lang_string.ts +3 -2
  250. package/src/util/misc/svgParsing.ts +2 -1
  251. package/tsconfig.spec.json +1 -0
  252. package/vitest.config.ts +12 -2
  253. package/vitest.extend.ts +6 -2
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Circle } from '../Circle';
3
+ import { Ellipse } from '../Ellipse';
4
+ import { Rect } from '../Rect';
5
+ import { FabricText } from '../Text/Text';
6
+ import { FabricImage } from '../Image';
7
+ import { Shadow } from '../../Shadow';
8
+
9
+ const MALICIOUS = 'x" /><script>alert(1)</script>';
10
+ const MALICIOUS2 = `x" onclick="alert('svg animatetransform onbegin')"`;
11
+ const ONCLICK_PAYLOAD = `onclick="alert('svg animatetransform onbegin')"`;
12
+
13
+ describe.each([MALICIOUS, MALICIOUS2])(
14
+ 'Object SVG export sanitization (%s)',
15
+ (payload) => {
16
+ it('sanitizes object id attributes', () => {
17
+ const rect = new Rect({
18
+ id: payload,
19
+ width: 10,
20
+ height: 10,
21
+ });
22
+
23
+ const svg = rect.toSVG();
24
+ expect(svg).not.toContain('<script>');
25
+ expect(svg).not.toContain(ONCLICK_PAYLOAD);
26
+ });
27
+
28
+ it('sanitizes object style attributes', () => {
29
+ const rect = new Rect({
30
+ width: 10,
31
+ height: 10,
32
+ fillRule: payload as unknown as 'nonzero',
33
+ strokeLineCap: payload as unknown as 'round',
34
+ strokeLineJoin: payload as unknown as 'round',
35
+ strokeDashArray: [payload as unknown as number],
36
+ paintFirst: payload as unknown as 'stroke',
37
+ shadow: new Shadow({
38
+ color: 'rgba(0, 0, 0, 0.5)',
39
+ blur: 0,
40
+ offsetX: 0,
41
+ offsetY: 0,
42
+ }),
43
+ });
44
+ rect.shadow.id = payload as unknown as number;
45
+
46
+ const svg = rect.toSVG();
47
+ expect(svg).not.toContain('<script>');
48
+ expect(svg).not.toContain(ONCLICK_PAYLOAD);
49
+ });
50
+
51
+ it('sanitizes circle radius output', () => {
52
+ const circle = new Circle({
53
+ radius: payload as unknown as number,
54
+ });
55
+
56
+ const svg = circle.toSVG();
57
+ expect(svg).not.toContain('<script>');
58
+ expect(svg).not.toContain(ONCLICK_PAYLOAD);
59
+ });
60
+
61
+ it('sanitizes ellipse radii output', () => {
62
+ const ellipse = new Ellipse({
63
+ rx: payload as unknown as number,
64
+ ry: payload as unknown as number,
65
+ });
66
+
67
+ const svg = ellipse.toSVG();
68
+ expect(svg).not.toContain('<script>');
69
+ expect(svg).not.toContain(ONCLICK_PAYLOAD);
70
+ });
71
+
72
+ it('sanitizes text content and font attributes', () => {
73
+ const text = new FabricText('<script>alert(1)</script>', {
74
+ fontFamily: `Times New Roman ${payload}`,
75
+ });
76
+
77
+ const svg = text.toSVG();
78
+ expect(svg).not.toContain('<script>');
79
+ expect(svg).not.toContain(ONCLICK_PAYLOAD);
80
+ });
81
+
82
+ it('sanitizes text style overrides', () => {
83
+ const text = new FabricText('x', {
84
+ styles: {
85
+ 0: {
86
+ 0: {
87
+ fill: `red ${payload}`,
88
+ fontFamily: `Times ${payload}`,
89
+ fontWeight: `bold ${payload}`,
90
+ fontStyle: `italic ${payload}`,
91
+ fontSize: payload as unknown as number,
92
+ },
93
+ },
94
+ },
95
+ });
96
+
97
+ const svg = text.toSVG();
98
+ expect(svg).not.toContain('<script>');
99
+ expect(svg).not.toContain(ONCLICK_PAYLOAD);
100
+ });
101
+
102
+ it('sanitizes image src output', () => {
103
+ const element = new Image(10, 10);
104
+ element.src = `data:image/svg+xml,<svg>${payload}</svg>`;
105
+ const image = new FabricImage(element, { width: 10, height: 10 });
106
+
107
+ const svg = image.toSVG();
108
+ expect(svg).not.toContain('<script>');
109
+ expect(svg).not.toContain(ONCLICK_PAYLOAD);
110
+ });
111
+ },
112
+ );
@@ -4,10 +4,7 @@ import type { LockInteractionProps } from './LockInteractionProps';
4
4
  import type { ObjectProps } from './ObjectProps';
5
5
 
6
6
  export interface FabricObjectProps
7
- extends ObjectProps,
8
- ControlProps,
9
- BorderProps,
10
- LockInteractionProps {
7
+ extends ObjectProps, ControlProps, BorderProps, LockInteractionProps {
11
8
  /**
12
9
  * When `true`, cache does not get updated during scaling. The picture will get blocky if scaled
13
10
  * too much and will be redrawn with correct details at the end of scaling.
@@ -10,9 +10,7 @@ import type {
10
10
  } from './SerializedObjectProps';
11
11
 
12
12
  export interface ObjectProps
13
- extends SerializedObjectProps,
14
- ClipPathProps,
15
- ObjectTransformActionProps {
13
+ extends SerializedObjectProps, ClipPathProps, ObjectTransformActionProps {
16
14
  clipPath?: FabricObject;
17
15
  fill: TFiller | string | null;
18
16
  stroke: TFiller | string | null;
@@ -1,13 +1,10 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { Path } from './Path';
3
3
  import type { TSimpleParsedCommand } from '../util';
4
- import { FabricObject, getFabricDocument, version } from '../../fabric';
4
+ import { FabricObject, getFabricDocument } from '../../fabric';
5
+ import { createReferenceObject } from '../../test/utils';
5
6
 
6
- const REFERENCE_PATH_OBJECT = {
7
- version: version,
8
- type: 'Path',
9
- originX: 'center' as const,
10
- originY: 'center' as const,
7
+ const REFERENCE_PATH_OBJECT = createReferenceObject('Path', {
11
8
  left: 200,
12
9
  top: 200,
13
10
  width: 200,
@@ -15,33 +12,13 @@ const REFERENCE_PATH_OBJECT = {
15
12
  fill: 'red',
16
13
  stroke: 'blue',
17
14
  strokeWidth: 0,
18
- strokeDashArray: null,
19
- strokeLineCap: 'butt' as const,
20
- strokeDashOffset: 0,
21
- strokeLineJoin: 'miter' as const,
22
- strokeMiterLimit: 4,
23
- scaleX: 1,
24
- scaleY: 1,
25
- angle: 0,
26
- flipX: false,
27
- flipY: false,
28
- opacity: 1,
29
15
  path: [
30
16
  ['M', 100, 100],
31
17
  ['L', 300, 100],
32
18
  ['L', 200, 300],
33
19
  ['Z'],
34
20
  ] as TSimpleParsedCommand[],
35
- shadow: null,
36
- visible: true,
37
- backgroundColor: '',
38
- fillRule: 'nonzero' as const,
39
- paintFirst: 'fill' as const,
40
- globalCompositeOperation: 'source-over' as const,
41
- skewX: 0,
42
- skewY: 0,
43
- strokeUniform: false,
44
- };
21
+ });
45
22
 
46
23
  function getPathElement(path: string) {
47
24
  const namespace = 'http://www.w3.org/2000/svg';
@@ -35,8 +35,7 @@ interface UniquePathProps {
35
35
  }
36
36
 
37
37
  export interface SerializedPathProps
38
- extends SerializedObjectProps,
39
- UniquePathProps {}
38
+ extends SerializedObjectProps, UniquePathProps {}
40
39
 
41
40
  export interface PathProps extends FabricObjectProps, UniquePathProps {}
42
41
 
@@ -214,11 +213,10 @@ export class Path<
214
213
  * of the instance
215
214
  */
216
215
  _toSVG() {
217
- const path = joinPath(this.path, config.NUM_FRACTION_DIGITS);
218
216
  return [
219
217
  '<path ',
220
218
  'COMMON_PARTS',
221
- `d="${path}" stroke-linecap="round" />\n`,
219
+ `d="${joinPath(this.path, config.NUM_FRACTION_DIGITS)}" stroke-linecap="round" />\n`,
222
220
  ];
223
221
  }
224
222
 
@@ -1,10 +1,10 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import { Point } from '../Point';
3
3
  import { getFabricDocument } from '../env';
4
- import { VERSION as version } from '../constants';
5
4
  import { Polygon } from './Polygon';
6
5
  import { Polyline } from './Polyline';
7
6
  import { FabricObject } from './Object/FabricObject';
7
+ import { createReferenceObject } from '../../test/utils';
8
8
 
9
9
  function getPoints() {
10
10
  return [
@@ -13,40 +13,13 @@ function getPoints() {
13
13
  ];
14
14
  }
15
15
 
16
- const REFERENCE_OBJECT = {
17
- version: version,
18
- type: 'Polygon',
19
- originX: 'center',
20
- originY: 'center',
16
+ const REFERENCE_OBJECT = createReferenceObject('Polygon', {
21
17
  left: 15,
22
18
  top: 17,
23
19
  width: 10,
24
20
  height: 10,
25
- fill: 'rgb(0,0,0)',
26
- stroke: null,
27
- strokeWidth: 1,
28
- strokeDashArray: null,
29
- strokeLineCap: 'butt',
30
- strokeDashOffset: 0,
31
- strokeLineJoin: 'miter',
32
- strokeMiterLimit: 4,
33
- scaleX: 1,
34
- scaleY: 1,
35
- angle: 0,
36
- flipX: false,
37
- flipY: false,
38
- opacity: 1,
39
21
  points: getPoints(),
40
- shadow: null,
41
- visible: true,
42
- backgroundColor: '',
43
- fillRule: 'nonzero',
44
- paintFirst: 'fill',
45
- globalCompositeOperation: 'source-over',
46
- skewX: 0,
47
- skewY: 0,
48
- strokeUniform: false,
49
- } as const;
22
+ });
50
23
 
51
24
  const REFERENCE_EMPTY_OBJECT = {
52
25
  points: [],
@@ -273,7 +246,7 @@ describe('Polygon', () => {
273
246
  expect(polygon.toSVG, 'toSVG should be a function').toBeTypeOf('function');
274
247
 
275
248
  const EXPECTED_SVG =
276
- '<g transform="matrix(1 0 0 1 15 17)" >\n<polygon style="stroke: rgb(0,0,255); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;" points="-5,-5 5,5 " />\n</g>\n';
249
+ '<g transform="matrix(1 0 0 1 15 17)" >\n<polygon style="stroke: rgb(0,0,255); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;" points="-5,-5 5,5" />\n</g>\n';
277
250
 
278
251
  expect(polygon.toSVG(), 'SVG output should match expected').toBe(
279
252
  EXPECTED_SVG,
@@ -3,9 +3,9 @@ import { Point } from '../Point';
3
3
 
4
4
  import { describe, expect, it } from 'vitest';
5
5
  import { getFabricDocument } from '../env';
6
- import { version } from '../../package.json';
7
6
  import { Polygon } from './Polygon';
8
7
  import { FabricObject } from './Object/FabricObject';
8
+ import { createReferenceObject } from '../../test/utils';
9
9
 
10
10
  const points = [
11
11
  { x: 2, y: 2 },
@@ -13,40 +13,13 @@ const points = [
13
13
  { x: 12, y: 7 },
14
14
  ];
15
15
 
16
- const REFERENCE_OBJECT = {
17
- version: version,
18
- type: 'Polyline',
19
- originX: 'center',
20
- originY: 'center',
16
+ const REFERENCE_OBJECT = createReferenceObject('Polyline', {
21
17
  left: 15,
22
18
  top: 17,
23
19
  width: 10,
24
20
  height: 10,
25
- fill: 'rgb(0,0,0)',
26
- stroke: null,
27
- strokeWidth: 1,
28
- strokeDashArray: null,
29
- strokeLineCap: 'butt',
30
- strokeDashOffset: 0,
31
- strokeLineJoin: 'miter',
32
- strokeMiterLimit: 4,
33
- scaleX: 1,
34
- scaleY: 1,
35
- angle: 0,
36
- flipX: false,
37
- flipY: false,
38
- opacity: 1,
39
21
  points: getPoints(),
40
- shadow: null,
41
- visible: true,
42
- backgroundColor: '',
43
- fillRule: 'nonzero',
44
- paintFirst: 'fill',
45
- globalCompositeOperation: 'source-over',
46
- skewX: 0,
47
- skewY: 0,
48
- strokeUniform: false,
49
- } as const;
22
+ });
50
23
 
51
24
  describe('Polyline', () => {
52
25
  describe('_calcDimensions and pathOffset', () => {
@@ -229,7 +202,7 @@ describe('Polyline', () => {
229
202
  const polyline = new Polygon(getPoints(), { fill: 'red', stroke: 'blue' });
230
203
  expect(polyline.toSVG).toBeTypeOf('function');
231
204
  const EXPECTED_SVG =
232
- '<g transform="matrix(1 0 0 1 15 17)" >\n<polygon style="stroke: rgb(0,0,255); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;" points="-5,-5 5,5 " />\n</g>\n';
205
+ '<g transform="matrix(1 0 0 1 15 17)" >\n<polygon style="stroke: rgb(0,0,255); stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;" points="-5,-5 5,5" />\n</g>\n';
233
206
  expect(polyline.toSVG()).toEqual(EXPECTED_SVG);
234
207
  });
235
208
 
@@ -25,6 +25,7 @@ import {
25
25
  TOP,
26
26
  } from '../constants';
27
27
  import type { CSSRules } from '../parser/typedefs';
28
+ import { escapeXml } from '../util/lang_string';
28
29
 
29
30
  export const polylineDefaultValues: Partial<TClassProperties<Polyline>> = {
30
31
  /**
@@ -323,27 +324,25 @@ export class Polyline<
323
324
  * of the instance
324
325
  */
325
326
  _toSVG() {
326
- const points = [],
327
- diffX = this.pathOffset.x,
327
+ const diffX = this.pathOffset.x,
328
328
  diffY = this.pathOffset.y,
329
329
  NUM_FRACTION_DIGITS = config.NUM_FRACTION_DIGITS;
330
330
 
331
- for (let i = 0, len = this.points.length; i < len; i++) {
332
- points.push(
333
- toFixed(this.points[i].x - diffX, NUM_FRACTION_DIGITS),
334
- ',',
335
- toFixed(this.points[i].y - diffY, NUM_FRACTION_DIGITS),
336
- ' ',
337
- );
338
- }
331
+ const points = this.points
332
+ .map(
333
+ ({ x, y }) =>
334
+ `${toFixed(x - diffX, NUM_FRACTION_DIGITS)},${toFixed(y - diffY, NUM_FRACTION_DIGITS)}`,
335
+ )
336
+ .join(' ');
337
+
339
338
  return [
340
339
  `<${
341
- (this.constructor as typeof Polyline).type.toLowerCase() as
340
+ escapeXml((this.constructor as typeof Polyline).type).toLowerCase() as
342
341
  | 'polyline'
343
342
  | 'polygon'
344
343
  } `,
345
344
  'COMMON_PARTS',
346
- `points="${points.join('')}" />\n`,
345
+ `points="${points}" />\n`,
347
346
  ];
348
347
  }
349
348
 
@@ -7,42 +7,12 @@ import { Pattern } from '../Pattern';
7
7
  // will require some kind of handling here
8
8
  import { getEnv } from '../env';
9
9
  import { loadSVGFromString } from '../parser/loadSVGFromString';
10
+ import { createReferenceObject } from '../../test/utils';
10
11
 
11
- const REFERENCE_RECT = {
12
- version,
13
- type: 'Rect',
14
- originX: 'center',
15
- originY: 'center',
16
- left: 0,
17
- top: 0,
18
- width: 0,
19
- height: 0,
20
- fill: 'rgb(0,0,0)',
21
- stroke: null,
22
- strokeWidth: 1,
23
- strokeDashArray: null,
24
- strokeLineCap: 'butt',
25
- strokeDashOffset: 0,
26
- strokeLineJoin: 'miter',
27
- strokeMiterLimit: 4,
28
- scaleX: 1,
29
- scaleY: 1,
30
- angle: 0,
31
- flipX: false,
32
- flipY: false,
33
- opacity: 1,
34
- shadow: null,
35
- visible: true,
36
- backgroundColor: '',
37
- fillRule: 'nonzero',
38
- paintFirst: 'fill',
39
- globalCompositeOperation: 'source-over',
12
+ const REFERENCE_RECT = createReferenceObject('Rect', {
40
13
  rx: 0,
41
14
  ry: 0,
42
- skewX: 0,
43
- skewY: 0,
44
- strokeUniform: false,
45
- };
15
+ });
46
16
 
47
17
  describe('Rect', () => {
48
18
  it('constructor', function () {
@@ -273,4 +243,26 @@ describe('Rect', () => {
273
243
  expect(rectObject.paintFirst).toBe('stroke');
274
244
  expect(rectSvg).toContain('paint-order="stroke"');
275
245
  });
246
+
247
+ describe('svg attribute injection', () => {
248
+ it('properties are properly escaped', () => {
249
+ const rect = new Rect({
250
+ id: 'asd"><script>alert(1)</script>',
251
+ width: 100,
252
+ height: 100,
253
+ });
254
+ const svg = rect.toSVG();
255
+ expect(svg).toContain(
256
+ `id="asd&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;"`,
257
+ );
258
+ });
259
+ it('polyglot test', () => {
260
+ const polyglotPayload =
261
+ 'jaVasCript:/*-/*`/*\\`/*\'/*"/**/(/* */oNcliCk=alert() )';
262
+ const rect = new Rect({ id: polyglotPayload, width: 100, height: 100 });
263
+ const svg = rect.toSVG();
264
+ // Should escape all special characters
265
+ expect(svg).not.toContain(polyglotPayload);
266
+ });
267
+ });
276
268
  });
@@ -7,6 +7,7 @@ import { FabricObject, cacheProperties } from './Object/FabricObject';
7
7
  import type { FabricObjectProps, SerializedObjectProps } from './Object/types';
8
8
  import type { ObjectEvents } from '../EventTypeDefs';
9
9
  import type { CSSRules } from '../parser/typedefs';
10
+ import { escapeXml } from '../util/lang_string';
10
11
 
11
12
  export const rectDefaultValues: Partial<TClassProperties<Rect>> = {
12
13
  rx: 0,
@@ -19,18 +20,17 @@ interface UniqueRectProps {
19
20
  }
20
21
 
21
22
  export interface SerializedRectProps
22
- extends SerializedObjectProps,
23
- UniqueRectProps {}
23
+ extends SerializedObjectProps, UniqueRectProps {}
24
24
 
25
25
  export interface RectProps extends FabricObjectProps, UniqueRectProps {}
26
26
 
27
27
  const RECT_PROPS = ['rx', 'ry'] as const;
28
28
 
29
29
  export class Rect<
30
- Props extends TOptions<RectProps> = Partial<RectProps>,
31
- SProps extends SerializedRectProps = SerializedRectProps,
32
- EventSpec extends ObjectEvents = ObjectEvents,
33
- >
30
+ Props extends TOptions<RectProps> = Partial<RectProps>,
31
+ SProps extends SerializedRectProps = SerializedRectProps,
32
+ EventSpec extends ObjectEvents = ObjectEvents,
33
+ >
34
34
  extends FabricObject<Props, SProps, EventSpec>
35
35
  implements RectProps
36
36
  {
@@ -164,7 +164,7 @@ export class Rect<
164
164
  'COMMON_PARTS',
165
165
  `x="${-width / 2}" y="${
166
166
  -height / 2
167
- }" rx="${rx}" ry="${ry}" width="${width}" height="${height}" />\n`,
167
+ }" rx="${escapeXml(rx)}" ry="${escapeXml(ry)}" width="${escapeXml(width)}" height="${escapeXml(height)}" />\n`,
168
168
  ];
169
169
  }
170
170
 
@@ -10,38 +10,15 @@ import {
10
10
  getFabricDocument,
11
11
  IText,
12
12
  Textbox,
13
- version,
14
13
  } from '../../../fabric';
15
14
  import { toFixed } from '../../util';
15
+ import { createReferenceObject } from '../../../test/utils';
16
16
 
17
17
  const CHAR_WIDTH = 20;
18
18
 
19
- const REFERENCE_TEXT_OBJECT = {
20
- version: version,
21
- type: 'Text',
22
- originX: 'center',
23
- originY: 'center',
24
- left: 0,
25
- top: 0,
19
+ const REFERENCE_TEXT_OBJECT = createReferenceObject('Text', {
26
20
  width: CHAR_WIDTH,
27
21
  height: 45.2,
28
- fill: 'rgb(0,0,0)',
29
- stroke: null,
30
- strokeWidth: 1,
31
- strokeDashArray: null,
32
- strokeLineCap: 'butt',
33
- strokeDashOffset: 0,
34
- strokeLineJoin: 'miter',
35
- strokeMiterLimit: 4,
36
- scaleX: 1,
37
- scaleY: 1,
38
- angle: 0,
39
- flipX: false,
40
- flipY: false,
41
- opacity: 1,
42
- shadow: null,
43
- visible: true,
44
- backgroundColor: '',
45
22
  text: 'x',
46
23
  fontSize: 40,
47
24
  fontWeight: 'normal',
@@ -53,21 +30,15 @@ const REFERENCE_TEXT_OBJECT = {
53
30
  linethrough: false,
54
31
  textAlign: 'left',
55
32
  textBackgroundColor: '',
56
- fillRule: 'nonzero',
57
- paintFirst: 'fill',
58
- globalCompositeOperation: 'source-over',
59
- skewX: 0,
60
- skewY: 0,
61
33
  charSpacing: 0,
62
34
  styles: [],
63
35
  path: undefined,
64
- strokeUniform: false,
65
36
  direction: 'ltr',
66
37
  pathStartOffset: 0,
67
38
  pathSide: 'left',
68
39
  pathAlign: 'baseline',
69
40
  textDecorationThickness: 66.667,
70
- };
41
+ });
71
42
 
72
43
  function createTextObject() {
73
44
  return new FabricText('x');
@@ -125,8 +125,7 @@ interface UniqueTextProps {
125
125
  }
126
126
 
127
127
  export interface SerializedTextProps
128
- extends SerializedObjectProps,
129
- UniqueTextProps {
128
+ extends SerializedObjectProps, UniqueTextProps {
130
129
  styles: TextStyleArray | TextStyle;
131
130
  }
132
131
 
@@ -139,10 +138,10 @@ export interface TextProps extends FabricObjectProps, UniqueTextProps {
139
138
  * @see {@link http://fabric5.fabricjs.com/fabric-intro-part-2#text}
140
139
  */
141
140
  export class FabricText<
142
- Props extends TOptions<TextProps> = Partial<TextProps>,
143
- SProps extends SerializedTextProps = SerializedTextProps,
144
- EventSpec extends ObjectEvents = ObjectEvents,
145
- >
141
+ Props extends TOptions<TextProps> = Partial<TextProps>,
142
+ SProps extends SerializedTextProps = SerializedTextProps,
143
+ EventSpec extends ObjectEvents = ObjectEvents,
144
+ >
146
145
  extends StyledText<Props, SProps, EventSpec>
147
146
  implements UniqueTextProps
148
147
  {
@@ -77,12 +77,12 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin {
77
77
  return [
78
78
  textBgRects.join(''),
79
79
  '\t\t<text xml:space="preserve" ',
80
- `font-family="${this.fontFamily.replace(dblQuoteRegex, "'")}" `,
81
- `font-size="${this.fontSize}" `,
82
- this.fontStyle ? `font-style="${this.fontStyle}" ` : '',
83
- this.fontWeight ? `font-weight="${this.fontWeight}" ` : '',
80
+ `font-family="${escapeXml(this.fontFamily.replace(dblQuoteRegex, "'"))}" `,
81
+ `font-size="${escapeXml(this.fontSize)}" `,
82
+ this.fontStyle ? `font-style="${escapeXml(this.fontStyle)}" ` : '',
83
+ this.fontWeight ? `font-weight="${escapeXml(this.fontWeight)}" ` : '',
84
84
  textDecoration ? `text-decoration="${textDecoration}" ` : '',
85
- this.direction === 'rtl' ? `direction="${this.direction}" ` : '',
85
+ this.direction === 'rtl' ? `direction="rtl" ` : '',
86
86
  'style="',
87
87
  this.getSvgStyles(noShadow),
88
88
  '"',
@@ -112,7 +112,7 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin {
112
112
  // bounding-box background
113
113
  this.backgroundColor &&
114
114
  textBgRects.push(
115
- ...createSVGInlineRect(
115
+ createSVGInlineRect(
116
116
  this.backgroundColor,
117
117
  -this.width / 2,
118
118
  -this.height / 2,
@@ -270,7 +270,7 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin {
270
270
  if (currentColor !== lastColor) {
271
271
  lastColor &&
272
272
  textBgRects.push(
273
- ...createSVGInlineRect(
273
+ createSVGInlineRect(
274
274
  lastColor,
275
275
  leftOffset + boxStart,
276
276
  textTopOffset,
@@ -287,7 +287,7 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin {
287
287
  }
288
288
  currentColor &&
289
289
  textBgRects.push(
290
- ...createSVGInlineRect(
290
+ createSVGInlineRect(
291
291
  lastColor,
292
292
  leftOffset + boxStart,
293
293
  textTopOffset,
@@ -339,17 +339,17 @@ export class TextSVGExportMixin extends FabricObjectSVGExportMixin {
339
339
  const thickness = textDecorationThickness || this.textDecorationThickness;
340
340
  return [
341
341
  stroke ? colorPropToSVG(STROKE, stroke) : '',
342
- strokeWidth ? `stroke-width: ${strokeWidth}; ` : '',
342
+ strokeWidth ? `stroke-width: ${escapeXml(strokeWidth)}; ` : '',
343
343
  fontFamily
344
344
  ? `font-family: ${
345
345
  !fontFamily.includes("'") && !fontFamily.includes('"')
346
- ? `'${fontFamily}'`
347
- : fontFamily
346
+ ? `'${escapeXml(fontFamily)}'`
347
+ : escapeXml(fontFamily)
348
348
  }; `
349
349
  : '',
350
- fontSize ? `font-size: ${fontSize}px; ` : '',
351
- fontStyle ? `font-style: ${fontStyle}; ` : '',
352
- fontWeight ? `font-weight: ${fontWeight}; ` : '',
350
+ fontSize ? `font-size: ${escapeXml(fontSize)}px; ` : '',
351
+ fontStyle ? `font-style: ${escapeXml(fontStyle)}; ` : '',
352
+ fontWeight ? `font-weight: ${escapeXml(fontWeight)}; ` : '',
353
353
  textDecoration
354
354
  ? `text-decoration: ${textDecoration}; text-decoration-thickness: ${toFixed((thickness * this.getObjectScaling().y) / 10, config.NUM_FRACTION_DIGITS)}%; `
355
355
  : '',
@@ -196,7 +196,7 @@ exports[`FabricText > toObject 1`] = `
196
196
  "type": "Text",
197
197
  "underline": false,
198
198
  "visible": true,
199
- "width": 60,
199
+ "width": 59.98,
200
200
  }
201
201
  `;
202
202