diff --git a/src/shared/browser-url.test.ts b/src/shared/browser-url.test.ts index ffc01c90e82..3c49f89486b 100644 --- a/src/shared/browser-url.test.ts +++ b/src/shared/browser-url.test.ts @@ -42,6 +42,12 @@ describe('browser-url helpers', () => { expect(normalizeBrowserNavigationUrl('C:\\Users\\me\\Downloads\\Example.ipynb')).toBe( 'file:///C:/Users/me/Downloads/Example.ipynb' ) + expect(normalizeBrowserNavigationUrl('\\\\server\\share\\Example.ipynb')).toBe( + 'file://server/share/Example.ipynb' + ) + expect( + normalizeBrowserNavigationUrl('\\\\wsl.localhost\\Ubuntu\\home\\me\\Example.ipynb') + ).toBe('file://wsl.localhost/Ubuntu/home/me/Example.ipynb') }) // Why: in-app preview is fine (sandboxed webview), but handing file:// to @@ -49,6 +55,7 @@ describe('browser-url helpers', () => { // arbitrary paths. External-open paths must still refuse file://. it('rejects file:// for external opens even though it is allowed in-app', () => { expect(normalizeExternalBrowserUrl('file:///etc/passwd')).toBeNull() + expect(normalizeExternalBrowserUrl('\\\\server\\share\\Example.ipynb')).toBeNull() }) it('returns null for non-URL input without search engine opt-in', () => { diff --git a/src/shared/browser-url.ts b/src/shared/browser-url.ts index f30edec23c9..8dc52eaa9c1 100644 --- a/src/shared/browser-url.ts +++ b/src/shared/browser-url.ts @@ -9,6 +9,7 @@ const LOCAL_ADDRESS_PATTERN = // a URL attempt, not a search query. const LOOKS_LIKE_URL_PATTERN = /^[^\s]+\.[a-z]{2,}(\/.*)?$/i const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/][^\s]*$/ +const WINDOWS_UNC_PATH_PATTERN = /^\\\\[^\s\\/]+[\\/][^\\/]+(?:[\\/].*)?$/ const UNIX_ABSOLUTE_PATH_PATTERN = /^\/[^\s]*$/ export type SearchEngine = 'google' | 'duckduckgo' | 'bing' | 'kagi' @@ -146,6 +147,12 @@ function absolutePathToFileUrl(filePath: string): string { : `file:///${segments.join('/')}` } +function windowsUncPathToFileUrl(filePath: string): string { + const normalizedPath = filePath.replaceAll('\\', '/').replace(/^\/+/, '') + const [host, ...pathSegments] = normalizedPath.split('/') + return `file://${host}/${pathSegments.map(encodeURIComponent).join('/')}` +} + export function normalizeBrowserNavigationUrl( rawUrl: string, searchEngine?: SearchEngine | null, @@ -164,6 +171,10 @@ export function normalizeBrowserNavigationUrl( } } + if (WINDOWS_UNC_PATH_PATTERN.test(trimmed)) { + return windowsUncPathToFileUrl(trimmed) + } + if (UNIX_ABSOLUTE_PATH_PATTERN.test(trimmed) || WINDOWS_ABSOLUTE_PATH_PATTERN.test(trimmed)) { return absolutePathToFileUrl(trimmed) }