svg-toolbox 1.1.12 → 1.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.
- package/README.md +105 -87
- package/es/analyze/paths.js +14 -0
- package/es/analyze/paths.js.map +1 -1
- package/es/compare/__tests__/diff.test.d.ts +1 -0
- package/es/compare/__tests__/diff.test.js +64 -0
- package/es/compare/__tests__/diff.test.js.map +1 -0
- package/es/compare/diff.js +13 -7
- package/es/compare/diff.js.map +1 -1
- package/es/convert/image.js +10 -5
- package/es/convert/image.js.map +1 -1
- package/es/optimize/__tests__/cleanup.test.js +29 -0
- package/es/optimize/__tests__/cleanup.test.js.map +1 -1
- package/es/optimize/cleanup.d.ts +8 -0
- package/es/optimize/cleanup.js +38 -1
- package/es/optimize/cleanup.js.map +1 -1
- package/es/utils/__tests__/path-validation.test.d.ts +1 -0
- package/es/utils/__tests__/path-validation.test.js +64 -0
- package/es/utils/__tests__/path-validation.test.js.map +1 -0
- package/es/utils/path-validation.d.ts +29 -0
- package/es/utils/path-validation.js +101 -0
- package/es/utils/path-validation.js.map +1 -0
- package/lib/analyze/paths.js +14 -0
- package/lib/analyze/paths.js.map +1 -1
- package/lib/compare/__tests__/diff.test.d.ts +1 -0
- package/lib/compare/__tests__/diff.test.js +128 -0
- package/lib/compare/__tests__/diff.test.js.map +1 -0
- package/lib/compare/diff.js +12 -8
- package/lib/compare/diff.js.map +1 -1
- package/lib/convert/image.js +10 -7
- package/lib/convert/image.js.map +1 -1
- package/lib/optimize/__tests__/cleanup.test.js +29 -0
- package/lib/optimize/__tests__/cleanup.test.js.map +1 -1
- package/lib/optimize/cleanup.d.ts +8 -0
- package/lib/optimize/cleanup.js +38 -1
- package/lib/optimize/cleanup.js.map +1 -1
- package/lib/utils/__tests__/path-validation.test.d.ts +1 -0
- package/lib/utils/__tests__/path-validation.test.js +69 -0
- package/lib/utils/__tests__/path-validation.test.js.map +1 -0
- package/lib/utils/path-validation.d.ts +29 -0
- package/lib/utils/path-validation.js +109 -0
- package/lib/utils/path-validation.js.map +1 -0
- package/package.json +10 -3
package/es/optimize/cleanup.js
CHANGED
|
@@ -33,11 +33,48 @@ export function removeEmptyAttributes(svgContent) {
|
|
|
33
33
|
removeEmptyAttrs(svgElement);
|
|
34
34
|
return svgElement.outerHTML.trim();
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Maximum allowed SVG content length to prevent ReDoS attacks
|
|
38
|
+
*/
|
|
39
|
+
const MAX_SVG_CONTENT_LENGTH = 10000000; // 10MB should be sufficient for most SVG files
|
|
36
40
|
/**
|
|
37
41
|
* Removes comments from SVG content
|
|
42
|
+
*
|
|
43
|
+
* This function uses iterative replacement to handle nested comments correctly
|
|
44
|
+
* and includes input length validation to prevent ReDoS attacks.
|
|
45
|
+
*
|
|
46
|
+
* The function handles cases like:
|
|
47
|
+
* - Simple comments: <!-- comment -->
|
|
48
|
+
* - Nested comments: <!--<!-- inner -->-->
|
|
49
|
+
* - Multiple comments: <!-- one --><!-- two -->
|
|
38
50
|
*/
|
|
39
51
|
export function removeComments(svgContent) {
|
|
40
|
-
|
|
52
|
+
// Validate input length to prevent ReDoS attacks
|
|
53
|
+
if (svgContent.length > MAX_SVG_CONTENT_LENGTH) {
|
|
54
|
+
throw new Error(`SVG content length exceeds maximum allowed length of ${MAX_SVG_CONTENT_LENGTH} characters`);
|
|
55
|
+
}
|
|
56
|
+
// Use iterative replacement to handle nested comments
|
|
57
|
+
// This prevents incomplete sanitization where nested comments could reappear
|
|
58
|
+
// Example: "<!--<!-- comment -->-->" should become "" not "<!-- comment -->"
|
|
59
|
+
let previous;
|
|
60
|
+
let result = svgContent;
|
|
61
|
+
let iterations = 0;
|
|
62
|
+
const MAX_ITERATIONS = 1000; // Prevent infinite loops
|
|
63
|
+
do {
|
|
64
|
+
previous = result;
|
|
65
|
+
// Match HTML comments and standalone delimiters:
|
|
66
|
+
// - Full comments: <!-- ... --> or <!-- ... --!>
|
|
67
|
+
// - Orphaned starts: <!--
|
|
68
|
+
// - Orphaned ends: --> or --!>
|
|
69
|
+
// The regex uses non-greedy matching (*?) to match the shortest possible comment
|
|
70
|
+
// and is applied iteratively until no further matches remain.
|
|
71
|
+
result = result.replace(/<!--[\s\S]*?--!?>|<!--|--!?>(?=[^>]|$)/g, '');
|
|
72
|
+
iterations++;
|
|
73
|
+
if (iterations > MAX_ITERATIONS) {
|
|
74
|
+
throw new Error('Comment removal exceeded maximum iterations. SVG content may be malformed.');
|
|
75
|
+
}
|
|
76
|
+
} while (result !== previous);
|
|
77
|
+
return result;
|
|
41
78
|
}
|
|
42
79
|
/**
|
|
43
80
|
* Normalizes whitespace in SVG content
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/optimize/cleanup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAqB,MAAM,qBAAqB,CAAC;AAE1E;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAA4B;IAChE,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,CAAC;QAC5C,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAE7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE;QAC/B,WAAW,EAAE,eAAe;KAC7B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;IACrC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAEjD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,SAAS,gBAAgB,CAAC,OAAgB;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7C,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACnB,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC5C,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YAC3C,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAE7B,OAAO,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/optimize/cleanup.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAqB,MAAM,qBAAqB,CAAC;AAE1E;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAA4B;IAChE,MAAM,SAAS,GAAG,gBAAgB,CAAC,UAAU,CAAC;QAC5C,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAE7B,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,SAAS,EAAE;QAC/B,WAAW,EAAE,eAAe;KAC7B,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;IACrC,MAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAEjD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,SAAS,gBAAgB,CAAC,OAAgB;QACxC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC7C,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACnB,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC5C,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YAC3C,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAE7B,OAAO,UAAU,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,MAAM,sBAAsB,GAAG,QAAQ,CAAC,CAAC,+CAA+C;AAExF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB;IAC/C,iDAAiD;IACjD,IAAI,UAAU,CAAC,MAAM,GAAG,sBAAsB,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,wDAAwD,sBAAsB,aAAa,CAAC,CAAC;IAC/G,CAAC;IAED,sDAAsD;IACtD,6EAA6E;IAC7E,6EAA6E;IAC7E,IAAI,QAAgB,CAAC;IACrB,IAAI,MAAM,GAAG,UAAU,CAAC;IACxB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,cAAc,GAAG,IAAI,CAAC,CAAC,yBAAyB;IAEtD,GAAG,CAAC;QACF,QAAQ,GAAG,MAAM,CAAC;QAClB,iDAAiD;QACjD,iDAAiD;QACjD,0BAA0B;QAC1B,+BAA+B;QAC/B,iFAAiF;QACjF,8DAA8D;QAC9D,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,yCAAyC,EAAE,EAAE,CAAC,CAAC;QACvE,UAAU,EAAE,CAAC;QAEb,IAAI,UAAU,GAAG,cAAc,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;QAChG,CAAC;IACH,CAAC,QAAQ,MAAM,KAAK,QAAQ,EAAE;IAE9B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,UAAU;SACd,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;SACvB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,UAA4B;IACtD,IAAI,SAAS,GAAG,gBAAgB,CAAC,UAAU,CAAC;QAC1C,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAE7B,SAAS,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IACtC,SAAS,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC7C,SAAS,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;IAE3C,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { validateFilePath, validateReadPath, validateWritePath } from '../path-validation';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
describe('Path Validation', () => {
|
|
6
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-toolbox-test-'));
|
|
7
|
+
afterAll(() => {
|
|
8
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
9
|
+
});
|
|
10
|
+
describe('validateFilePath', () => {
|
|
11
|
+
it('should normalize valid paths', () => {
|
|
12
|
+
const result = validateFilePath('./test.svg');
|
|
13
|
+
expect(path.isAbsolute(result)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it('should reject paths with .. traversal', () => {
|
|
16
|
+
expect(() => validateFilePath('../etc/passwd')).toThrow('Path traversal detected');
|
|
17
|
+
expect(() => validateFilePath('../../secret')).toThrow('Path traversal detected');
|
|
18
|
+
});
|
|
19
|
+
it('should reject empty paths', () => {
|
|
20
|
+
expect(() => validateFilePath('')).toThrow('File path must be a non-empty string');
|
|
21
|
+
expect(() => validateFilePath(null)).toThrow('File path must be a non-empty string');
|
|
22
|
+
});
|
|
23
|
+
it('should restrict paths to base directory when provided', () => {
|
|
24
|
+
const baseDir = tempDir;
|
|
25
|
+
const validPath = path.join(baseDir, 'test.svg');
|
|
26
|
+
expect(() => validateFilePath(validPath, baseDir)).not.toThrow();
|
|
27
|
+
expect(() => validateFilePath('/etc/passwd', baseDir)).toThrow('Path is outside the allowed directory');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('validateReadPath', () => {
|
|
31
|
+
it('should validate file extensions', () => {
|
|
32
|
+
expect(() => validateReadPath('test.svg', ['.svg'])).not.toThrow();
|
|
33
|
+
expect(() => validateReadPath('test.png', ['.svg'])).toThrow('File extension .png is not allowed');
|
|
34
|
+
});
|
|
35
|
+
it('should accept multiple allowed extensions', () => {
|
|
36
|
+
expect(() => validateReadPath('test.svg', ['.svg', '.png'])).not.toThrow();
|
|
37
|
+
expect(() => validateReadPath('test.png', ['.svg', '.png'])).not.toThrow();
|
|
38
|
+
});
|
|
39
|
+
it('should be case-insensitive for extensions', () => {
|
|
40
|
+
expect(() => validateReadPath('test.SVG', ['.svg'])).not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe('validateWritePath', () => {
|
|
44
|
+
it('should validate file extensions', () => {
|
|
45
|
+
const writePath = path.join(tempDir, 'test.png');
|
|
46
|
+
expect(() => validateWritePath(writePath, ['.png'])).not.toThrow();
|
|
47
|
+
expect(() => validateWritePath(writePath, ['.svg'])).toThrow('File extension .png is not allowed');
|
|
48
|
+
});
|
|
49
|
+
it('should create directory if it does not exist', () => {
|
|
50
|
+
const newDir = path.join(tempDir, 'new-dir');
|
|
51
|
+
const writePath = path.join(newDir, 'test.png');
|
|
52
|
+
expect(fs.existsSync(newDir)).toBe(false);
|
|
53
|
+
validateWritePath(writePath, ['.png']);
|
|
54
|
+
expect(fs.existsSync(newDir)).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it('should restrict writes to base directory when provided', () => {
|
|
57
|
+
const baseDir = tempDir;
|
|
58
|
+
const validPath = path.join(baseDir, 'test.png');
|
|
59
|
+
expect(() => validateWritePath(validPath, ['.png'], baseDir)).not.toThrow();
|
|
60
|
+
expect(() => validateWritePath('/tmp/test.png', ['.png'], baseDir)).toThrow('Path is outside the allowed directory');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=path-validation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-validation.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/path-validation.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation utilities to prevent path traversal attacks
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Validates and normalizes a file path to prevent directory traversal attacks
|
|
6
|
+
*
|
|
7
|
+
* @param filePath - The file path to validate
|
|
8
|
+
* @param baseDirectory - Optional base directory to restrict paths to
|
|
9
|
+
* @returns Normalized absolute path
|
|
10
|
+
* @throws Error if path is invalid or contains traversal sequences
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateFilePath(filePath: string, baseDirectory?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Validates that a file path is safe for reading
|
|
15
|
+
*
|
|
16
|
+
* @param filePath - The file path to validate
|
|
17
|
+
* @param allowedExtensions - Optional array of allowed file extensions (e.g., ['.svg', '.png'])
|
|
18
|
+
* @returns Normalized absolute path
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateReadPath(filePath: string, allowedExtensions?: string[]): string;
|
|
21
|
+
/**
|
|
22
|
+
* Validates that a file path is safe for writing
|
|
23
|
+
*
|
|
24
|
+
* @param filePath - The file path to validate
|
|
25
|
+
* @param allowedExtensions - Optional array of allowed file extensions
|
|
26
|
+
* @param baseDirectory - Optional base directory to restrict writes to
|
|
27
|
+
* @returns Normalized absolute path
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateWritePath(filePath: string, allowedExtensions?: string[], baseDirectory?: string): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path validation utilities to prevent path traversal attacks
|
|
3
|
+
*/
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
/**
|
|
7
|
+
* Maximum allowed path length to prevent buffer overflow attacks
|
|
8
|
+
*/
|
|
9
|
+
const MAX_PATH_LENGTH = 4096;
|
|
10
|
+
/**
|
|
11
|
+
* Validates and normalizes a file path to prevent directory traversal attacks
|
|
12
|
+
*
|
|
13
|
+
* @param filePath - The file path to validate
|
|
14
|
+
* @param baseDirectory - Optional base directory to restrict paths to
|
|
15
|
+
* @returns Normalized absolute path
|
|
16
|
+
* @throws Error if path is invalid or contains traversal sequences
|
|
17
|
+
*/
|
|
18
|
+
export function validateFilePath(filePath, baseDirectory) {
|
|
19
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
20
|
+
throw new Error('File path must be a non-empty string');
|
|
21
|
+
}
|
|
22
|
+
// Check path length to prevent buffer overflow attacks
|
|
23
|
+
if (filePath.length > MAX_PATH_LENGTH) {
|
|
24
|
+
throw new Error(`Path length exceeds maximum allowed length of ${MAX_PATH_LENGTH} characters`);
|
|
25
|
+
}
|
|
26
|
+
// Check for null bytes which can be used in path traversal attacks
|
|
27
|
+
if (filePath.includes('\0')) {
|
|
28
|
+
throw new Error('Path contains null bytes which are not allowed');
|
|
29
|
+
}
|
|
30
|
+
// Check for path traversal sequences BEFORE normalization
|
|
31
|
+
// This catches attempts like ../, ..\, etc.
|
|
32
|
+
if (filePath.includes('..')) {
|
|
33
|
+
throw new Error('Path traversal detected: paths containing ".." are not allowed');
|
|
34
|
+
}
|
|
35
|
+
// Resolve to absolute path first, then normalize
|
|
36
|
+
// This ensures that . sequences are resolved
|
|
37
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
38
|
+
? path.resolve(filePath)
|
|
39
|
+
: path.resolve(process.cwd(), filePath);
|
|
40
|
+
// Normalize the resolved path
|
|
41
|
+
const normalizedPath = path.normalize(absolutePath);
|
|
42
|
+
// Additional safety check: ensure the normalized path doesn't contain .. after resolution
|
|
43
|
+
if (normalizedPath.includes('..')) {
|
|
44
|
+
throw new Error('Path traversal detected: paths containing ".." are not allowed');
|
|
45
|
+
}
|
|
46
|
+
// If base directory is provided, ensure the path is within it
|
|
47
|
+
if (baseDirectory) {
|
|
48
|
+
const baseDir = path.isAbsolute(baseDirectory)
|
|
49
|
+
? path.resolve(path.normalize(baseDirectory))
|
|
50
|
+
: path.resolve(process.cwd(), baseDirectory);
|
|
51
|
+
const resolvedPath = path.resolve(normalizedPath);
|
|
52
|
+
// Use path.relative to check if path is within base directory
|
|
53
|
+
const relativePath = path.relative(baseDir, resolvedPath);
|
|
54
|
+
// If relative path starts with .. or is absolute, it's outside the base directory
|
|
55
|
+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
56
|
+
throw new Error('Path is outside the allowed directory');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return normalizedPath;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validates that a file path is safe for reading
|
|
63
|
+
*
|
|
64
|
+
* @param filePath - The file path to validate
|
|
65
|
+
* @param allowedExtensions - Optional array of allowed file extensions (e.g., ['.svg', '.png'])
|
|
66
|
+
* @returns Normalized absolute path
|
|
67
|
+
*/
|
|
68
|
+
export function validateReadPath(filePath, allowedExtensions) {
|
|
69
|
+
const validatedPath = validateFilePath(filePath);
|
|
70
|
+
if (allowedExtensions && allowedExtensions.length > 0) {
|
|
71
|
+
const ext = path.extname(validatedPath).toLowerCase();
|
|
72
|
+
if (!allowedExtensions.includes(ext)) {
|
|
73
|
+
throw new Error(`File extension ${ext} is not allowed. Allowed extensions: ${allowedExtensions.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return validatedPath;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Validates that a file path is safe for writing
|
|
80
|
+
*
|
|
81
|
+
* @param filePath - The file path to validate
|
|
82
|
+
* @param allowedExtensions - Optional array of allowed file extensions
|
|
83
|
+
* @param baseDirectory - Optional base directory to restrict writes to
|
|
84
|
+
* @returns Normalized absolute path
|
|
85
|
+
*/
|
|
86
|
+
export function validateWritePath(filePath, allowedExtensions, baseDirectory) {
|
|
87
|
+
const validatedPath = validateFilePath(filePath, baseDirectory);
|
|
88
|
+
if (allowedExtensions && allowedExtensions.length > 0) {
|
|
89
|
+
const ext = path.extname(validatedPath).toLowerCase();
|
|
90
|
+
if (!allowedExtensions.includes(ext)) {
|
|
91
|
+
throw new Error(`File extension ${ext} is not allowed. Allowed extensions: ${allowedExtensions.join(', ')}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Ensure the directory exists
|
|
95
|
+
const dir = path.dirname(validatedPath);
|
|
96
|
+
if (!fs.existsSync(dir)) {
|
|
97
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
return validatedPath;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=path-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-validation.js","sourceRoot":"","sources":["../../src/utils/path-validation.ts"],"names":[],"mappings":"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"}
|
package/lib/analyze/paths.js
CHANGED
|
@@ -9,15 +9,29 @@ exports.getPathStatistics = getPathStatistics;
|
|
|
9
9
|
var jsdom_1 = require("jsdom");
|
|
10
10
|
var element_1 = require("../core/element");
|
|
11
11
|
var validation_1 = require("../utils/validation");
|
|
12
|
+
/**
|
|
13
|
+
* Maximum allowed path data length to prevent ReDoS attacks
|
|
14
|
+
*/
|
|
15
|
+
var MAX_PATH_DATA_LENGTH = 100000; // 100KB should be sufficient for most SVG paths
|
|
12
16
|
/**
|
|
13
17
|
* Parses the 'd' attribute of a path element into command objects
|
|
14
18
|
*/
|
|
15
19
|
function parsePathData(pathData) {
|
|
20
|
+
// Validate input length to prevent ReDoS attacks
|
|
21
|
+
if (pathData.length > MAX_PATH_DATA_LENGTH) {
|
|
22
|
+
throw new Error("Path data length exceeds maximum allowed length of ".concat(MAX_PATH_DATA_LENGTH, " characters"));
|
|
23
|
+
}
|
|
16
24
|
var commands = [];
|
|
17
25
|
// Split by path commands (M, L, H, V, C, S, Q, T, A, Z)
|
|
18
26
|
var commandRegex = /([MmLlHhVvCcSsQqTtAaZz])((?:\s*-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?\s*,?\s*)*)/g;
|
|
19
27
|
var match;
|
|
28
|
+
var iterations = 0;
|
|
29
|
+
var MAX_ITERATIONS = 10000; // Prevent infinite loops
|
|
20
30
|
while ((match = commandRegex.exec(pathData)) !== null) {
|
|
31
|
+
iterations++;
|
|
32
|
+
if (iterations > MAX_ITERATIONS) {
|
|
33
|
+
throw new Error('Path parsing exceeded maximum iterations. Path data may be malformed.');
|
|
34
|
+
}
|
|
21
35
|
var type = match[1];
|
|
22
36
|
var paramsStr = match[2].trim();
|
|
23
37
|
if (type.toLowerCase() === 'z') {
|
package/lib/analyze/paths.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/analyze/paths.ts"],"names":[],"mappings":";AAAA;;GAEG;;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/analyze/paths.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAeH,sCAoCC;AAKD,oCA4BC;AAKD,8CAuBC;AA9GD,+BAA8B;AAC9B,2CAA+C;AAC/C,kDAA0E;AAG1E;;GAEG;AACH,IAAM,oBAAoB,GAAG,MAAM,CAAC,CAAC,gDAAgD;AAErF;;GAEG;AACH,SAAgB,aAAa,CAAC,QAAgB;IAC5C,iDAAiD;IACjD,IAAI,QAAQ,CAAC,MAAM,GAAG,oBAAoB,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CAAC,6DAAsD,oBAAoB,gBAAa,CAAC,CAAC;IAC3G,CAAC;IAED,IAAM,QAAQ,GAAkB,EAAE,CAAC;IAEnC,wDAAwD;IACxD,IAAM,YAAY,GAAG,6EAA6E,CAAC;IACnG,IAAI,KAAK,CAAC;IACV,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAM,cAAc,GAAG,KAAK,CAAC,CAAC,yBAAyB;IAEvD,OAAO,CAAC,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACtD,UAAU,EAAE,CAAC;QACb,IAAI,UAAU,GAAG,cAAc,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,uEAAuE,CAAC,CAAC;QAC3F,CAAC;QACD,IAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAElC,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,MAAA,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;QACtC,CAAC;aAAM,IAAI,SAAS,EAAE,CAAC;YACrB,IAAM,MAAM,GAAG,SAAS;iBACrB,KAAK,CAAC,QAAQ,CAAC;iBACf,MAAM,CAAC,UAAA,CAAC,IAAI,OAAA,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAf,CAAe,CAAC;iBAC5B,GAAG,CAAC,MAAM,CAAC;iBACX,MAAM,CAAC,UAAA,CAAC,IAAI,OAAA,CAAC,KAAK,CAAC,CAAC,CAAC,EAAT,CAAS,CAAC,CAAC;YAE1B,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,MAAA,EAAE,MAAM,QAAA,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,UAA4B;IACvD,IAAM,SAAS,GAAG,IAAA,6BAAgB,EAAC,UAAU,CAAC;QAC5C,CAAC,CAAC,UAAU;QACZ,CAAC,CAAC,IAAA,sBAAY,EAAC,UAAU,CAAC,CAAC;IAE7B,IAAM,GAAG,GAAG,IAAI,aAAK,CAAC,SAAS,EAAE;QAC/B,WAAW,EAAE,eAAe;KAC7B,CAAC,CAAC;IAEH,IAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;IACrC,IAAM,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAEjD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,IAAM,KAAK,GAAG,UAAU,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAClD,IAAM,YAAY,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEtD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,UAAC,IAAI,EAAE,KAAK;QACpC,IAAM,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC;YACN,IAAM,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,eAAQ,KAAK,CAAE,CAAC;YACtD,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,SAAgB,iBAAiB,CAAC,UAA4B;IAK5D,IAAM,YAAY,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IAE9C,IAAI,aAAa,GAAG,CAAC,CAAC;IACtB,IAAM,YAAY,GAA2B,EAAE,CAAC;IAEhD,YAAY,CAAC,OAAO,CAAC,UAAA,QAAQ;QAC3B,aAAa,IAAI,QAAQ,CAAC,MAAM,CAAC;QACjC,QAAQ,CAAC,OAAO,CAAC,UAAA,GAAG;YAClB,IAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,UAAU,EAAE,YAAY,CAAC,IAAI;QAC7B,aAAa,eAAA;QACb,YAAY,cAAA;KACb,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
var diff_1 = require("../diff");
|
|
43
|
+
var fs_1 = __importDefault(require("fs"));
|
|
44
|
+
var path_1 = __importDefault(require("path"));
|
|
45
|
+
describe('Image Diff', function () {
|
|
46
|
+
var fixturesDir = path_1.default.join(__dirname, '../../../test-fixtures');
|
|
47
|
+
var test1Path = path_1.default.join(fixturesDir, 'test1.svg');
|
|
48
|
+
var test2Path = path_1.default.join(fixturesDir, 'test2.svg');
|
|
49
|
+
var outputDir = path_1.default.join(fixturesDir, 'diff-output');
|
|
50
|
+
var diffImagePath = path_1.default.join(outputDir, 'diff-test1-test2.png');
|
|
51
|
+
beforeAll(function () {
|
|
52
|
+
// Ensure output directory exists
|
|
53
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
54
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
afterAll(function () {
|
|
58
|
+
// Clean up: optionally remove the diff image after tests
|
|
59
|
+
// Uncomment the following line if you want to clean up after tests
|
|
60
|
+
// if (fs.existsSync(diffImagePath)) {
|
|
61
|
+
// fs.unlinkSync(diffImagePath);
|
|
62
|
+
// }
|
|
63
|
+
});
|
|
64
|
+
it('should generate diff image between two similar but different SVGs', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
65
|
+
var result, savedFile;
|
|
66
|
+
return __generator(this, function (_a) {
|
|
67
|
+
switch (_a.label) {
|
|
68
|
+
case 0:
|
|
69
|
+
// Verify test fixtures exist
|
|
70
|
+
expect(fs_1.default.existsSync(test1Path)).toBe(true);
|
|
71
|
+
expect(fs_1.default.existsSync(test2Path)).toBe(true);
|
|
72
|
+
return [4 /*yield*/, (0, diff_1.diffImages)(test1Path, test2Path, diffImagePath, 0.1)];
|
|
73
|
+
case 1:
|
|
74
|
+
result = _a.sent();
|
|
75
|
+
// Verify diff image was created
|
|
76
|
+
expect(fs_1.default.existsSync(diffImagePath)).toBe(true);
|
|
77
|
+
// Verify result contains expected properties
|
|
78
|
+
expect(result).toHaveProperty('diffPngBuffer');
|
|
79
|
+
expect(result).toHaveProperty('numDiffPixels');
|
|
80
|
+
expect(result.diffPngBuffer).toBeInstanceOf(Buffer);
|
|
81
|
+
expect(typeof result.numDiffPixels).toBe('number');
|
|
82
|
+
// Since the SVGs are different, there should be some differences
|
|
83
|
+
expect(result.numDiffPixels).toBeGreaterThan(0);
|
|
84
|
+
savedFile = fs_1.default.readFileSync(diffImagePath);
|
|
85
|
+
expect(savedFile.equals(result.diffPngBuffer)).toBe(true);
|
|
86
|
+
// Verify file size is reasonable (not empty)
|
|
87
|
+
expect(savedFile.length).toBeGreaterThan(0);
|
|
88
|
+
console.log("Diff image saved to: ".concat(diffImagePath));
|
|
89
|
+
console.log("Number of different pixels: ".concat(result.numDiffPixels));
|
|
90
|
+
return [2 /*return*/];
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}); });
|
|
94
|
+
it('should handle identical SVGs (no differences)', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
95
|
+
var identicalDiffPath, result;
|
|
96
|
+
return __generator(this, function (_a) {
|
|
97
|
+
switch (_a.label) {
|
|
98
|
+
case 0:
|
|
99
|
+
identicalDiffPath = path_1.default.join(outputDir, 'diff-identical.png');
|
|
100
|
+
return [4 /*yield*/, (0, diff_1.diffImages)(test1Path, test1Path, identicalDiffPath, 0.1)];
|
|
101
|
+
case 1:
|
|
102
|
+
result = _a.sent();
|
|
103
|
+
// Should have no differences
|
|
104
|
+
expect(result.numDiffPixels).toBe(0);
|
|
105
|
+
expect(fs_1.default.existsSync(identicalDiffPath)).toBe(true);
|
|
106
|
+
console.log("Identical comparison diff saved to: ".concat(identicalDiffPath));
|
|
107
|
+
console.log("Number of different pixels: ".concat(result.numDiffPixels));
|
|
108
|
+
return [2 /*return*/];
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}); });
|
|
112
|
+
it('should generate diff without saving to file', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
113
|
+
var result;
|
|
114
|
+
return __generator(this, function (_a) {
|
|
115
|
+
switch (_a.label) {
|
|
116
|
+
case 0: return [4 /*yield*/, (0, diff_1.diffImages)(test1Path, test2Path, undefined, 0.1)];
|
|
117
|
+
case 1:
|
|
118
|
+
result = _a.sent();
|
|
119
|
+
// Should still return result even without file path
|
|
120
|
+
expect(result).toHaveProperty('diffPngBuffer');
|
|
121
|
+
expect(result).toHaveProperty('numDiffPixels');
|
|
122
|
+
expect(result.numDiffPixels).toBeGreaterThan(0);
|
|
123
|
+
return [2 /*return*/];
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}); });
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=diff.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.test.js","sourceRoot":"","sources":["../../../src/compare/__tests__/diff.test.ts"],"names":[],"mappings":"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"}
|
package/lib/compare/diff.js
CHANGED
|
@@ -50,6 +50,7 @@ var path_1 = __importDefault(require("path"));
|
|
|
50
50
|
var sharp_1 = __importDefault(require("sharp"));
|
|
51
51
|
var pngjs_1 = require("pngjs");
|
|
52
52
|
var pixelmatch_1 = __importDefault(require("pixelmatch"));
|
|
53
|
+
var path_validation_1 = require("../utils/path-validation");
|
|
53
54
|
/**
|
|
54
55
|
* Compares two PNG images at the pixel level and generates a diff image
|
|
55
56
|
*/
|
|
@@ -74,21 +75,23 @@ function pixelLevelDiff(pngA, pngB, threshold) {
|
|
|
74
75
|
*/
|
|
75
76
|
function diffImages(pathA_1, pathB_1, diffFilePath_1) {
|
|
76
77
|
return __awaiter(this, arguments, void 0, function (pathA, pathB, diffFilePath, threshold) {
|
|
77
|
-
var pngA, pngB, result, ext;
|
|
78
|
+
var validatedPathA, validatedPathB, pngA, pngB, result, ext, validatedWritePath;
|
|
78
79
|
if (threshold === void 0) { threshold = 0.1; }
|
|
79
80
|
return __generator(this, function (_a) {
|
|
80
81
|
switch (_a.label) {
|
|
81
82
|
case 0:
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
validatedPathA = (0, path_validation_1.validateReadPath)(pathA, ['.svg', '.png', '.jpg', '.jpeg', '.webp']);
|
|
84
|
+
validatedPathB = (0, path_validation_1.validateReadPath)(pathB, ['.svg', '.png', '.jpg', '.jpeg', '.webp']);
|
|
85
|
+
if (!fs_1.default.existsSync(validatedPathA)) {
|
|
86
|
+
throw new Error("File not found: ".concat(validatedPathA));
|
|
84
87
|
}
|
|
85
|
-
if (!fs_1.default.existsSync(
|
|
86
|
-
throw new Error("File not found: ".concat(
|
|
88
|
+
if (!fs_1.default.existsSync(validatedPathB)) {
|
|
89
|
+
throw new Error("File not found: ".concat(validatedPathB));
|
|
87
90
|
}
|
|
88
|
-
return [4 /*yield*/, (0, sharp_1.default)(
|
|
91
|
+
return [4 /*yield*/, (0, sharp_1.default)(validatedPathA).png().toBuffer()];
|
|
89
92
|
case 1:
|
|
90
93
|
pngA = _a.sent();
|
|
91
|
-
return [4 /*yield*/, (0, sharp_1.default)(
|
|
94
|
+
return [4 /*yield*/, (0, sharp_1.default)(validatedPathB).png().toBuffer()];
|
|
92
95
|
case 2:
|
|
93
96
|
pngB = _a.sent();
|
|
94
97
|
result = pixelLevelDiff(pngA, pngB, threshold);
|
|
@@ -97,7 +100,8 @@ function diffImages(pathA_1, pathB_1, diffFilePath_1) {
|
|
|
97
100
|
if (!ext) {
|
|
98
101
|
throw new Error('Diff file path must have a file extension');
|
|
99
102
|
}
|
|
100
|
-
|
|
103
|
+
validatedWritePath = (0, path_validation_1.validateWritePath)(diffFilePath, ['.png']);
|
|
104
|
+
fs_1.default.writeFileSync(validatedWritePath, result.diffPngBuffer);
|
|
101
105
|
}
|
|
102
106
|
return [2 /*return*/, result];
|
|
103
107
|
}
|
package/lib/compare/diff.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/compare/diff.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/compare/diff.ts"],"names":[],"mappings":"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"}
|
package/lib/convert/image.js
CHANGED
|
@@ -47,6 +47,7 @@ exports.svg2Png = svg2Png;
|
|
|
47
47
|
var fs_1 = __importDefault(require("fs"));
|
|
48
48
|
var sharp_1 = __importDefault(require("sharp"));
|
|
49
49
|
var dimensions_1 = require("../core/dimensions");
|
|
50
|
+
var path_validation_1 = require("../utils/path-validation");
|
|
50
51
|
/**
|
|
51
52
|
* Converts an SVG file to PNG format
|
|
52
53
|
*
|
|
@@ -56,21 +57,22 @@ var dimensions_1 = require("../core/dimensions");
|
|
|
56
57
|
*/
|
|
57
58
|
function svgToImage(svgPath_1) {
|
|
58
59
|
return __awaiter(this, arguments, void 0, function (svgPath, options) {
|
|
59
|
-
var _a, scale, _b, format, _c, quality, svgContent, dimensions, width, height, sharpInstance;
|
|
60
|
+
var _a, scale, _b, format, _c, quality, validatedPath, svgContent, dimensions, width, height, sharpInstance;
|
|
60
61
|
if (options === void 0) { options = {}; }
|
|
61
62
|
return __generator(this, function (_d) {
|
|
62
63
|
_a = options.scale, scale = _a === void 0 ? 2 : _a, _b = options.format, format = _b === void 0 ? 'png' : _b, _c = options.quality, quality = _c === void 0 ? 90 : _c;
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
validatedPath = (0, path_validation_1.validateReadPath)(svgPath, ['.svg']);
|
|
65
|
+
if (!fs_1.default.existsSync(validatedPath)) {
|
|
66
|
+
throw new Error("SVG file not found: ".concat(validatedPath));
|
|
65
67
|
}
|
|
66
|
-
svgContent = fs_1.default.readFileSync(
|
|
68
|
+
svgContent = fs_1.default.readFileSync(validatedPath, 'utf8');
|
|
67
69
|
dimensions = (0, dimensions_1.getSVGDimensions)(svgContent);
|
|
68
70
|
if (!dimensions.width || !dimensions.height) {
|
|
69
71
|
throw new Error('SVG must have width and height or viewBox');
|
|
70
72
|
}
|
|
71
73
|
width = scale * dimensions.width;
|
|
72
74
|
height = scale * dimensions.height;
|
|
73
|
-
sharpInstance = (0, sharp_1.default)(
|
|
75
|
+
sharpInstance = (0, sharp_1.default)(validatedPath).resize(width, height);
|
|
74
76
|
switch (format) {
|
|
75
77
|
case 'jpg':
|
|
76
78
|
case 'jpeg':
|
|
@@ -95,7 +97,7 @@ function svgToImage(svgPath_1) {
|
|
|
95
97
|
*/
|
|
96
98
|
function svg2Png(svgPath, pngPath, scale) {
|
|
97
99
|
return __awaiter(this, void 0, void 0, function () {
|
|
98
|
-
var buffer;
|
|
100
|
+
var buffer, validatedWritePath;
|
|
99
101
|
return __generator(this, function (_a) {
|
|
100
102
|
switch (_a.label) {
|
|
101
103
|
case 0:
|
|
@@ -106,7 +108,8 @@ function svg2Png(svgPath, pngPath, scale) {
|
|
|
106
108
|
case 1:
|
|
107
109
|
buffer = _a.sent();
|
|
108
110
|
if (pngPath) {
|
|
109
|
-
|
|
111
|
+
validatedWritePath = (0, path_validation_1.validateWritePath)(pngPath, ['.png']);
|
|
112
|
+
fs_1.default.writeFileSync(validatedWritePath, buffer);
|
|
110
113
|
return [2 /*return*/];
|
|
111
114
|
}
|
|
112
115
|
return [2 /*return*/, buffer];
|
package/lib/convert/image.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/convert/image.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/convert/image.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeH,gCAwCC;AAOD,0BAmBC;AA/ED,0CAAoB;AACpB,gDAA0B;AAC1B,iDAAsD;AAEtD,4DAA+E;AAE/E;;;;;;GAMG;AACH,SAAsB,UAAU;wDAC9B,OAAe,EACf,OAA+B;;QAA/B,wBAAA,EAAA,YAA+B;;YAEvB,KAA4C,OAAO,MAA1C,EAAT,KAAK,mBAAG,CAAC,KAAA,EAAE,KAAiC,OAAO,OAA1B,EAAd,MAAM,mBAAG,KAAK,KAAA,EAAE,KAAiB,OAAO,QAAZ,EAAZ,OAAO,mBAAG,EAAE,KAAA,CAAa;YAGtD,aAAa,GAAG,IAAA,kCAAgB,EAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;YAE1D,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,8BAAuB,aAAa,CAAE,CAAC,CAAC;YAC1D,CAAC;YAEK,UAAU,GAAG,YAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YACpD,UAAU,GAAG,IAAA,6BAAgB,EAAC,UAAU,CAAC,CAAC;YAEhD,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;gBAC5C,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAC/D,CAAC;YAEK,KAAK,GAAG,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC;YACjC,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC,MAAM,CAAC;YAErC,aAAa,GAAG,IAAA,eAAK,EAAC,aAAa,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAE/D,QAAQ,MAAM,EAAE,CAAC;gBACf,KAAK,KAAK,CAAC;gBACX,KAAK,MAAM;oBACT,aAAa,GAAG,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,SAAA,EAAE,CAAC,CAAC;oBAChD,MAAM;gBACR,KAAK,MAAM;oBACT,aAAa,GAAG,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,SAAA,EAAE,CAAC,CAAC;oBAChD,MAAM;gBACR,KAAK,KAAK,CAAC;gBACX;oBACE,aAAa,GAAG,aAAa,CAAC,GAAG,EAAE,CAAC;oBACpC,MAAM;YACV,CAAC;YAED,sBAAO,aAAa,CAAC,QAAQ,EAAE,EAAC;;;CACjC;AAED;;;;GAIG;AACH,SAAsB,OAAO,CAC3B,OAAe,EACf,OAAgB,EAChB,KAAc;;;;;;oBAEd,IAAI,CAAC,OAAO,EAAE,CAAC;wBACb,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;oBAC/C,CAAC;oBAEc,qBAAM,UAAU,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,aAAL,KAAK,cAAL,KAAK,GAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAA;;oBAAxE,MAAM,GAAG,SAA+D;oBAE9E,IAAI,OAAO,EAAE,CAAC;wBAEN,kBAAkB,GAAG,IAAA,mCAAiB,EAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;wBAChE,YAAE,CAAC,aAAa,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;wBAC7C,sBAAO;oBACT,CAAC;oBAED,sBAAO,MAAM,EAAC;;;;CACf"}
|