@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
154 lines (140 loc) • 5.99 kB
text/typescript
import mime from 'mime';
/**
* Build a path string from a path and a hash/search object
*
* This function constructs a properly formatted URL path by combining a base path
* with optional hash and search parameters. It uses URL constructor for proper
* encoding and formatting while removing the temporary base domain.
*
* @param path - The base path (can be relative, absolute, or include protocol)
* @param options - Optional configuration object
* @param options.hash - Hash fragment to append (with or without leading #)
* @param options.search - Search/query parameters to append (with or without leading ?)
* @returns Formatted path string with hash and search parameters
*
* @example
* ```typescript
* pathString('/home') // '/home'
* pathString('/home', { search: 'id=1&name=test' }) // '/home?id=1&name=test'
* pathString('/home', { hash: 'top' }) // '/home#top'
* pathString('./home') // '/home'
* pathString('https://example.com/path') // 'https://example.com/path'
* ```
*/
export const pathString = (
path: string,
{
hash = '',
search = '',
}: {
hash?: string;
search?: string;
} = {},
) => {
// Use a temporary base URL for proper URL parsing and formatting
const tempBase = 'https://a.com';
const url = new URL(path, tempBase);
// Add hash fragment if provided
if (hash) url.hash = hash;
// Add search parameters if provided
if (search) url.search = search;
// Return the formatted URL without the temporary base
return url.toString().replace(tempBase, '');
};
/**
* Get file extension from URL
*
* This function extracts the file extension from a URL's pathname and validates it against
* common image formats. It properly handles URLs with query parameters, hash fragments,
* relative paths, and various edge cases. Returns empty string for invalid cases.
*
* @param url - The URL to extract extension from (can be relative, absolute, or include query parameters and hash fragments)
* @returns file extension without dot (e.g., 'jpg', 'png', 'webp'), or empty string for invalid cases
*
* @example
* ```typescript
* inferFileExtensionFromImageUrl('https://example.com/image.jpg') // 'jpg'
* inferFileExtensionFromImageUrl('https://example.com/image.png?v=123') // 'png'
* inferFileExtensionFromImageUrl('https://example.com/image.webp#section') // 'webp'
* inferFileExtensionFromImageUrl('generations/images/photo.png') // 'png'
* inferFileExtensionFromImageUrl('https://example.com/document.txt') // '' (empty string)
* inferFileExtensionFromImageUrl('invalid-url') // '' (empty string)
* ```
*/
export const inferFileExtensionFromImageUrl = (url: string): string => {
// Use a temporary base URL for proper URL parsing and formatting (handles relative paths)
const tempBase = 'https://a.com';
const urlObj = new URL(url, tempBase);
const pathname = urlObj.pathname;
// Find the last dot in the pathname to get the file extension
const lastDotIndex = pathname.lastIndexOf('.');
if (lastDotIndex === -1) return ''; // No extension found, return empty string
// Extract extension after the last dot and convert to lowercase
const extension = pathname.slice(Math.max(0, lastDotIndex + 1)).toLowerCase();
// Validate against common image extensions
const validImageExtensions = ['webp', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'tiff', 'tif'];
if (validImageExtensions.includes(extension)) {
return extension;
}
// Default fallback for non-image extensions
return '';
};
/**
* Infer content type (MIME type) from an image URL
*
* This function extracts the file extension from a URL and returns the corresponding MIME type.
* It properly handles URLs with query parameters, hash fragments, relative paths, and various edge cases.
*
* @param url - The image URL to analyze (can be relative, absolute, or include query parameters and hash fragments)
* @returns MIME type string (e.g., 'image/jpeg', 'image/png')
* @throws {Error} When the URL doesn't contain a valid file extension
*
* @example
* ```typescript
* inferContentTypeFromImageUrl('https://example.com/image.jpg') // 'image/jpeg'
* inferContentTypeFromImageUrl('https://example.com/image.png?v=123') // 'image/png'
* inferContentTypeFromImageUrl('https://example.com/image.webp#section') // 'image/webp'
* inferContentTypeFromImageUrl('generations/images/photo.png') // 'image/png'
* ```
*/
export function inferContentTypeFromImageUrl(url: string) {
// Get the file extension using the dedicated function
// inferFileExtensionFromImageUrl only returns valid image extensions or empty string
const extension = inferFileExtensionFromImageUrl(url);
// If no valid extension found, throw error
if (!extension) {
throw new Error(`Invalid image url: ${url}`);
}
// Get MIME type using the mime library
// Since extension is guaranteed to be a valid image extension from the whitelist,
// mime.getType() will always return a valid image MIME type
const mimeType = mime.getType(extension);
return mimeType!; // Non-null assertion is safe due to whitelist validation
}
/**
* Check if a URL points to localhost (127.0.0.1)
*
* This function safely determines if the provided URL's hostname is '127.0.0.1'.
* It handles malformed URLs gracefully by returning false instead of throwing errors.
*
* @param url - The URL string to check
* @returns true if the URL's hostname is '127.0.0.1', false otherwise (including for malformed URLs)
*
* @example
* ```typescript
* isLocalUrl('http://127.0.0.1:8080/path') // true
* isLocalUrl('https://example.com') // false
* isLocalUrl('invalid-url') // false (instead of throwing)
* isLocalUrl('') // false (instead of throwing)
* ```
*
* check: apps/desktop/src/main/core/StaticFileServerManager.ts
*/
export function isLocalUrl(url: string) {
try {
return new URL(url).hostname === '127.0.0.1';
} catch {
// Return false for malformed URLs instead of throwing
return false;
}
}