diff --git a/src/common/utils/__tests__/paths.test.ts b/src/common/utils/__tests__/paths.test.ts new file mode 100644 index 0000000000..0eec289fb9 --- /dev/null +++ b/src/common/utils/__tests__/paths.test.ts @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { describeIf } from "../../../../integration/helpers/utils"; +import { isWindows } from "../../vars"; +import { isLogicalChildPath } from "../paths"; + +describe("isLogicalChildPath", () => { + describeIf(isWindows)("windows tests", () => { + it.each([ + { + parentPath: "C:\\Foo", + testPath: "C:\\Foo\\Bar", + expected: true, + }, + { + parentPath: "C:\\Foo", + testPath: "C:\\Bar", + expected: false, + }, + { + parentPath: "C:\\Foo", + testPath: "C:/Bar", + expected: false, + }, + { + parentPath: "C:\\Foo", + testPath: "C:/Foo/Bar", + expected: true, + }, + { + parentPath: "C:\\Foo", + testPath: "D:\\Foo\\Bar", + expected: false, + }, + ])("test %#", (testData) => { + expect(isLogicalChildPath(testData.parentPath, testData.testPath)).toBe(testData.expected); + }); + }); + + describeIf(!isWindows)("posix tests", () => { + it.each([ + { + parentPath: "/foo", + testPath: "/foo", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/bar", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/foobar", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/foo/bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/../bar", + expected: false, + }, + { + parentPath: "/foo", + testPath: "/foo/./bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/.bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/..bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/...bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/..\\.bar", + expected: true, + }, + { + parentPath: "/bar/../foo", + testPath: "/foo/bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "/foo/\\bar", + expected: true, + }, + { + parentPath: "/foo", + testPath: "./bar", + expected: false, + }, + ])("test %#", (testData) => { + expect(isLogicalChildPath(testData.parentPath, testData.testPath)).toBe(testData.expected); + }); + }); +}); diff --git a/src/common/utils/paths.ts b/src/common/utils/paths.ts index 391e1392f2..68d8f61ffa 100644 --- a/src/common/utils/paths.ts +++ b/src/common/utils/paths.ts @@ -33,3 +33,35 @@ function resolveTilde(filePath: string) { export function resolvePath(filePath: string): string { return path.resolve(resolveTilde(filePath)); } + +/** + * Checks if `testPath` represents a potential filesystem entry that would be + * logically "within" the `parentPath` directory. + * + * This function will return `true` in the above case, and `false` otherwise. + * It will return `false` if the two paths are the same (after resolving them). + * + * The function makes no FS calls and is platform dependant. Meaning that the + * results are only guaranteed to be correct for the platform you are running + * on. + * @param parentPath The known path of a directory + * @param testPath The path that is to be tested + */ +export function isLogicalChildPath(parentPath: string, testPath: string): boolean { + parentPath = path.resolve(parentPath); + testPath = path.resolve(testPath); + + if (parentPath === testPath) { + return false; + } + + while (testPath.length >= parentPath.length) { + if (testPath === parentPath) { + return true; + } + + testPath = path.dirname(testPath); + } + + return false; +} diff --git a/src/migrations/user-store/5.0.3-beta.1.ts b/src/migrations/user-store/5.0.3-beta.1.ts index 72f3899627..ef3448116d 100644 --- a/src/migrations/user-store/5.0.3-beta.1.ts +++ b/src/migrations/user-store/5.0.3-beta.1.ts @@ -26,6 +26,7 @@ import os from "os"; import { ClusterStore, ClusterStoreModel } from "../../common/cluster-store"; import type { KubeconfigSyncEntry, UserPreferencesModel } from "../../common/user-store"; import { MigrationDeclaration, migrationLog } from "../helpers"; +import { isLogicalChildPath } from "../../common/utils"; export default { version: "5.0.3-beta.1", @@ -36,7 +37,6 @@ export default { const extensionDataDir = path.resolve(app.getPath("userData"), "extension_data"); const syncPaths = new Set(syncKubeconfigEntries.map(s => s.filePath)); - console.log(extensionDataDir); syncPaths.add(path.join(os.homedir(), ".kube")); for (const cluster of clusters) { @@ -52,9 +52,7 @@ export default { continue; } - const relativeToExtensionData = path.relative(cluster.kubeConfigPath, extensionDataDir); - - if (relativeToExtensionData === "" || relativeToExtensionData.match(/^(\.\.)([\\\/]\.\.)*$/)) { + if (isLogicalChildPath(extensionDataDir, cluster.kubeConfigPath)) { migrationLog(`Skipping ${cluster.id} because kubeConfigPath is placed under an extension_data folder`); continue; }