Skip to main content
Glama
safe-url.ts4.72 kB
/** * SafeURL utility * * Provides a safer alternative to URL constructor for database connections * that may contain special characters in passwords or other parts */ /** * Interface defining the structure of a URL parser * that can handle special characters in connection strings */ export interface ISafeURL { protocol: string; hostname: string; port: string; pathname: string; username: string; password: string; searchParams: Map<string, string>; getSearchParam(name: string): string | null; forEachSearchParam(callback: (value: string, key: string) => void): void; } /** * SafeURL class implements a parser for handling DSN strings * with special characters that might break the standard URL constructor */ export class SafeURL implements ISafeURL { protocol: string; hostname: string; port: string; pathname: string; username: string; password: string; searchParams: Map<string, string>; /** * Parse a URL and handle special characters in passwords * This is a safe alternative to the URL constructor * * @param urlString - The DSN string to parse */ constructor(urlString: string) { // Initialize with defaults this.protocol = ''; this.hostname = ''; this.port = ''; this.pathname = ''; this.username = ''; this.password = ''; this.searchParams = new Map<string, string>(); // Validate URL string if (!urlString || urlString.trim() === '') { throw new Error('URL string cannot be empty'); } try { // Extract protocol const protocolSeparator: number = urlString.indexOf('://'); if (protocolSeparator !== -1) { this.protocol = urlString.substring(0, protocolSeparator + 1); // includes the colon urlString = urlString.substring(protocolSeparator + 3); // rest after :// } else { throw new Error('Invalid URL format: missing protocol (e.g., "mysql://")'); } // Extract query params if any const questionMarkIndex: number = urlString.indexOf('?'); let queryParams: string = ''; if (questionMarkIndex !== -1) { queryParams = urlString.substring(questionMarkIndex + 1); urlString = urlString.substring(0, questionMarkIndex); // Parse query parameters queryParams.split('&').forEach(pair => { const parts: string[] = pair.split('='); if (parts.length === 2 && parts[0] && parts[1]) { this.searchParams.set(parts[0], decodeURIComponent(parts[1])); } }); } // Extract authentication const atIndex: number = urlString.indexOf('@'); if (atIndex !== -1) { const auth: string = urlString.substring(0, atIndex); urlString = urlString.substring(atIndex + 1); // Split into username and password const colonIndex: number = auth.indexOf(':'); if (colonIndex !== -1) { this.username = auth.substring(0, colonIndex); this.password = auth.substring(colonIndex + 1); // Decode username and password this.username = decodeURIComponent(this.username); this.password = decodeURIComponent(this.password); } else { this.username = auth; } } // Extract pathname const pathSeparatorIndex: number = urlString.indexOf('/'); if (pathSeparatorIndex !== -1) { this.pathname = urlString.substring(pathSeparatorIndex); urlString = urlString.substring(0, pathSeparatorIndex); } // Extract hostname and port const colonIndex: number = urlString.indexOf(':'); if (colonIndex !== -1) { this.hostname = urlString.substring(0, colonIndex); this.port = urlString.substring(colonIndex + 1); } else { this.hostname = urlString; } // Additional validation if (this.protocol === '') { throw new Error('Invalid URL: protocol is required'); } } catch (error) { throw new Error(`Failed to parse URL: ${error instanceof Error ? error.message : String(error)}`); } } /** * Helper method to safely get a parameter from query string * * @param name - The parameter name to retrieve * @returns The parameter value or null if not found */ getSearchParam(name: string): string | null { return this.searchParams.has(name) ? this.searchParams.get(name) as string : null; } /** * Helper method to iterate over all parameters * * @param callback - Function to call for each parameter */ forEachSearchParam(callback: (value: string, key: string) => void): void { this.searchParams.forEach((value, key) => callback(value, key)); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bytebase/dbhub'

If you have feedback or need assistance with the MCP directory API, please join our Discord server