From 6d9538c5d2b2f68e3b8f010f7e92c8498f9d2f72 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 31 Oct 2025 18:27:56 +0000 Subject: [PATCH] feat: implement feed and validation utilities for smartfeed --- ...rtfeed.classes.feed.ts => classes.feed.ts} | 0 ...sses.smartfeed.ts => classes.smartfeed.ts} | 0 ts/{smartfeed.plugins.ts => plugins.ts} | 0 ts/smartfeed.validation.ts | 167 ++++++++++++++++++ 4 files changed, 167 insertions(+) rename ts/{smartfeed.classes.feed.ts => classes.feed.ts} (100%) rename ts/{smartfeed.classes.smartfeed.ts => classes.smartfeed.ts} (100%) rename ts/{smartfeed.plugins.ts => plugins.ts} (100%) create mode 100644 ts/smartfeed.validation.ts diff --git a/ts/smartfeed.classes.feed.ts b/ts/classes.feed.ts similarity index 100% rename from ts/smartfeed.classes.feed.ts rename to ts/classes.feed.ts diff --git a/ts/smartfeed.classes.smartfeed.ts b/ts/classes.smartfeed.ts similarity index 100% rename from ts/smartfeed.classes.smartfeed.ts rename to ts/classes.smartfeed.ts diff --git a/ts/smartfeed.plugins.ts b/ts/plugins.ts similarity index 100% rename from ts/smartfeed.plugins.ts rename to ts/plugins.ts diff --git a/ts/smartfeed.validation.ts b/ts/smartfeed.validation.ts new file mode 100644 index 0000000..522b074 --- /dev/null +++ b/ts/smartfeed.validation.ts @@ -0,0 +1,167 @@ +/** + * Validation utilities for smartfeed + * Provides security, validation, and sanitization functions + */ + +/** + * Validates that a URL is absolute and optionally prefers HTTPS + * @param url - The URL to validate + * @param preferHttps - Whether to warn if not HTTPS + * @returns Validated URL + * @throws Error if URL is invalid or not absolute + */ +export function validateUrl(url: string, preferHttps: boolean = true): string { + if (!url || typeof url !== 'string') { + throw new Error('URL must be a non-empty string'); + } + + // Check if URL is absolute + try { + const parsedUrl = new URL(url); + + // Validate protocol + if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') { + throw new Error(`Invalid URL protocol: ${parsedUrl.protocol}. Only http and https are supported.`); + } + + // Prefer HTTPS for security + if (preferHttps && parsedUrl.protocol === 'http:') { + console.warn(`Warning: URL '${url}' uses HTTP instead of HTTPS. HTTPS is recommended for security and privacy.`); + } + + return url; + } catch (error) { + if (error instanceof TypeError) { + throw new Error(`Invalid or relative URL: ${url}. All URLs must be absolute (e.g., https://example.com/path)`); + } + throw error; + } +} + +/** + * Sanitizes HTML content to prevent XSS attacks + * This is a basic implementation - for production use, consider a dedicated library + * @param content - The content to sanitize + * @returns Sanitized content + */ +export function sanitizeContent(content: string): string { + if (!content || typeof content !== 'string') { + return ''; + } + + // Basic HTML entity encoding to prevent XSS + return content + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/\//g, '/'); +} + +/** + * Validates that required fields are present and non-empty + * @param obj - Object to validate + * @param requiredFields - Array of required field names + * @param objectName - Name of object for error messages + * @throws Error if validation fails + */ +export function validateRequiredFields( + obj: Record, + requiredFields: string[], + objectName: string = 'Object' +): void { + const missingFields: string[] = []; + + for (const field of requiredFields) { + if (!obj[field] || (typeof obj[field] === 'string' && obj[field].trim() === '')) { + missingFields.push(field); + } + } + + if (missingFields.length > 0) { + throw new Error( + `${objectName} validation failed: Missing or empty required fields: ${missingFields.join(', ')}` + ); + } +} + +/** + * Validates an email address format + * @param email - Email to validate + * @returns Validated email + * @throws Error if email is invalid + */ +export function validateEmail(email: string): string { + if (!email || typeof email !== 'string') { + throw new Error('Email must be a non-empty string'); + } + + // Basic email validation regex + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + throw new Error(`Invalid email address: ${email}`); + } + + return email; +} + +/** + * Validates a timestamp + * @param timestamp - Timestamp to validate (milliseconds since epoch) + * @returns Validated timestamp + * @throws Error if timestamp is invalid + */ +export function validateTimestamp(timestamp: number): number { + if (typeof timestamp !== 'number' || isNaN(timestamp)) { + throw new Error('Timestamp must be a valid number'); + } + + if (timestamp < 0) { + throw new Error('Timestamp cannot be negative'); + } + + // Check if timestamp is reasonable (not in far future) + const now = Date.now(); + const tenYearsFromNow = now + (10 * 365 * 24 * 60 * 60 * 1000); + + if (timestamp > tenYearsFromNow) { + console.warn(`Warning: Timestamp ${timestamp} is more than 10 years in the future`); + } + + return timestamp; +} + +/** + * Validates that a domain is properly formatted + * @param domain - Domain to validate + * @returns Validated domain + * @throws Error if domain is invalid + */ +export function validateDomain(domain: string): string { + if (!domain || typeof domain !== 'string') { + throw new Error('Domain must be a non-empty string'); + } + + // Basic domain validation + const domainRegex = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i; + if (!domainRegex.test(domain)) { + throw new Error(`Invalid domain format: ${domain}`); + } + + return domain; +} + +/** + * Creates a validation error with context + * @param message - Error message + * @param context - Additional context information + * @returns Error object + */ +export function createValidationError(message: string, context?: Record): Error { + const error = new Error(message); + if (context) { + Object.assign(error, { context }); + } + return error; +}