From 738e83155b16a2aff1da24aca831f51507886480 Mon Sep 17 00:00:00 2001 From: yogeshwaran-c Date: Mon, 13 Apr 2026 11:17:40 +0530 Subject: [PATCH] Use extended-length path prefix for long vscode-file resources on Windows On Windows, Electron's registerFileProtocol handler fails to load files whose absolute path exceeds MAX_PATH (259). This breaks markdown image hover previews (and any other vscode-file:// resource) for deeply nested files, even though the preview rendering itself works because it goes through a different code path. Work around the underlying Electron/Win32 limitation (electron/electron#49101) by prefixing paths with '\?\' (or '\?\UNC\' for UNC paths) before handing them back to Electron's protocol callback. The extended-length prefix is only applied on Windows and only when the resolved path is close to or over the limit, so normal short paths are unaffected. The validRoots / validExtensions allow-list checks are still performed against the normalized (unprefixed) path so the security boundary is unchanged. Fixes #261880 --- .../electron-main/protocolMainService.ts | 54 +++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/vs/platform/protocol/electron-main/protocolMainService.ts b/src/vs/platform/protocol/electron-main/protocolMainService.ts index f5c2e3a9dd709..2a9b7a89affc7 100644 --- a/src/vs/platform/protocol/electron-main/protocolMainService.ts +++ b/src/vs/platform/protocol/electron-main/protocolMainService.ts @@ -7,7 +7,7 @@ import { session } from 'electron'; import { Disposable, IDisposable, toDisposable } from '../../../base/common/lifecycle.js'; import { COI, FileAccess, Schemas, CacheControlheaders, DocumentPolicyheaders } from '../../../base/common/network.js'; import { basename, extname, normalize } from '../../../base/common/path.js'; -import { isLinux } from '../../../base/common/platform.js'; +import { isLinux, isWindows } from '../../../base/common/platform.js'; import { TernarySearchTree } from '../../../base/common/ternarySearchTree.js'; import { URI } from '../../../base/common/uri.js'; import { generateUuid } from '../../../base/common/uuid.js'; @@ -19,6 +19,48 @@ import { IUserDataProfilesService } from '../../userDataProfile/common/userDataP type ProtocolCallback = { (result: string | Electron.FilePathWithHeaders | { error: number }): void }; +/** + * On Windows, the default Win32 file APIs enforce a `MAX_PATH` limit of 259 + * characters. When a resource path exceeds this limit, Electron's + * `registerFileProtocol` handler fails to load the file (see + * https://github.com/microsoft/vscode/issues/261880 and + * https://github.com/electron/electron/issues/49101). + * + * Prefixing an absolute path with `\\?\` (or `\\?\UNC\` for UNC paths) + * opts the Win32 APIs into extended-length path mode which bypasses + * `MAX_PATH`. + */ +const WINDOWS_LONG_PATH_THRESHOLD = 248; // conservative: Win32 directory limit is 248 while file limit is 259 +const WINDOWS_EXTENDED_PATH_PREFIX = '\\\\?\\'; +const WINDOWS_EXTENDED_UNC_PREFIX = '\\\\?\\UNC\\'; + +function toWindowsLongPathIfNeeded(path: string): string { + if (!isWindows) { + return path; + } + + if (path.length < WINDOWS_LONG_PATH_THRESHOLD) { + return path; + } + + // Already using the extended-length prefix + if (path.startsWith(WINDOWS_EXTENDED_PATH_PREFIX)) { + return path; + } + + // UNC path: \\server\share\... -> \\?\UNC\server\share\... + if (path.length >= 2 && path.charCodeAt(0) === 92 /* \ */ && path.charCodeAt(1) === 92 /* \ */) { + return WINDOWS_EXTENDED_UNC_PREFIX + path.substring(2); + } + + // Drive-letter absolute path: C:\... -> \\?\C:\... + if (path.length >= 3 && path.charCodeAt(1) === 58 /* : */) { + return WINDOWS_EXTENDED_PATH_PREFIX + path; + } + + return path; +} + export class ProtocolMainService extends Disposable implements IProtocolMainService { declare readonly _serviceBrand: undefined; @@ -123,14 +165,20 @@ export class ProtocolMainService extends Disposable implements IProtocolMainServ }; } + // On Windows, paths longer than MAX_PATH (259) cause Electron's file + // protocol handler to fail. Apply the `\\?\` extended-length prefix so + // Win32 APIs bypass the limit (e.g. markdown image hover previews for + // deeply nested files). See https://github.com/microsoft/vscode/issues/261880. + const resolvedPath = toWindowsLongPathIfNeeded(path); + // first check by validRoots if (this.validRoots.findSubstr(path)) { - return callback({ path, headers }); + return callback({ path: resolvedPath, headers }); } // then check by validExtensions if (this.validExtensions.has(extname(path).toLowerCase())) { - return callback({ path, headers }); + return callback({ path: resolvedPath, headers }); } // finally block to load the resource