From a15a94a339c9524f06ab9e6e5bc8e150f4fa5754 Mon Sep 17 00:00:00 2001 From: Akos Kitta Date: Fri, 13 Jan 2023 11:56:18 +0100 Subject: [PATCH] fix: workaround for # in the app path Closes #1815 Ref eclipse-theia/theia#12064 Signed-off-by: Akos Kitta --- .../src/node/arduino-ide-backend-module.ts | 11 ++ .../node/theia/plugin-ext/plugin-deployer.ts | 100 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 arduino-ide-extension/src/node/theia/plugin-ext/plugin-deployer.ts diff --git a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts index 3c968ae23..17ba65ace 100644 --- a/arduino-ide-extension/src/node/arduino-ide-backend-module.ts +++ b/arduino-ide-extension/src/node/arduino-ide-backend-module.ts @@ -113,6 +113,11 @@ import { MessagingContribution } from './theia/core/messaging-contribution'; import { MessagingService } from '@theia/core/lib/node/messaging/messaging-service'; import { HostedPluginReader } from './theia/plugin-ext/plugin-reader'; import { HostedPluginReader as TheiaHostedPluginReader } from '@theia/plugin-ext/lib/hosted/node/plugin-reader'; +import { PluginDeployer } from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { + LocalDirectoryPluginDeployerResolverWithFallback, + PluginDeployer_GH_12064, +} from './theia/plugin-ext/plugin-deployer'; export default new ContainerModule((bind, unbind, isBound, rebind) => { bind(BackendApplication).toSelf().inSingletonScope(); @@ -392,6 +397,12 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => { // https://github.com/arduino/arduino-ide/pull/1706#pullrequestreview-1195595080 bind(HostedPluginReader).toSelf().inSingletonScope(); rebind(TheiaHostedPluginReader).toService(HostedPluginReader); + + // https://github.com/eclipse-theia/theia/issues/12064 + bind(LocalDirectoryPluginDeployerResolverWithFallback) + .toSelf() + .inSingletonScope(); + rebind(PluginDeployer).to(PluginDeployer_GH_12064).inSingletonScope(); }); function bindChildLogger(bind: interfaces.Bind, name: string): void { diff --git a/arduino-ide-extension/src/node/theia/plugin-ext/plugin-deployer.ts b/arduino-ide-extension/src/node/theia/plugin-ext/plugin-deployer.ts new file mode 100644 index 000000000..19c46ead1 --- /dev/null +++ b/arduino-ide-extension/src/node/theia/plugin-ext/plugin-deployer.ts @@ -0,0 +1,100 @@ +import { URI } from '@theia/core/lib/common/uri'; +import { + inject, + injectable, + postConstruct, +} from '@theia/core/shared/inversify'; +import { + PluginDeployerResolver, + PluginDeployerResolverContext, +} from '@theia/plugin-ext/lib/common/plugin-protocol'; +import { PluginDeployerImpl } from '@theia/plugin-ext/lib/main/node/plugin-deployer-impl'; +import { LocalDirectoryPluginDeployerResolver } from '@theia/plugin-ext/lib/main/node/resolvers/local-directory-plugin-deployer-resolver'; +import { constants, promises as fs } from 'fs'; +import { isAbsolute, resolve } from 'path'; + +@injectable() +export class LocalDirectoryPluginDeployerResolverWithFallback extends LocalDirectoryPluginDeployerResolver { + override async resolve( + pluginResolverContext: PluginDeployerResolverContext + ): Promise { + const origin = pluginResolverContext.getOriginId(); + // The original implementation must not run when there is a hash in the path. Otherwise, it can resolve an undesired directory. + // Consider app under c:\Users\username\Desktop\# here is my app\ + // Then the flawed logic will incorrectly find c:\Users\username\Desktop location after stripping the rest of the path after the hash. + // The implementation which provides a workaround for the hash in the path assumes that the original Theia logic is correct, when no hash present in the URI path. + let localPath: string | null; + if (origin.includes('#')) { + localPath = await resolveLocalPluginPathFallback( + pluginResolverContext, + this.supportedScheme + ); + } else { + localPath = await this.originalResolveLocalPluginPath( + pluginResolverContext, + this.supportedScheme + ); + } + if (localPath) { + await this.resolveFromLocalPath(pluginResolverContext, localPath); + } + } + + private async originalResolveLocalPluginPath( + context: PluginDeployerResolverContext, + scheme: string + ): Promise { + const object = >this; + if ( + 'resolveLocalPluginPath' in object && + typeof object['resolveLocalPluginPath'] === 'function' + ) { + return object['resolveLocalPluginPath'](context, scheme); + } + return null; + } +} + +async function resolveLocalPluginPathFallback( + context: PluginDeployerResolverContext, + scheme: string +): Promise { + const uri = new URI(context.getOriginId()); + if (uri.scheme === scheme) { + const unencodedRawUri = uri.toString(true); + let fsPath = unencodedRawUri.substring(`${scheme}:`.length); + if (!isAbsolute(fsPath)) { + fsPath = resolve(process.cwd(), fsPath); + } + try { + await fs.access(fsPath, constants.R_OK); + return fsPath; + } catch { + console.warn( + `The local plugin referenced by ${context.getOriginId()} does not exist.` + ); + } + } + return null; +} + +@injectable() +export class PluginDeployer_GH_12064 extends PluginDeployerImpl { + @inject(LocalDirectoryPluginDeployerResolverWithFallback) + private readonly pluginResolver: LocalDirectoryPluginDeployerResolverWithFallback; + + @postConstruct() + protected adjustPluginResolvers(): void { + const pluginResolvers = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this as any).pluginResolvers + ); + const index = pluginResolvers.findIndex( + (pluginResolver) => + pluginResolver instanceof LocalDirectoryPluginDeployerResolver + ); + if (index >= 0) { + pluginResolvers.splice(index, 1, this.pluginResolver); + } + } +}