html2canvas-pro 2.1.0 → 2.1.1

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 (186) hide show
  1. package/dist/html2canvas-pro.esm.js +21 -7
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +21 -7
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +3 -3
  6. package/dist/lib/core/cache-storage.js +2 -2
  7. package/dist/lib/core/features.js +2 -2
  8. package/dist/lib/render/canvas/background-renderer.js +6 -0
  9. package/dist/lib/render/canvas/canvas-renderer.js +5 -1
  10. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  11. package/package.json +3 -11
  12. package/dist/lib/invariant.js +0 -9
  13. package/dist/types/invariant.d.ts +0 -1
  14. package/src/__tests__/index.ts +0 -99
  15. package/src/config.ts +0 -107
  16. package/src/core/__mocks__/cache-storage.ts +0 -1
  17. package/src/core/__mocks__/context.ts +0 -19
  18. package/src/core/__mocks__/features.ts +0 -8
  19. package/src/core/__mocks__/logger.ts +0 -17
  20. package/src/core/__tests__/cache-storage.test.ts +0 -205
  21. package/src/core/__tests__/cache-storage.ts +0 -278
  22. package/src/core/__tests__/logger.ts +0 -29
  23. package/src/core/__tests__/validator.ts +0 -359
  24. package/src/core/bitwise.ts +0 -1
  25. package/src/core/cache-storage.ts +0 -315
  26. package/src/core/context.ts +0 -31
  27. package/src/core/debugger.ts +0 -32
  28. package/src/core/features.ts +0 -222
  29. package/src/core/logger.ts +0 -64
  30. package/src/core/origin-checker.ts +0 -57
  31. package/src/core/performance-monitor.ts +0 -241
  32. package/src/core/render-element.ts +0 -272
  33. package/src/core/util.ts +0 -1
  34. package/src/core/validator.ts +0 -593
  35. package/src/css/index.ts +0 -427
  36. package/src/css/layout/__mocks__/bounds.ts +0 -6
  37. package/src/css/layout/bounds.ts +0 -79
  38. package/src/css/layout/text.ts +0 -161
  39. package/src/css/property-descriptor.ts +0 -49
  40. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  41. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  42. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  43. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  44. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  45. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  46. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  47. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  48. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  49. package/src/css/property-descriptors/background-clip.ts +0 -30
  50. package/src/css/property-descriptors/background-color.ts +0 -9
  51. package/src/css/property-descriptors/background-image.ts +0 -27
  52. package/src/css/property-descriptors/background-origin.ts +0 -31
  53. package/src/css/property-descriptors/background-position.ts +0 -38
  54. package/src/css/property-descriptors/background-repeat.ts +0 -44
  55. package/src/css/property-descriptors/background-size.ts +0 -27
  56. package/src/css/property-descriptors/border-color.ts +0 -13
  57. package/src/css/property-descriptors/border-radius.ts +0 -19
  58. package/src/css/property-descriptors/border-style.ts +0 -34
  59. package/src/css/property-descriptors/border-width.ts +0 -20
  60. package/src/css/property-descriptors/box-shadow.ts +0 -60
  61. package/src/css/property-descriptors/clip-path.ts +0 -271
  62. package/src/css/property-descriptors/color.ts +0 -9
  63. package/src/css/property-descriptors/content.ts +0 -26
  64. package/src/css/property-descriptors/counter-increment.ts +0 -43
  65. package/src/css/property-descriptors/counter-reset.ts +0 -36
  66. package/src/css/property-descriptors/direction.ts +0 -23
  67. package/src/css/property-descriptors/display.ts +0 -117
  68. package/src/css/property-descriptors/duration.ts +0 -14
  69. package/src/css/property-descriptors/float.ts +0 -29
  70. package/src/css/property-descriptors/font-family.ts +0 -38
  71. package/src/css/property-descriptors/font-size.ts +0 -9
  72. package/src/css/property-descriptors/font-style.ts +0 -25
  73. package/src/css/property-descriptors/font-variant.ts +0 -12
  74. package/src/css/property-descriptors/font-weight.ts +0 -26
  75. package/src/css/property-descriptors/image-rendering.ts +0 -33
  76. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  77. package/src/css/property-descriptors/line-break.ts +0 -22
  78. package/src/css/property-descriptors/line-height.ts +0 -22
  79. package/src/css/property-descriptors/list-style-image.ts +0 -19
  80. package/src/css/property-descriptors/list-style-position.ts +0 -22
  81. package/src/css/property-descriptors/list-style-type.ts +0 -179
  82. package/src/css/property-descriptors/margin.ts +0 -13
  83. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  84. package/src/css/property-descriptors/object-fit.ts +0 -39
  85. package/src/css/property-descriptors/opacity.ts +0 -15
  86. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  87. package/src/css/property-descriptors/overflow.ts +0 -34
  88. package/src/css/property-descriptors/padding.ts +0 -14
  89. package/src/css/property-descriptors/paint-order.ts +0 -42
  90. package/src/css/property-descriptors/position.ts +0 -30
  91. package/src/css/property-descriptors/quotes.ts +0 -57
  92. package/src/css/property-descriptors/rotate.ts +0 -34
  93. package/src/css/property-descriptors/text-align.ts +0 -26
  94. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  95. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  96. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  97. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  98. package/src/css/property-descriptors/text-overflow.ts +0 -23
  99. package/src/css/property-descriptors/text-shadow.ts +0 -52
  100. package/src/css/property-descriptors/text-transform.ts +0 -27
  101. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  102. package/src/css/property-descriptors/transform-origin.ts +0 -29
  103. package/src/css/property-descriptors/transform.ts +0 -74
  104. package/src/css/property-descriptors/visibility.ts +0 -25
  105. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  106. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  107. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  108. package/src/css/property-descriptors/word-break.ts +0 -25
  109. package/src/css/property-descriptors/writing-mode.ts +0 -37
  110. package/src/css/property-descriptors/z-index.ts +0 -27
  111. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  112. package/src/css/syntax/parser.ts +0 -188
  113. package/src/css/syntax/tokenizer.ts +0 -822
  114. package/src/css/type-descriptor.ts +0 -7
  115. package/src/css/types/__tests__/color-tests.ts +0 -147
  116. package/src/css/types/__tests__/image-tests.ts +0 -239
  117. package/src/css/types/angle.ts +0 -86
  118. package/src/css/types/color-math.ts +0 -22
  119. package/src/css/types/color-spaces/a98.ts +0 -86
  120. package/src/css/types/color-spaces/p3.ts +0 -92
  121. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  122. package/src/css/types/color-spaces/rec2020.ts +0 -90
  123. package/src/css/types/color-spaces/srgb.ts +0 -87
  124. package/src/css/types/color-utilities.ts +0 -452
  125. package/src/css/types/color.ts +0 -485
  126. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  127. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  128. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  129. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  130. package/src/css/types/functions/counter.ts +0 -511
  131. package/src/css/types/functions/gradient.ts +0 -206
  132. package/src/css/types/functions/linear-gradient.ts +0 -28
  133. package/src/css/types/functions/radial-gradient.ts +0 -101
  134. package/src/css/types/image.ts +0 -120
  135. package/src/css/types/index.ts +0 -1
  136. package/src/css/types/length-percentage.ts +0 -137
  137. package/src/css/types/length.ts +0 -7
  138. package/src/css/types/time.ts +0 -20
  139. package/src/dom/__mocks__/document-cloner.ts +0 -22
  140. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  141. package/src/dom/__tests__/element-container.test.ts +0 -129
  142. package/src/dom/document-cloner.ts +0 -929
  143. package/src/dom/dom-normalizer.ts +0 -133
  144. package/src/dom/element-container.ts +0 -75
  145. package/src/dom/elements/li-element-container.ts +0 -10
  146. package/src/dom/elements/ol-element-container.ts +0 -12
  147. package/src/dom/elements/select-element-container.ts +0 -10
  148. package/src/dom/elements/textarea-element-container.ts +0 -9
  149. package/src/dom/node-parser.ts +0 -177
  150. package/src/dom/node-type-guards.ts +0 -70
  151. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  152. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  153. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  154. package/src/dom/replaced-elements/index.ts +0 -5
  155. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  156. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  157. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  158. package/src/dom/text-container.ts +0 -42
  159. package/src/global.d.ts +0 -19
  160. package/src/index.ts +0 -82
  161. package/src/invariant.ts +0 -5
  162. package/src/options.ts +0 -55
  163. package/src/render/__tests__/object-fit.test.ts +0 -85
  164. package/src/render/background.ts +0 -298
  165. package/src/render/bezier-curve.ts +0 -47
  166. package/src/render/border.ts +0 -165
  167. package/src/render/bound-curves.ts +0 -388
  168. package/src/render/box-sizing.ts +0 -31
  169. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  170. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  171. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  172. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  173. package/src/render/canvas/background-renderer.ts +0 -271
  174. package/src/render/canvas/border-renderer.ts +0 -224
  175. package/src/render/canvas/canvas-path.ts +0 -31
  176. package/src/render/canvas/canvas-renderer.ts +0 -641
  177. package/src/render/canvas/effects-renderer.ts +0 -130
  178. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  179. package/src/render/canvas/text-renderer.ts +0 -700
  180. package/src/render/effects.ts +0 -75
  181. package/src/render/font-metrics.ts +0 -72
  182. package/src/render/object-fit.ts +0 -100
  183. package/src/render/path.ts +0 -37
  184. package/src/render/renderer-interface.ts +0 -28
  185. package/src/render/stacking-context.ts +0 -386
  186. package/src/render/vector.ts +0 -19
@@ -1,278 +0,0 @@
1
- import { deepStrictEqual, fail } from 'assert';
2
- import { FEATURES } from '../features';
3
- import { CacheStorage } from '../cache-storage';
4
- import { Context } from '../context';
5
- import { Bounds } from '../../css/layout/bounds';
6
- import { Html2CanvasConfig } from '../../config';
7
-
8
- const proxy = 'http://example.com/proxy';
9
-
10
- const createMockContext = (origin: string, opts = {}) => {
11
- const mockWindow = {
12
- location: {
13
- href: origin
14
- },
15
- document: {
16
- createElement(_name: string) {
17
- let _href = '';
18
- return {
19
- set href(value: string) {
20
- _href = value;
21
- },
22
- get href() {
23
- return _href;
24
- },
25
- get protocol() {
26
- return new URL(_href).protocol;
27
- },
28
- get hostname() {
29
- return new URL(_href).hostname;
30
- },
31
- get port() {
32
- return new URL(_href).port;
33
- }
34
- };
35
- }
36
- }
37
- };
38
-
39
- // For backward compatibility, also initialize static CacheStorage
40
- CacheStorage.setContext(mockWindow as Window);
41
-
42
- const config = new Html2CanvasConfig({ window: mockWindow as Window });
43
-
44
- return new Context(
45
- {
46
- logging: false,
47
- imageTimeout: 0,
48
- useCORS: false,
49
- allowTaint: false,
50
- proxy,
51
- ...opts
52
- },
53
- new Bounds(0, 0, 0, 0),
54
- config
55
- );
56
- };
57
-
58
- const images: ImageMock[] = [];
59
- const xhr: XMLHttpRequestMock[] = [];
60
- const sleep = async (timeout: number) => await new Promise((resolve) => setTimeout(resolve, timeout));
61
-
62
- class ImageMock {
63
- src?: string;
64
- crossOrigin?: string;
65
- onload?: () => void;
66
- constructor() {
67
- images.push(this);
68
- }
69
- }
70
-
71
- class XMLHttpRequestMock {
72
- sent: boolean;
73
- status: number;
74
- timeout: number;
75
- method?: string;
76
- url?: string;
77
- response?: string;
78
- onload?: () => void;
79
- ontimeout?: () => void;
80
- constructor() {
81
- this.sent = false;
82
- this.status = 500;
83
- this.timeout = 5000;
84
- xhr.push(this);
85
- }
86
-
87
- async load(status: number, response: string) {
88
- this.response = response;
89
- this.status = status;
90
- if (this.onload) {
91
- this.onload();
92
- }
93
- await sleep(0);
94
- }
95
-
96
- open(method: string, url: string) {
97
- this.method = method;
98
- this.url = url;
99
- }
100
- send() {
101
- this.sent = true;
102
- }
103
- }
104
-
105
- Object.defineProperty(global, 'Image', { value: ImageMock, writable: true });
106
- Object.defineProperty(global, 'XMLHttpRequest', {
107
- value: XMLHttpRequestMock,
108
- writable: true
109
- });
110
-
111
- const setFeatures = (opts: { [key: string]: boolean } = {}) => {
112
- const defaults: { [key: string]: boolean } = {
113
- SUPPORT_SVG_DRAWING: true,
114
- SUPPORT_CORS_IMAGES: true,
115
- SUPPORT_CORS_XHR: true,
116
- SUPPORT_RESPONSE_TYPE: false
117
- };
118
-
119
- Object.keys(defaults).forEach((key) => {
120
- Object.defineProperty(FEATURES, key, {
121
- value: typeof opts[key] === 'boolean' ? opts[key] : defaults[key],
122
- writable: true
123
- });
124
- });
125
- };
126
-
127
- describe('cache-storage', () => {
128
- beforeEach(() => setFeatures());
129
- afterEach(() => {
130
- xhr.splice(0, xhr.length);
131
- images.splice(0, images.length);
132
- });
133
- it('addImage adds images to cache', async () => {
134
- const { cache } = createMockContext('http://example.com', { proxy: null });
135
- await cache.addImage('http://example.com/test.jpg');
136
- await cache.addImage('http://example.com/test2.jpg');
137
-
138
- deepStrictEqual(images.length, 2);
139
- deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
140
- deepStrictEqual(images[1].src, 'http://example.com/test2.jpg');
141
- });
142
-
143
- it('addImage should not add duplicate entries', async () => {
144
- const { cache } = createMockContext('http://example.com');
145
- await cache.addImage('http://example.com/test.jpg');
146
- await cache.addImage('http://example.com/test.jpg');
147
-
148
- deepStrictEqual(images.length, 1);
149
- deepStrictEqual(images[0].src, 'http://example.com/test.jpg');
150
- });
151
-
152
- describe('svg', () => {
153
- it('should add svg images correctly', async () => {
154
- const { cache } = createMockContext('http://example.com');
155
- await cache.addImage('http://example.com/test.svg');
156
- await cache.addImage('http://example.com/test2.svg');
157
-
158
- deepStrictEqual(images.length, 2);
159
- deepStrictEqual(images[0].src, 'http://example.com/test.svg');
160
- deepStrictEqual(images[1].src, 'http://example.com/test2.svg');
161
- });
162
-
163
- it('should omit svg images if not supported', async () => {
164
- setFeatures({ SUPPORT_SVG_DRAWING: false });
165
- const { cache } = createMockContext('http://example.com');
166
- await cache.addImage('http://example.com/test.svg');
167
- await cache.addImage('http://example.com/test2.svg');
168
-
169
- deepStrictEqual(images.length, 0);
170
- });
171
- });
172
-
173
- describe('cross-origin', () => {
174
- it('addImage should not add images it cannot load/render', async () => {
175
- const { cache } = createMockContext('http://example.com', {
176
- proxy: undefined
177
- });
178
- await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
179
- deepStrictEqual(images.length, 0);
180
- });
181
-
182
- it('addImage should add images if tainting enabled', async () => {
183
- const { cache } = createMockContext('http://example.com', {
184
- allowTaint: true,
185
- proxy: undefined
186
- });
187
- await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
188
- deepStrictEqual(images.length, 1);
189
- deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
190
- deepStrictEqual(images[0].crossOrigin, undefined);
191
- });
192
-
193
- it('addImage should add images if cors enabled', async () => {
194
- const { cache } = createMockContext('http://example.com', { useCORS: true });
195
- await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
196
- deepStrictEqual(images.length, 1);
197
- deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
198
- deepStrictEqual(images[0].crossOrigin, 'anonymous');
199
- });
200
-
201
- it('addImage should not add images if cors enabled but not supported', async () => {
202
- setFeatures({ SUPPORT_CORS_IMAGES: false });
203
-
204
- const { cache } = createMockContext('http://example.com', {
205
- useCORS: true,
206
- proxy: undefined
207
- });
208
- await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
209
- deepStrictEqual(images.length, 0);
210
- });
211
-
212
- it('addImage should not add images to proxy if cors enabled', async () => {
213
- const { cache } = createMockContext('http://example.com', { useCORS: true });
214
- await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
215
- deepStrictEqual(images.length, 1);
216
- deepStrictEqual(images[0].src, 'http://html2canvas.hertzen.com/test.jpg');
217
- deepStrictEqual(images[0].crossOrigin, 'anonymous');
218
- });
219
-
220
- it('addImage should use proxy ', async () => {
221
- const { cache } = createMockContext('http://example.com');
222
- await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
223
- deepStrictEqual(xhr.length, 1);
224
- deepStrictEqual(
225
- xhr[0].url,
226
- `${proxy}?url=${encodeURIComponent('http://html2canvas.hertzen.com/test.jpg')}&responseType=text`
227
- );
228
- await xhr[0].load(200, '<data response>');
229
-
230
- deepStrictEqual(images.length, 1);
231
- deepStrictEqual(images[0].src, '<data response>');
232
- });
233
-
234
- it('proxy should respect imageTimeout', async () => {
235
- const { cache } = createMockContext('http://example.com', {
236
- imageTimeout: 10
237
- });
238
- await cache.addImage('http://html2canvas.hertzen.com/test.jpg');
239
-
240
- deepStrictEqual(xhr.length, 1);
241
- deepStrictEqual(
242
- xhr[0].url,
243
- `${proxy}?url=${encodeURIComponent('http://html2canvas.hertzen.com/test.jpg')}&responseType=text`
244
- );
245
- deepStrictEqual(xhr[0].timeout, 10);
246
- if (xhr[0].ontimeout) {
247
- xhr[0].ontimeout();
248
- }
249
- try {
250
- await cache.match('http://html2canvas.hertzen.com/test.jpg');
251
- fail('Expected result to timeout');
252
- } catch (e) {}
253
- });
254
- });
255
-
256
- it('match should return cache entry', async () => {
257
- const { cache } = createMockContext('http://example.com');
258
- await cache.addImage('http://example.com/test.jpg');
259
-
260
- if (images[0].onload) {
261
- images[0].onload();
262
- }
263
-
264
- const response = await cache.match('http://example.com/test.jpg');
265
-
266
- deepStrictEqual((response as HTMLImageElement).src, 'http://example.com/test.jpg');
267
- });
268
-
269
- it('image should respect imageTimeout', async () => {
270
- const { cache } = createMockContext('http://example.com', { imageTimeout: 10 });
271
- cache.addImage('http://example.com/test.jpg');
272
-
273
- try {
274
- await cache.match('http://example.com/test.jpg');
275
- fail('Expected result to timeout');
276
- } catch (e) {}
277
- });
278
- });
@@ -1,29 +0,0 @@
1
- import { Logger } from '../logger';
2
-
3
- describe('logger', () => {
4
- let infoSpy: any;
5
-
6
- beforeEach(() => {
7
- infoSpy = vi.spyOn(console, 'info').mockImplementation(() => {
8
- // do nothing
9
- });
10
- });
11
-
12
- afterEach(() => {
13
- infoSpy.mockRestore();
14
- });
15
-
16
- it('should call console.info when logger enabled', () => {
17
- const id = Math.random().toString();
18
- const logger = new Logger({ id, enabled: true });
19
- logger.info('testing');
20
- expect(infoSpy).toHaveBeenLastCalledWith(id, expect.stringMatching(/\d+ms/), 'testing');
21
- });
22
-
23
- it("shouldn't call console.info when logger disabled", () => {
24
- const id = Math.random().toString();
25
- const logger = new Logger({ id, enabled: false });
26
- logger.info('testing');
27
- expect(infoSpy).not.toHaveBeenCalled();
28
- });
29
- });
@@ -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;