@nanggo/social-preview
Version:
Generate beautiful social media preview images from any URL
249 lines (248 loc) • 7.33 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateColor = validateColor;
const types_1 = require("../../types");
const security_1 = require("../../constants/security");
const text_1 = require("./text");
/**
* Validates CSS color values to prevent injection attacks.
* Accepts: hex colors, rgb/rgba, hsl/hsla, and named colors.
*/
function validateColor(color) {
// Sanitize control characters first
const sanitizedColor = (0, text_1.sanitizeControlChars)(color.trim());
// Security checks - reject dangerous patterns
if (!isSafeColorInput(sanitizedColor)) {
throw new types_1.PreviewGeneratorError(types_1.ErrorType.VALIDATION_ERROR, `Invalid color value: ${color}. Contains potentially dangerous characters or patterns.`);
}
// Hex color validation (#RGB, #RRGGBB, #RRGGBBAA)
const hexPattern = /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/;
if (hexPattern.test(sanitizedColor)) {
return sanitizedColor;
}
// RGB validation with proper range checking
const rgbMatch = sanitizedColor.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/);
if (rgbMatch) {
const [, r, g, b] = rgbMatch;
const red = parseInt(r, 10);
const green = parseInt(g, 10);
const blue = parseInt(b, 10);
if (red <= 255 && green <= 255 && blue <= 255) {
return sanitizedColor;
}
}
// RGBA validation with proper range checking
const rgbaMatch = sanitizedColor.match(/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0?\.\d+)\s*\)$/);
if (rgbaMatch) {
const [, r, g, b, a] = rgbaMatch;
const red = parseInt(r, 10);
const green = parseInt(g, 10);
const blue = parseInt(b, 10);
const alpha = parseFloat(a);
if (red <= 255 && green <= 255 && blue <= 255 && alpha >= 0 && alpha <= 1) {
return sanitizedColor;
}
}
// HSL validation with proper range checking
const hslMatch = sanitizedColor.match(/^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/);
if (hslMatch) {
const [, h, s, l] = hslMatch;
const hue = parseInt(h, 10);
const saturation = parseInt(s, 10);
const lightness = parseInt(l, 10);
if (hue <= 360 && saturation <= 100 && lightness <= 100) {
return sanitizedColor;
}
}
// HSLA validation with proper range checking
const hslaMatch = sanitizedColor.match(/^hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(0|1|0?\.\d+)\s*\)$/);
if (hslaMatch) {
const [, h, s, l, a] = hslaMatch;
const hue = parseInt(h, 10);
const saturation = parseInt(s, 10);
const lightness = parseInt(l, 10);
const alpha = parseFloat(a);
if (hue <= 360 && saturation <= 100 && lightness <= 100 && alpha >= 0 && alpha <= 1) {
return sanitizedColor;
}
}
// Named colors (CSS standard colors) - using Set for O(1) lookup
const namedColors = new Set([
'black',
'silver',
'gray',
'white',
'maroon',
'red',
'purple',
'fuchsia',
'green',
'lime',
'olive',
'yellow',
'navy',
'blue',
'teal',
'aqua',
'orange',
'aliceblue',
'antiquewhite',
'aquamarine',
'azure',
'beige',
'bisque',
'blanchedalmond',
'blueviolet',
'brown',
'burlywood',
'cadetblue',
'chartreuse',
'chocolate',
'coral',
'cornflowerblue',
'cornsilk',
'crimson',
'cyan',
'darkblue',
'darkcyan',
'darkgoldenrod',
'darkgray',
'darkgreen',
'darkgrey',
'darkkhaki',
'darkmagenta',
'darkolivegreen',
'darkorange',
'darkorchid',
'darkred',
'darksalmon',
'darkseagreen',
'darkslateblue',
'darkslategray',
'darkslategrey',
'darkturquoise',
'darkviolet',
'deeppink',
'deepskyblue',
'dimgray',
'dimgrey',
'dodgerblue',
'firebrick',
'floralwhite',
'forestgreen',
'gainsboro',
'ghostwhite',
'gold',
'goldenrod',
'greenyellow',
'grey',
'honeydew',
'hotpink',
'indianred',
'indigo',
'ivory',
'khaki',
'lavender',
'lavenderblush',
'lawngreen',
'lemonchiffon',
'lightblue',
'lightcoral',
'lightcyan',
'lightgoldenrodyellow',
'lightgray',
'lightgreen',
'lightgrey',
'lightpink',
'lightsalmon',
'lightseagreen',
'lightskyblue',
'lightslategray',
'lightslategrey',
'lightsteelblue',
'lightyellow',
'limegreen',
'linen',
'magenta',
'mediumaquamarine',
'mediumblue',
'mediumorchid',
'mediumpurple',
'mediumseagreen',
'mediumslateblue',
'mediumspringgreen',
'mediumturquoise',
'mediumvioletred',
'midnightblue',
'mintcream',
'mistyrose',
'moccasin',
'navajowhite',
'oldlace',
'olivedrab',
'orangered',
'orchid',
'palegoldenrod',
'palegreen',
'paleturquoise',
'palevioletred',
'papayawhip',
'peachpuff',
'peru',
'pink',
'plum',
'powderblue',
'rosybrown',
'royalblue',
'saddlebrown',
'salmon',
'sandybrown',
'seagreen',
'seashell',
'sienna',
'skyblue',
'slateblue',
'slategray',
'slategrey',
'snow',
'springgreen',
'steelblue',
'tan',
'thistle',
'tomato',
'turquoise',
'violet',
'wheat',
'whitesmoke',
'yellowgreen',
'transparent',
]);
if (namedColors.has(sanitizedColor.toLowerCase())) {
return sanitizedColor.toLowerCase();
}
// If validation fails, throw an error
throw new types_1.PreviewGeneratorError(types_1.ErrorType.VALIDATION_ERROR, `Invalid color value: ${color}. Please use a valid CSS color format.`);
}
function isSafeColorInput(color) {
// Maximum length check to prevent DoS
if (color.length > security_1.MAX_COLOR_LENGTH) {
return false;
}
// Check against dangerous CSS patterns
for (const pattern of security_1.DANGEROUS_CSS_PATTERNS) {
// Create new RegExp to avoid global flag state issues
const testPattern = new RegExp(pattern.source, pattern.flags);
if (testPattern.test(color)) {
return false;
}
}
// Additional checks for suspicious combinations
for (const pattern of security_1.SUSPICIOUS_PATTERNS) {
// Create new RegExp to avoid global flag state issues
const testPattern = new RegExp(pattern.source, pattern.flags);
if (testPattern.test(color)) {
return false;
}
}
return true;
}