| """Validation utilities for user-supplied input.""" |
|
|
| import re |
| from urllib.parse import urlparse |
|
|
| from open_webui.env import ( |
| PROFILE_IMAGE_ALLOWED_MIME_TYPES, |
| PROFILE_IMAGE_MAX_DATA_URI_SIZE, |
| ) |
|
|
| _USER_PROFILE_IMAGE_RE = re.compile(r'^/api/v1/users/[^/?#]+/profile/image$') |
|
|
| |
| _mime_suffixes = '|'.join(re.escape(t.split('/')[-1]) for t in sorted(PROFILE_IMAGE_ALLOWED_MIME_TYPES)) |
| _SAFE_DATA_URI_RE = re.compile(rf'^data:image/({_mime_suffixes});base64,', re.IGNORECASE) |
|
|
| |
| |
| |
| |
| _SAFE_STATIC_PATHS = frozenset( |
| { |
| '/user.png', |
| '/favicon.png', |
| '/static/favicon.png', |
| } |
| ) |
|
|
|
|
| def validate_profile_image_url(url: str) -> str: |
| """ |
| Pydantic-compatible validator for profile image URLs. |
| |
| Allowed formats: |
| - Empty string (falls back to default avatar) |
| - Known static-asset paths assigned by OWUI (exact match) |
| - The OWUI profile-image API route ``/api/v1/users/{id}/profile/image`` |
| - ``http://`` and ``https://`` URLs with a valid hostname |
| - ``data:image/{png,jpeg,gif,webp};base64,...`` URIs |
| |
| Everything else is rejected, including: |
| - Dangerous schemes (javascript:, file:, ftp:, …) |
| - SVG data URIs (can contain embedded scripts) |
| - Arbitrary relative paths (prevents authenticated GET triggers) |
| - Scheme-relative URLs (``//host/path``) |
| - data URIs larger than PROFILE_IMAGE_MAX_DATA_URI_SIZE bytes |
| """ |
| if not url: |
| return url |
|
|
| |
|
|
| if url in _SAFE_STATIC_PATHS: |
| return url |
|
|
| if _USER_PROFILE_IMAGE_RE.match(url): |
| return url |
|
|
| |
|
|
| |
| |
| parsed = urlparse(url) |
|
|
| |
| |
| |
| if parsed.scheme in ('http', 'https'): |
| if not parsed.hostname: |
| raise ValueError('Invalid profile image URL: HTTP(S) URLs must include a host.') |
| return url |
|
|
| |
| |
| |
| if _SAFE_DATA_URI_RE.match(url): |
| if PROFILE_IMAGE_MAX_DATA_URI_SIZE and len(url) > PROFILE_IMAGE_MAX_DATA_URI_SIZE: |
| raise ValueError( |
| f'Invalid profile image URL: data URI exceeds the {PROFILE_IMAGE_MAX_DATA_URI_SIZE}-byte limit.' |
| ) |
| return url |
|
|
| raise ValueError( |
| 'Invalid profile image URL: must be a known internal path, ' |
| 'an HTTP(S) URL with a host, or a data:image URI (png/jpeg/gif/webp).' |
| ) |
|
|