UNPKG

@vizzly-testing/cli

Version:

Visual review platform for UI developers and designers

180 lines (161 loc) 5.71 kB
/** * @module image-input-detector * @description Utilities for detecting whether a string is a base64 image or a file path */ /** * Check if a string is valid base64 encoded data * * Base64 strings consist only of: * - A-Z, a-z, 0-9, +, / * - Optional padding with = at the end * - Must be in groups of 4 characters (with optional 2-3 char group at end with padding) * * @param {string} str - String to check * @returns {boolean} True if the string appears to be valid base64 * * @example * isBase64('ZmFrZS1wbmctZGF0YQ==') // true * isBase64('./screenshot.png') // false * isBase64('/absolute/path.png') // false */ export function isBase64(str) { if (typeof str !== 'string' || str.length === 0) { return false; } // Strip data URI prefix if present (e.g., data:image/png;base64,...) let base64Content = str; if (str.startsWith('data:')) { let match = str.match(/^data:[a-zA-Z0-9+/.-]+;base64,(.+)$/); if (!match) { return false; // Has data: prefix but invalid format } base64Content = match[1]; } // Quick check: base64 only contains these characters // Use a simple character class check instead of a complex regex to avoid // catastrophic backtracking on large strings if (!/^[A-Za-z0-9+/=]+$/.test(base64Content)) { return false; } // Check length is valid (must be multiple of 4, accounting for padding) let len = base64Content.length; if (len % 4 !== 0) { return false; } // Check padding is valid (only at end, max 2 = chars) let paddingMatch = base64Content.match(/=+$/); if (paddingMatch && paddingMatch[0].length > 2) { return false; } return true; } /** * Check if a string looks like a file path * * Detects common file path patterns across platforms: * - Relative paths: ./file.png, ../file.png, subdirectory/file.png * - Absolute Unix paths: /absolute/path/file.png * - Absolute Windows paths: C:\path\file.png, C:/path/file.png * - File URIs: file:///path/to/file.png * - Paths with common image extensions * * Note: This is heuristic-based and doesn't check file existence * * @param {string} str - String to check * @returns {boolean} True if the string looks like a file path * * @example * looksLikeFilePath('./screenshot.png') // true * looksLikeFilePath('/abs/path/file.png') // true * looksLikeFilePath('C:\\Windows\\file.png') // true * looksLikeFilePath('ZmFrZS1wbmctZGF0YQ==') // false */ export function looksLikeFilePath(str) { if (typeof str !== 'string' || str.length === 0) { return false; } // 0. Length check - file paths are short, base64 screenshots are huge // Even the longest realistic file path is < 500 chars // This makes detection O(1) for large base64 strings // Use same threshold (1000) as detectImageInputType for consistency if (str.length > 1000) { return false; } // 1. Explicitly reject data URIs (they contain : and / which would match path patterns) if (str.startsWith('data:')) { return false; } // 2. Check for file:// URI scheme if (str.startsWith('file://')) { return true; } // 3. Windows absolute paths (C:\ or C:/) - base64 never starts with drive letter if (/^[A-Za-z]:[/\\]/.test(str)) { return true; } // 4. Relative path indicators (./ or ../) - base64 never starts with dot if (/^\.\.?[/\\]/.test(str)) { return true; } // 5. Check for common image file extensions // This is the safest check - base64 never ends with .png/.jpg/etc // Catches: /path/file.png, subdir/file.png, file.png if (/\.(png|jpe?g|gif|webp|bmp|svg|tiff?|ico)$/i.test(str)) { return true; } // Note: We intentionally don't check for bare "/" prefix or "/" anywhere // because JPEG base64 starts with "/9j/" which would false-positive // File paths without extensions are rare for images and will fall through // to base64 detection, which is acceptable for backwards compat return false; } /** * Detect the type of image input * * Determines whether a string input is: * - 'base64': Base64 encoded image data * - 'file-path': A file path (relative or absolute) * - 'unknown': Cannot determine (ambiguous or invalid) * * Strategy (optimized for performance): * 1. Check for data URI prefix first (O(1), definitive) * 2. Check file path patterns (O(1) prefix/suffix checks) * 3. For large non-path strings, assume base64 (skip expensive validation) * 4. Only run full base64 validation on small ambiguous strings * * This avoids O(n) regex validation on large screenshot buffers. * * @param {string} str - String to detect * @returns {'base64' | 'file-path' | 'unknown'} Detected input type * * @example * detectImageInputType('./screenshot.png') // 'file-path' * detectImageInputType('ZmFrZS1wbmctZGF0YQ==') // 'base64' * detectImageInputType('data:image/png;base64,iVBOR...') // 'base64' * detectImageInputType('C:\\path\\image.png') // 'file-path' * detectImageInputType('invalid!!!') // 'unknown' */ export function detectImageInputType(str) { if (typeof str !== 'string' || str.length === 0) { return 'unknown'; } // 1. Data URIs are definitively base64 (O(1) check) if (str.startsWith('data:')) { return 'base64'; } // 2. Check file path patterns (O(1) prefix/suffix checks) if (looksLikeFilePath(str)) { return 'file-path'; } // 3. For large strings that aren't file paths, assume base64 // Screenshots are typically 100KB+ as base64, file paths are <1KB // Skip expensive O(n) validation for large strings if (str.length > 1000) { return 'base64'; } // 4. Full validation only for small ambiguous strings if (isBase64(str)) { return 'base64'; } return 'unknown'; }