generated from nhcarrigan/template
208c11d153
### Explanation _No response_ ### Issue _No response_ ### Attestations - [ ] I have read and agree to the [Code of Conduct](https://docs.nhcarrigan.com/community/coc/) - [ ] I have read and agree to the [Community Guidelines](https://docs.nhcarrigan.com/community/guide/). - [ ] My contribution complies with the [Contributor Covenant](https://docs.nhcarrigan.com/dev/covenant/). ### Dependencies - [ ] I have pinned the dependencies to a specific patch version. ### Style - [ ] I have run the linter and resolved any errors. - [ ] My pull request uses an appropriate title, matching the conventional commit standards. - [ ] My scope of feat/fix/chore/etc. correctly matches the nature of changes in my pull request. ### Tests - [ ] My contribution adds new code, and I have added tests to cover it. - [ ] My contribution modifies existing code, and I have updated the tests to reflect these changes. - [ ] All new and existing tests pass locally with my changes. - [ ] Code coverage remains at or above the configured threshold. ### Documentation _No response_ ### Versioning _No response_ Reviewed-on: #64
95 lines
2.1 KiB
TypeScript
95 lines
2.1 KiB
TypeScript
/**
|
|
* @copyright 2026 NHCarrigan
|
|
* @license Naomi's Public License
|
|
* @author Naomi Carrigan
|
|
*/
|
|
|
|
/**
|
|
* Validates that a URL is a proper base64 data string.
|
|
*/
|
|
export function validateDataUrl(url: string): boolean {
|
|
return /^data:image\/(jpeg|png|gif|webp|svg\+xml);base64,[A-Za-z0-9+/=]+$/.test(url);
|
|
}
|
|
|
|
/**
|
|
* Validates that a URL is safe and points to an allowed protocol.
|
|
* Prevents javascript:, data:, vbscript:, and file: URLs.
|
|
*/
|
|
export function validateUrl(url: string): boolean {
|
|
if (!url) {
|
|
return true; // Empty URLs are acceptable for optional fields
|
|
}
|
|
|
|
// Check for dangerous protocols
|
|
const dangerousProtocols = /^(javascript|data|vbscript|file):/i;
|
|
if (dangerousProtocols.test(url)) {
|
|
return false;
|
|
}
|
|
|
|
// Must be a valid URL format
|
|
try {
|
|
const parsedUrl = new URL(url);
|
|
// Only allow http and https protocols
|
|
if (!["http:", "https:"].includes(parsedUrl.protocol)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates string length is within acceptable bounds.
|
|
*/
|
|
export function validateStringLength(
|
|
value: string | undefined,
|
|
maxLength: number
|
|
): boolean {
|
|
if (!value) {
|
|
return true;
|
|
}
|
|
return value.length <= maxLength;
|
|
}
|
|
|
|
/**
|
|
* Validates that a slug contains only safe characters (alphanumeric, hyphens, underscores).
|
|
*/
|
|
export function validateSlug(slug: string | undefined): boolean {
|
|
if (!slug) {
|
|
return true;
|
|
}
|
|
// Allow alphanumeric, hyphens, underscores only
|
|
const slugPattern = /^[a-z0-9-_]+$/i;
|
|
return slugPattern.test(slug) && slug.length <= 50;
|
|
}
|
|
|
|
/**
|
|
* Validates rating is within acceptable range.
|
|
*/
|
|
export function validateRating(rating: number | undefined): boolean {
|
|
if (rating === undefined) {
|
|
return true;
|
|
}
|
|
return Number.isInteger(rating) && rating >= 0 && rating <= 10;
|
|
}
|
|
|
|
/**
|
|
* Maximum string lengths for various fields.
|
|
*/
|
|
export const MAX_LENGTHS = {
|
|
TITLE: 500,
|
|
AUTHOR: 200,
|
|
DESCRIPTION: 5000,
|
|
BIO: 1000,
|
|
SLUG: 50,
|
|
URL: 2048,
|
|
DISPLAY_NAME: 100,
|
|
USERNAME: 100,
|
|
COMMENT_CONTENT: 10000,
|
|
NOTES: 5000,
|
|
TAGS: 50, // per tag
|
|
ISBN: 50,
|
|
DATA_URL: 5 * 1024 * 1024, // 5MB in bytes (not chars)
|
|
} as const;
|