html2canvas-pro 2.1.0 → 2.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 (266) hide show
  1. package/dist/html2canvas-pro.esm.js +10226 -10526
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +10869 -11171
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +8 -8
  6. package/dist/lib/config.js +0 -22
  7. package/dist/lib/core/cache-storage.js +3 -40
  8. package/dist/lib/core/constants.js +25 -0
  9. package/dist/lib/core/context.js +1 -0
  10. package/dist/lib/core/features.js +3 -2
  11. package/dist/lib/core/validator.js +3 -3
  12. package/dist/lib/css/grouped/background-styles.js +36 -0
  13. package/dist/lib/css/grouped/border-styles.js +75 -0
  14. package/dist/lib/css/grouped/font-styles.js +93 -0
  15. package/dist/lib/css/grouped/layout-styles.js +127 -0
  16. package/dist/lib/css/index.js +74 -46
  17. package/dist/lib/css/layout/text.js +7 -6
  18. package/dist/lib/css/property-descriptors/background-blend-mode.js +41 -0
  19. package/dist/lib/css/property-descriptors/border-image-repeat.js +42 -0
  20. package/dist/lib/css/property-descriptors/border-image-slice.js +45 -0
  21. package/dist/lib/css/property-descriptors/border-image-source.js +21 -0
  22. package/dist/lib/css/property-descriptors/border-radius.js +1 -1
  23. package/dist/lib/css/property-descriptors/box-decoration-break.js +18 -0
  24. package/dist/lib/css/property-descriptors/counter-increment.js +17 -12
  25. package/dist/lib/css/property-descriptors/counter-reset.js +4 -12
  26. package/dist/lib/css/property-descriptors/filter.js +76 -0
  27. package/dist/lib/css/property-descriptors/font-variant-ligatures.js +34 -0
  28. package/dist/lib/css/property-descriptors/object-fit.js +1 -1
  29. package/dist/lib/css/property-descriptors/object-position.js +42 -0
  30. package/dist/lib/css/property-descriptors/visibility.js +1 -1
  31. package/dist/lib/css/property-descriptors/zoom.js +18 -0
  32. package/dist/lib/css/syntax/parser.js +0 -1
  33. package/dist/lib/css/types/color.js +5 -1
  34. package/dist/lib/css/types/functions/repeating-linear-gradient.js +9 -0
  35. package/dist/lib/css/types/image.js +12 -2
  36. package/dist/lib/css/types/length-percentage.js +6 -2
  37. package/dist/lib/css/types/safe-eval.js +80 -0
  38. package/dist/lib/dom/document-cloner.js +23 -163
  39. package/dist/lib/dom/slot-cloner.js +176 -0
  40. package/dist/lib/index.js +1 -17
  41. package/dist/lib/render/canvas/background-renderer.js +169 -30
  42. package/dist/lib/render/canvas/border-image-renderer.js +153 -0
  43. package/dist/lib/render/canvas/canvas-renderer.js +39 -190
  44. package/dist/lib/render/canvas/content-renderer.js +202 -0
  45. package/dist/lib/render/canvas/effects-renderer.js +3 -0
  46. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  47. package/dist/lib/render/canvas/text/text-decoration-renderer.js +99 -0
  48. package/dist/lib/render/canvas/text-renderer.js +100 -224
  49. package/dist/lib/render/effects.js +38 -3
  50. package/dist/lib/render/object-fit.js +19 -15
  51. package/dist/lib/render/stacking-context.js +11 -0
  52. package/dist/types/config.d.ts +0 -10
  53. package/dist/types/core/cache-storage.d.ts +0 -24
  54. package/dist/types/core/constants.d.ts +22 -0
  55. package/dist/types/core/context.d.ts +3 -0
  56. package/dist/types/core/performance-monitor.d.ts +4 -4
  57. package/dist/types/core/validator.d.ts +6 -8
  58. package/dist/types/css/grouped/background-styles.d.ts +16 -0
  59. package/dist/types/css/grouped/border-styles.d.ts +31 -0
  60. package/dist/types/css/grouped/font-styles.d.ts +35 -0
  61. package/dist/types/css/grouped/layout-styles.d.ts +46 -0
  62. package/dist/types/css/index.d.ts +30 -0
  63. package/dist/types/css/property-descriptors/background-blend-mode.d.ts +23 -0
  64. package/dist/types/css/property-descriptors/border-image-repeat.d.ts +12 -0
  65. package/dist/types/css/property-descriptors/border-image-slice.d.ts +10 -0
  66. package/dist/types/css/property-descriptors/border-image-source.d.ts +4 -0
  67. package/dist/types/css/property-descriptors/box-decoration-break.d.ts +6 -0
  68. package/dist/types/css/property-descriptors/counter-increment.d.ts +3 -0
  69. package/dist/types/css/property-descriptors/filter.d.ts +3 -0
  70. package/dist/types/css/property-descriptors/font-variant-ligatures.d.ts +14 -0
  71. package/dist/types/css/property-descriptors/object-position.d.ts +4 -0
  72. package/dist/types/css/property-descriptors/zoom.d.ts +3 -0
  73. package/dist/types/css/types/functions/repeating-linear-gradient.d.ts +4 -0
  74. package/dist/types/css/types/image.d.ts +4 -2
  75. package/dist/types/css/types/safe-eval.d.ts +8 -0
  76. package/dist/types/dom/document-cloner.d.ts +3 -44
  77. package/dist/types/dom/slot-cloner.d.ts +66 -0
  78. package/dist/types/index.d.ts +3 -7
  79. package/dist/types/options.d.ts +11 -0
  80. package/dist/types/render/canvas/background-renderer.d.ts +23 -0
  81. package/dist/types/render/canvas/border-image-renderer.d.ts +18 -0
  82. package/dist/types/render/canvas/canvas-renderer.d.ts +1 -0
  83. package/dist/types/render/canvas/content-renderer.d.ts +44 -0
  84. package/dist/types/render/canvas/text/text-decoration-renderer.d.ts +18 -0
  85. package/dist/types/render/canvas/text-renderer.d.ts +12 -1
  86. package/dist/types/render/effects.d.ts +12 -2
  87. package/dist/types/render/object-fit.d.ts +2 -1
  88. package/dist/types/render/renderer-interface.d.ts +11 -9
  89. package/package.json +7 -20
  90. package/dist/lib/dom/replaced-elements/pseudo-elements.js +0 -0
  91. package/dist/lib/invariant.js +0 -9
  92. package/dist/types/dom/replaced-elements/pseudo-elements.d.ts +0 -0
  93. package/dist/types/invariant.d.ts +0 -1
  94. package/src/__tests__/index.ts +0 -99
  95. package/src/config.ts +0 -107
  96. package/src/core/__mocks__/cache-storage.ts +0 -1
  97. package/src/core/__mocks__/context.ts +0 -19
  98. package/src/core/__mocks__/features.ts +0 -8
  99. package/src/core/__mocks__/logger.ts +0 -17
  100. package/src/core/__tests__/cache-storage.test.ts +0 -205
  101. package/src/core/__tests__/cache-storage.ts +0 -278
  102. package/src/core/__tests__/logger.ts +0 -29
  103. package/src/core/__tests__/validator.ts +0 -359
  104. package/src/core/bitwise.ts +0 -1
  105. package/src/core/cache-storage.ts +0 -315
  106. package/src/core/context.ts +0 -31
  107. package/src/core/debugger.ts +0 -32
  108. package/src/core/features.ts +0 -222
  109. package/src/core/logger.ts +0 -64
  110. package/src/core/origin-checker.ts +0 -57
  111. package/src/core/performance-monitor.ts +0 -241
  112. package/src/core/render-element.ts +0 -272
  113. package/src/core/util.ts +0 -1
  114. package/src/core/validator.ts +0 -593
  115. package/src/css/index.ts +0 -427
  116. package/src/css/layout/__mocks__/bounds.ts +0 -6
  117. package/src/css/layout/bounds.ts +0 -79
  118. package/src/css/layout/text.ts +0 -161
  119. package/src/css/property-descriptor.ts +0 -49
  120. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  121. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  122. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  123. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  124. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  125. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  126. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  127. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  128. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  129. package/src/css/property-descriptors/background-clip.ts +0 -30
  130. package/src/css/property-descriptors/background-color.ts +0 -9
  131. package/src/css/property-descriptors/background-image.ts +0 -27
  132. package/src/css/property-descriptors/background-origin.ts +0 -31
  133. package/src/css/property-descriptors/background-position.ts +0 -38
  134. package/src/css/property-descriptors/background-repeat.ts +0 -44
  135. package/src/css/property-descriptors/background-size.ts +0 -27
  136. package/src/css/property-descriptors/border-color.ts +0 -13
  137. package/src/css/property-descriptors/border-radius.ts +0 -19
  138. package/src/css/property-descriptors/border-style.ts +0 -34
  139. package/src/css/property-descriptors/border-width.ts +0 -20
  140. package/src/css/property-descriptors/box-shadow.ts +0 -60
  141. package/src/css/property-descriptors/clip-path.ts +0 -271
  142. package/src/css/property-descriptors/color.ts +0 -9
  143. package/src/css/property-descriptors/content.ts +0 -26
  144. package/src/css/property-descriptors/counter-increment.ts +0 -43
  145. package/src/css/property-descriptors/counter-reset.ts +0 -36
  146. package/src/css/property-descriptors/direction.ts +0 -23
  147. package/src/css/property-descriptors/display.ts +0 -117
  148. package/src/css/property-descriptors/duration.ts +0 -14
  149. package/src/css/property-descriptors/float.ts +0 -29
  150. package/src/css/property-descriptors/font-family.ts +0 -38
  151. package/src/css/property-descriptors/font-size.ts +0 -9
  152. package/src/css/property-descriptors/font-style.ts +0 -25
  153. package/src/css/property-descriptors/font-variant.ts +0 -12
  154. package/src/css/property-descriptors/font-weight.ts +0 -26
  155. package/src/css/property-descriptors/image-rendering.ts +0 -33
  156. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  157. package/src/css/property-descriptors/line-break.ts +0 -22
  158. package/src/css/property-descriptors/line-height.ts +0 -22
  159. package/src/css/property-descriptors/list-style-image.ts +0 -19
  160. package/src/css/property-descriptors/list-style-position.ts +0 -22
  161. package/src/css/property-descriptors/list-style-type.ts +0 -179
  162. package/src/css/property-descriptors/margin.ts +0 -13
  163. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  164. package/src/css/property-descriptors/object-fit.ts +0 -39
  165. package/src/css/property-descriptors/opacity.ts +0 -15
  166. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  167. package/src/css/property-descriptors/overflow.ts +0 -34
  168. package/src/css/property-descriptors/padding.ts +0 -14
  169. package/src/css/property-descriptors/paint-order.ts +0 -42
  170. package/src/css/property-descriptors/position.ts +0 -30
  171. package/src/css/property-descriptors/quotes.ts +0 -57
  172. package/src/css/property-descriptors/rotate.ts +0 -34
  173. package/src/css/property-descriptors/text-align.ts +0 -26
  174. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  175. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  176. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  177. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  178. package/src/css/property-descriptors/text-overflow.ts +0 -23
  179. package/src/css/property-descriptors/text-shadow.ts +0 -52
  180. package/src/css/property-descriptors/text-transform.ts +0 -27
  181. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  182. package/src/css/property-descriptors/transform-origin.ts +0 -29
  183. package/src/css/property-descriptors/transform.ts +0 -74
  184. package/src/css/property-descriptors/visibility.ts +0 -25
  185. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  186. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  187. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  188. package/src/css/property-descriptors/word-break.ts +0 -25
  189. package/src/css/property-descriptors/writing-mode.ts +0 -37
  190. package/src/css/property-descriptors/z-index.ts +0 -27
  191. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  192. package/src/css/syntax/parser.ts +0 -188
  193. package/src/css/syntax/tokenizer.ts +0 -822
  194. package/src/css/type-descriptor.ts +0 -7
  195. package/src/css/types/__tests__/color-tests.ts +0 -147
  196. package/src/css/types/__tests__/image-tests.ts +0 -239
  197. package/src/css/types/angle.ts +0 -86
  198. package/src/css/types/color-math.ts +0 -22
  199. package/src/css/types/color-spaces/a98.ts +0 -86
  200. package/src/css/types/color-spaces/p3.ts +0 -92
  201. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  202. package/src/css/types/color-spaces/rec2020.ts +0 -90
  203. package/src/css/types/color-spaces/srgb.ts +0 -87
  204. package/src/css/types/color-utilities.ts +0 -452
  205. package/src/css/types/color.ts +0 -485
  206. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  207. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  208. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  209. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  210. package/src/css/types/functions/counter.ts +0 -511
  211. package/src/css/types/functions/gradient.ts +0 -206
  212. package/src/css/types/functions/linear-gradient.ts +0 -28
  213. package/src/css/types/functions/radial-gradient.ts +0 -101
  214. package/src/css/types/image.ts +0 -120
  215. package/src/css/types/index.ts +0 -1
  216. package/src/css/types/length-percentage.ts +0 -137
  217. package/src/css/types/length.ts +0 -7
  218. package/src/css/types/time.ts +0 -20
  219. package/src/dom/__mocks__/document-cloner.ts +0 -22
  220. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  221. package/src/dom/__tests__/element-container.test.ts +0 -129
  222. package/src/dom/document-cloner.ts +0 -929
  223. package/src/dom/dom-normalizer.ts +0 -133
  224. package/src/dom/element-container.ts +0 -75
  225. package/src/dom/elements/li-element-container.ts +0 -10
  226. package/src/dom/elements/ol-element-container.ts +0 -12
  227. package/src/dom/elements/select-element-container.ts +0 -10
  228. package/src/dom/elements/textarea-element-container.ts +0 -9
  229. package/src/dom/node-parser.ts +0 -177
  230. package/src/dom/node-type-guards.ts +0 -70
  231. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  232. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  233. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  234. package/src/dom/replaced-elements/index.ts +0 -5
  235. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  236. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  237. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  238. package/src/dom/text-container.ts +0 -42
  239. package/src/global.d.ts +0 -19
  240. package/src/index.ts +0 -82
  241. package/src/invariant.ts +0 -5
  242. package/src/options.ts +0 -55
  243. package/src/render/__tests__/object-fit.test.ts +0 -85
  244. package/src/render/background.ts +0 -298
  245. package/src/render/bezier-curve.ts +0 -47
  246. package/src/render/border.ts +0 -165
  247. package/src/render/bound-curves.ts +0 -388
  248. package/src/render/box-sizing.ts +0 -31
  249. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  250. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  251. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  252. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  253. package/src/render/canvas/background-renderer.ts +0 -271
  254. package/src/render/canvas/border-renderer.ts +0 -224
  255. package/src/render/canvas/canvas-path.ts +0 -31
  256. package/src/render/canvas/canvas-renderer.ts +0 -641
  257. package/src/render/canvas/effects-renderer.ts +0 -130
  258. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  259. package/src/render/canvas/text-renderer.ts +0 -700
  260. package/src/render/effects.ts +0 -75
  261. package/src/render/font-metrics.ts +0 -72
  262. package/src/render/object-fit.ts +0 -100
  263. package/src/render/path.ts +0 -37
  264. package/src/render/renderer-interface.ts +0 -28
  265. package/src/render/stacking-context.ts +0 -386
  266. package/src/render/vector.ts +0 -19
@@ -1,359 +0,0 @@
1
- import { strictEqual } from 'assert';
2
- import { createDefaultValidator, createStrictValidator } from '../validator';
3
-
4
- describe('Validator', () => {
5
- describe('URL validation', () => {
6
- const validator = createDefaultValidator();
7
-
8
- it('should accept valid HTTP URLs', () => {
9
- const result = validator.validateUrl('http://example.com/test.jpg', 'image');
10
- strictEqual(result.valid, true);
11
- });
12
-
13
- it('should accept valid HTTPS URLs', () => {
14
- const result = validator.validateUrl('https://example.com/test.jpg', 'image');
15
- strictEqual(result.valid, true);
16
- });
17
-
18
- it('should accept data URLs by default', () => {
19
- const result = validator.validateUrl('data:image/png;base64,iVBORw0KGgo=', 'image');
20
- strictEqual(result.valid, true);
21
- });
22
-
23
- it('should accept blob URLs', () => {
24
- const result = validator.validateUrl('blob:http://example.com/uuid', 'image');
25
- strictEqual(result.valid, true);
26
- });
27
-
28
- it('should reject invalid protocols', () => {
29
- const result = validator.validateUrl('ftp://example.com/test.jpg', 'image');
30
- strictEqual(result.valid, false);
31
- strictEqual(result.error?.includes('Protocol'), true);
32
- });
33
-
34
- it('should reject file:// URLs', () => {
35
- const result = validator.validateUrl('file:///etc/passwd', 'image');
36
- strictEqual(result.valid, false);
37
- });
38
-
39
- it('should reject javascript: URLs', () => {
40
- const result = validator.validateUrl('javascript:alert(1)', 'general');
41
- strictEqual(result.valid, false);
42
- });
43
-
44
- it('should reject empty URLs', () => {
45
- const result = validator.validateUrl('', 'image');
46
- strictEqual(result.valid, false);
47
- });
48
-
49
- it('should reject non-string URLs', () => {
50
- const result = validator.validateUrl(null as any, 'image');
51
- strictEqual(result.valid, false);
52
- });
53
-
54
- it('should reject malformed URLs', () => {
55
- const result = validator.validateUrl('not a url', 'image');
56
- strictEqual(result.valid, false);
57
- strictEqual(result.error?.includes('Invalid URL'), true);
58
- });
59
- });
60
-
61
- describe('Proxy URL validation (SSRF prevention)', () => {
62
- it('should reject localhost for proxy URLs', () => {
63
- const validator = createDefaultValidator();
64
- const result = validator.validateUrl('http://localhost:8080/proxy', 'proxy');
65
- strictEqual(result.valid, false);
66
- strictEqual(result.error?.includes('Localhost'), true);
67
- });
68
-
69
- it('should reject 127.0.0.1 for proxy URLs', () => {
70
- const validator = createDefaultValidator();
71
- const result = validator.validateUrl('http://127.0.0.1/proxy', 'proxy');
72
- strictEqual(result.valid, false);
73
- });
74
-
75
- it('should reject ::1 for proxy URLs', () => {
76
- const validator = createDefaultValidator();
77
- const result = validator.validateUrl('http://[::1]/proxy', 'proxy');
78
- strictEqual(result.valid, false);
79
- });
80
-
81
- it('should reject private IP ranges (10.x.x.x)', () => {
82
- const validator = createDefaultValidator();
83
- const result = validator.validateUrl('http://10.0.0.1/proxy', 'proxy');
84
- strictEqual(result.valid, false);
85
- strictEqual(result.error?.includes('Private IP'), true);
86
- });
87
-
88
- it('should reject private IP ranges (172.16-31.x.x)', () => {
89
- const validator = createDefaultValidator();
90
- const results = [
91
- validator.validateUrl('http://172.16.0.1/proxy', 'proxy'),
92
- validator.validateUrl('http://172.20.0.1/proxy', 'proxy'),
93
- validator.validateUrl('http://172.31.255.254/proxy', 'proxy')
94
- ];
95
- results.forEach((result) => strictEqual(result.valid, false));
96
- });
97
-
98
- it('should reject private IP ranges (192.168.x.x)', () => {
99
- const validator = createDefaultValidator();
100
- const result = validator.validateUrl('http://192.168.1.1/proxy', 'proxy');
101
- strictEqual(result.valid, false);
102
- });
103
-
104
- it('should reject link-local addresses', () => {
105
- const validator = createDefaultValidator();
106
- const result = validator.validateUrl('http://169.254.1.1/proxy', 'proxy');
107
- strictEqual(result.valid, false);
108
- });
109
-
110
- it('should accept public IPs for proxy URLs', () => {
111
- const validator = createDefaultValidator();
112
- const result = validator.validateUrl('https://8.8.8.8/proxy', 'proxy');
113
- strictEqual(result.valid, true);
114
- });
115
-
116
- it('should enforce proxy domain whitelist', () => {
117
- const validator = createStrictValidator(['example.com', 'trusted.org']);
118
-
119
- const allowed = validator.validateUrl('https://example.com/proxy', 'proxy');
120
- strictEqual(allowed.valid, true);
121
-
122
- const notAllowed = validator.validateUrl('https://evil.com/proxy', 'proxy');
123
- strictEqual(notAllowed.valid, false);
124
- strictEqual(notAllowed.error?.includes('not in the allowed list'), true);
125
- });
126
-
127
- it('should allow subdomains of whitelisted domains', () => {
128
- const validator = createStrictValidator(['example.com']);
129
- const result = validator.validateUrl('https://api.example.com/proxy', 'proxy');
130
- strictEqual(result.valid, true);
131
- });
132
- });
133
-
134
- describe('CSP nonce validation', () => {
135
- const validator = createDefaultValidator();
136
-
137
- it('should accept valid nonce', () => {
138
- const result = validator.validateCspNonce('ABC123def456GHI789jkl');
139
- strictEqual(result.valid, true);
140
- });
141
-
142
- it('should reject empty nonce', () => {
143
- const result = validator.validateCspNonce('');
144
- strictEqual(result.valid, false);
145
- });
146
-
147
- it('should reject non-string nonce', () => {
148
- const result = validator.validateCspNonce(123 as any);
149
- strictEqual(result.valid, false);
150
- });
151
-
152
- it('should reject too short nonce', () => {
153
- const result = validator.validateCspNonce('short');
154
- strictEqual(result.valid, false);
155
- strictEqual(result.error?.includes('too short'), true);
156
- });
157
-
158
- it('should reject nonce with invalid characters', () => {
159
- const result = validator.validateCspNonce('ABC<script>alert(1)</script>');
160
- strictEqual(result.valid, false);
161
- strictEqual(result.error?.includes('invalid characters'), true);
162
- });
163
-
164
- it('should accept base64-like nonces', () => {
165
- const result = validator.validateCspNonce('AbCdEfGhIjKlMnOpQrStUvWxYz0123456789+/=');
166
- strictEqual(result.valid, true);
167
- });
168
- });
169
-
170
- describe('Image timeout validation', () => {
171
- const validator = createDefaultValidator();
172
-
173
- it('should accept valid timeout', () => {
174
- const result = validator.validateImageTimeout(15000);
175
- strictEqual(result.valid, true);
176
- });
177
-
178
- it('should reject negative timeout', () => {
179
- const result = validator.validateImageTimeout(-1000);
180
- strictEqual(result.valid, false);
181
- });
182
-
183
- it('should reject non-number timeout', () => {
184
- const result = validator.validateImageTimeout('15000' as any);
185
- strictEqual(result.valid, false);
186
- });
187
-
188
- it('should reject NaN timeout', () => {
189
- const result = validator.validateImageTimeout(NaN);
190
- strictEqual(result.valid, false);
191
- });
192
-
193
- it('should enforce maximum timeout', () => {
194
- const strictValidator = createStrictValidator([]);
195
- const result = strictValidator.validateImageTimeout(120000); // 2 minutes
196
- strictEqual(result.valid, false);
197
- strictEqual(result.error?.includes('exceeds maximum'), true);
198
- });
199
-
200
- it('should accept timeout within limit', () => {
201
- const strictValidator = createStrictValidator([]);
202
- const result = strictValidator.validateImageTimeout(30000); // 30 seconds
203
- strictEqual(result.valid, true);
204
- });
205
- });
206
-
207
- describe('Dimensions validation', () => {
208
- const validator = createDefaultValidator();
209
-
210
- it('should accept valid dimensions', () => {
211
- const result = validator.validateDimensions(800, 600);
212
- strictEqual(result.valid, true);
213
- });
214
-
215
- it('should reject zero dimensions', () => {
216
- const result = validator.validateDimensions(0, 600);
217
- strictEqual(result.valid, false);
218
- });
219
-
220
- it('should reject negative dimensions', () => {
221
- const result = validator.validateDimensions(800, -100);
222
- strictEqual(result.valid, false);
223
- });
224
-
225
- it('should reject NaN dimensions', () => {
226
- const result = validator.validateDimensions(NaN, 600);
227
- strictEqual(result.valid, false);
228
- });
229
-
230
- it('should reject non-number dimensions', () => {
231
- const result = validator.validateDimensions('800' as any, 600);
232
- strictEqual(result.valid, false);
233
- });
234
-
235
- it('should reject dimensions exceeding maximum', () => {
236
- const result = validator.validateDimensions(40000, 600);
237
- strictEqual(result.valid, false);
238
- strictEqual(result.error?.includes('exceed maximum'), true);
239
- });
240
-
241
- it('should accept dimensions at the limit', () => {
242
- const result = validator.validateDimensions(32767, 32767);
243
- strictEqual(result.valid, true);
244
- });
245
- });
246
-
247
- describe('Scale validation', () => {
248
- const validator = createDefaultValidator();
249
-
250
- it('should accept valid scale', () => {
251
- const result = validator.validateScale(2);
252
- strictEqual(result.valid, true);
253
- });
254
-
255
- it('should accept fractional scale', () => {
256
- const result = validator.validateScale(0.5);
257
- strictEqual(result.valid, true);
258
- });
259
-
260
- it('should reject zero scale', () => {
261
- const result = validator.validateScale(0);
262
- strictEqual(result.valid, false);
263
- });
264
-
265
- it('should reject negative scale', () => {
266
- const result = validator.validateScale(-2);
267
- strictEqual(result.valid, false);
268
- });
269
-
270
- it('should reject too large scale', () => {
271
- const result = validator.validateScale(20);
272
- strictEqual(result.valid, false);
273
- strictEqual(result.error?.includes('too large'), true);
274
- });
275
-
276
- it('should reject NaN scale', () => {
277
- const result = validator.validateScale(NaN);
278
- strictEqual(result.valid, false);
279
- });
280
- });
281
-
282
- describe('Element validation', () => {
283
- const validator = createDefaultValidator();
284
-
285
- it('should reject null element', () => {
286
- const result = validator.validateElement(null);
287
- strictEqual(result.valid, false);
288
- });
289
-
290
- it('should reject undefined element', () => {
291
- const result = validator.validateElement(undefined);
292
- strictEqual(result.valid, false);
293
- });
294
-
295
- it('should reject non-object element', () => {
296
- const result = validator.validateElement('not an element' as any);
297
- strictEqual(result.valid, false);
298
- });
299
-
300
- // Note: Full HTMLElement testing requires DOM environment (jsdom/browser)
301
- });
302
-
303
- describe('Options validation', () => {
304
- const validator = createDefaultValidator();
305
-
306
- it('should accept valid options', () => {
307
- const options = {
308
- scale: 2,
309
- width: 800,
310
- height: 600,
311
- imageTimeout: 15000
312
- };
313
- const result = validator.validateOptions(options);
314
- strictEqual(result.valid, true);
315
- });
316
-
317
- it('should collect multiple errors', () => {
318
- const options = {
319
- scale: -1,
320
- width: -800,
321
- imageTimeout: -5000
322
- };
323
- const result = validator.validateOptions(options);
324
- strictEqual(result.valid, false);
325
- // Should contain multiple error messages
326
- strictEqual(result.error?.includes('Scale'), true);
327
- strictEqual(result.error?.includes('Dimensions'), true);
328
- strictEqual(result.error?.includes('timeout'), true);
329
- });
330
-
331
- it('should allow missing optional fields', () => {
332
- const options = {};
333
- const result = validator.validateOptions(options);
334
- strictEqual(result.valid, true);
335
- });
336
- });
337
-
338
- describe('Strict validator', () => {
339
- const strictValidator = createStrictValidator(['trusted.com']);
340
-
341
- it('should reject data URLs in strict mode', () => {
342
- // Note: This would need implementation in createStrictValidator
343
- // Currently it doesn't disable data URLs
344
- });
345
-
346
- it('should enforce shorter timeout in strict mode', () => {
347
- const result = strictValidator.validateImageTimeout(120000);
348
- strictEqual(result.valid, false);
349
- });
350
-
351
- it('should enforce proxy whitelist in strict mode', () => {
352
- const allowed = strictValidator.validateUrl('https://trusted.com/proxy', 'proxy');
353
- strictEqual(allowed.valid, true);
354
-
355
- const denied = strictValidator.validateUrl('https://untrusted.com/proxy', 'proxy');
356
- strictEqual(denied.valid, false);
357
- });
358
- });
359
- });
@@ -1 +0,0 @@
1
- export const contains = (bit: number, value: number): boolean => (bit & value) !== 0;
@@ -1,315 +0,0 @@
1
- import { FEATURES } from './features';
2
- import { Context } from './context';
3
-
4
- /**
5
- * CacheStorage (Deprecated static methods)
6
- *
7
- * @deprecated The static methods of CacheStorage are deprecated.
8
- * Use OriginChecker class instead for instance-based origin checking.
9
- *
10
- * For backward compatibility, these methods remain but should not be used in new code.
11
- */
12
- export class CacheStorage {
13
- private static _link?: HTMLAnchorElement;
14
- private static _origin = 'about:blank';
15
-
16
- /**
17
- * @deprecated Use OriginChecker.getOrigin() instead
18
- */
19
- static getOrigin(url: string): string {
20
- const link = CacheStorage._link;
21
- if (!link) {
22
- return 'about:blank';
23
- }
24
-
25
- link.href = url;
26
- link.href = link.href; // IE9, LOL! - http://jsfiddle.net/niklasvh/2e48b/
27
- return link.protocol + link.hostname + link.port;
28
- }
29
-
30
- /**
31
- * @deprecated Use OriginChecker.isSameOrigin() instead
32
- */
33
- static isSameOrigin(src: string): boolean {
34
- return CacheStorage.getOrigin(src) === CacheStorage._origin;
35
- }
36
-
37
- /**
38
- * @deprecated No longer needed. OriginChecker is created per Context.
39
- */
40
- static setContext(window: Window): void {
41
- CacheStorage._link = window.document.createElement('a');
42
- CacheStorage._origin = CacheStorage.getOrigin(window.location.href);
43
- }
44
- }
45
-
46
- export interface ResourceOptions {
47
- imageTimeout: number;
48
- useCORS: boolean;
49
- allowTaint: boolean;
50
- proxy?: string;
51
- customIsSameOrigin?: (this: void, src: string, oldFn: (src: string) => boolean) => boolean | Promise<boolean>;
52
- maxCacheSize?: number; // Maximum cache size (default: 100, max: 10000)
53
- }
54
-
55
- interface CacheEntry {
56
- value: Promise<HTMLImageElement | HTMLCanvasElement | undefined>;
57
- }
58
-
59
- export class Cache {
60
- private readonly _cache: Map<string, CacheEntry> = new Map();
61
- private readonly maxSize: number;
62
- private readonly _pendingOperations: Map<string, Promise<void>> = new Map();
63
-
64
- constructor(
65
- private readonly context: Context,
66
- private readonly _options: ResourceOptions
67
- ) {
68
- // Default cache size: 100 items
69
- this.maxSize = _options.maxCacheSize ?? 100;
70
-
71
- if (this.maxSize < 1) {
72
- throw new Error('Cache maxSize must be at least 1');
73
- }
74
-
75
- if (this.maxSize > 10000) {
76
- this.context.logger.warn(
77
- `Cache maxSize ${this.maxSize} is very large and may cause memory issues. ` +
78
- `Consider using a smaller value (recommended: 100-1000).`
79
- );
80
- }
81
- }
82
-
83
- addImage(src: string): Promise<void> {
84
- // Wait for any pending operations on this key
85
- const pending = this._pendingOperations.get(src);
86
- if (pending) {
87
- return pending;
88
- }
89
-
90
- if (this.has(src)) {
91
- // Move to end for LRU ordering
92
- const entry = this._cache.get(src)!;
93
- this._cache.delete(src);
94
- this._cache.set(src, entry);
95
- return Promise.resolve();
96
- }
97
-
98
- if (isBlobImage(src) || isRenderable(src)) {
99
- // Create a pending operation to ensure atomicity
100
- const operation = this._addImageInternal(src);
101
- this._pendingOperations.set(src, operation);
102
- operation.finally(() => {
103
- this._pendingOperations.delete(src);
104
- });
105
-
106
- return operation;
107
- }
108
-
109
- return Promise.resolve();
110
- }
111
-
112
- private async _addImageInternal(src: string): Promise<void> {
113
- const timeoutMs = this._options.imageTimeout ?? 15000;
114
- const imageWithTimeout = this.withTimeout(
115
- this.loadImage(src),
116
- timeoutMs,
117
- `Timed out (${timeoutMs}ms) loading image`
118
- );
119
-
120
- // Handle errors to prevent unhandled rejections
121
- imageWithTimeout.catch((error) => {
122
- this.context.logger.error(
123
- `Failed to load image ${src}: ${error instanceof Error ? error.message : 'Unknown error'}`
124
- );
125
- });
126
-
127
- // Store the promise with timeout in cache
128
- this.set(src, imageWithTimeout);
129
- }
130
-
131
- private withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
132
- if (timeoutMs <= 0) {
133
- return promise;
134
- }
135
-
136
- let timeoutId: ReturnType<typeof setTimeout> | undefined;
137
- const timeout = new Promise<never>((_, reject) => {
138
- timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
139
- });
140
-
141
- return Promise.race([promise, timeout]).finally(() => {
142
- if (timeoutId !== undefined) {
143
- clearTimeout(timeoutId);
144
- }
145
- });
146
- }
147
-
148
- match(src: string): Promise<HTMLImageElement | HTMLCanvasElement | undefined> | undefined {
149
- const entry = this._cache.get(src);
150
- if (entry) {
151
- // Move to end for LRU ordering (O(1))
152
- this._cache.delete(src);
153
- this._cache.set(src, entry);
154
- return entry.value;
155
- }
156
- return undefined;
157
- }
158
-
159
- /**
160
- * Set a value in cache with LRU eviction (O(1) via Map insertion order).
161
- * Map preserves insertion order; delete+set on access moves items to the end.
162
- * The first key in Map.keys() is always the least recently used.
163
- */
164
- private set(key: string, value: Promise<HTMLImageElement | HTMLCanvasElement | undefined>): void {
165
- if (this._cache.has(key)) {
166
- // Update existing entry: move to end of Map
167
- this._cache.delete(key);
168
- } else if (this._cache.size >= this.maxSize) {
169
- // Evict LRU (first key = least recently used) — O(1)
170
- const lruKey = this._cache.keys().next().value;
171
- if (lruKey !== undefined) {
172
- this._cache.delete(lruKey);
173
- this.context.logger.debug(`Cache: Evicted LRU entry: ${lruKey}`);
174
- }
175
- }
176
-
177
- this._cache.set(key, { value });
178
- }
179
-
180
- /**
181
- * Get cache size
182
- */
183
- size(): number {
184
- return this._cache.size;
185
- }
186
-
187
- /**
188
- * Get max cache size
189
- */
190
- getMaxSize(): number {
191
- return this.maxSize;
192
- }
193
-
194
- /**
195
- * Clear all cache entries
196
- */
197
- clear(): void {
198
- this._cache.clear();
199
- }
200
-
201
- private async loadImage(key: string): Promise<HTMLImageElement | undefined> {
202
- const originChecker = this.context.originChecker;
203
- const defaultIsSameOrigin = (src: string) => originChecker.isSameOrigin(src);
204
-
205
- const isSameOrigin: boolean =
206
- typeof this._options.customIsSameOrigin === 'function'
207
- ? await this._options.customIsSameOrigin(key, defaultIsSameOrigin)
208
- : defaultIsSameOrigin(key);
209
- const useCORS =
210
- !isInlineImage(key) && this._options.useCORS === true && FEATURES.SUPPORT_CORS_IMAGES && !isSameOrigin;
211
- const useProxy =
212
- !isInlineImage(key) &&
213
- !isSameOrigin &&
214
- !isBlobImage(key) &&
215
- typeof this._options.proxy === 'string' &&
216
- FEATURES.SUPPORT_CORS_XHR &&
217
- !useCORS;
218
- if (
219
- !isSameOrigin &&
220
- this._options.allowTaint === false &&
221
- !isInlineImage(key) &&
222
- !isBlobImage(key) &&
223
- !useProxy &&
224
- !useCORS
225
- ) {
226
- return;
227
- }
228
-
229
- let src = key;
230
- if (useProxy) {
231
- src = await this.proxy(src);
232
- }
233
-
234
- this.context.logger.debug(`Added image ${key.substring(0, 256)}`);
235
-
236
- return await new Promise((resolve, reject) => {
237
- const img = new Image();
238
- img.onload = () => resolve(img);
239
- img.onerror = reject;
240
- //ios safari 10.3 taints canvas with data urls unless crossOrigin is set to anonymous
241
- if (isInlineBase64Image(src) || useCORS) {
242
- img.crossOrigin = 'anonymous';
243
- }
244
- img.src = src;
245
- if (img.complete === true) {
246
- // Inline XML images may fail to parse, throwing an Error later on
247
- setTimeout(() => resolve(img), 500);
248
- }
249
- });
250
- }
251
-
252
- private has(key: string): boolean {
253
- return this._cache.has(key);
254
- }
255
-
256
- keys(): Promise<string[]> {
257
- return Promise.resolve(Array.from(this._cache.keys()));
258
- }
259
-
260
- private proxy(src: string): Promise<string> {
261
- const proxy = this._options.proxy;
262
-
263
- if (!proxy) {
264
- throw new Error('No proxy defined');
265
- }
266
-
267
- const key = src.substring(0, 256);
268
-
269
- return new Promise((resolve, reject) => {
270
- const responseType = FEATURES.SUPPORT_RESPONSE_TYPE ? 'blob' : 'text';
271
- const xhr = new XMLHttpRequest();
272
- xhr.onload = () => {
273
- if (xhr.status === 200) {
274
- if (responseType === 'text') {
275
- resolve(xhr.response);
276
- } else {
277
- const reader = new FileReader();
278
- reader.addEventListener('load', () => resolve(reader.result as string), false);
279
- reader.addEventListener('error', (e) => reject(e), false);
280
- reader.readAsDataURL(xhr.response);
281
- }
282
- } else {
283
- reject(`Failed to proxy resource ${key} with status code ${xhr.status}`);
284
- }
285
- };
286
-
287
- xhr.onerror = reject;
288
- const queryString = proxy.indexOf('?') > -1 ? '&' : '?';
289
- xhr.open('GET', `${proxy}${queryString}url=${encodeURIComponent(src)}&responseType=${responseType}`);
290
-
291
- if (responseType !== 'text' && xhr instanceof XMLHttpRequest) {
292
- xhr.responseType = responseType;
293
- }
294
-
295
- if (this._options.imageTimeout) {
296
- const timeout = this._options.imageTimeout;
297
- xhr.timeout = timeout;
298
- xhr.ontimeout = () => reject(`Timed out (${timeout}ms) proxying ${key}`);
299
- }
300
-
301
- xhr.send();
302
- });
303
- }
304
- }
305
-
306
- const INLINE_SVG = /^data:image\/svg\+xml/i;
307
- const INLINE_BASE64 = /^data:image\/.*;base64,/i;
308
- const INLINE_IMG = /^data:image\/.*/i;
309
-
310
- const isRenderable = (src: string): boolean => FEATURES.SUPPORT_SVG_DRAWING || !isSVG(src);
311
- const isInlineImage = (src: string): boolean => INLINE_IMG.test(src);
312
- const isInlineBase64Image = (src: string): boolean => INLINE_BASE64.test(src);
313
- const isBlobImage = (src: string): boolean => src.substr(0, 4) === 'blob';
314
-
315
- const isSVG = (src: string): boolean => src.substr(-3).toLowerCase() === 'svg' || INLINE_SVG.test(src);