Skip to content

Commit 90d6615

Browse files
committed
Restrict parent directory traversal for finding the associated project
**Changes** 1. Added a launch-time JVM property for the netbeans LSP server "project.limitScanRoot", with its value set to the user's home directory path and the workspace folders, if any. - This is obtained using Node's `os.homedir()` function in *nbcode.ts* - Multiple paths are delimited by the OS-specific separator. 2. Added a configuration property "jdk.advanced.disable.projectSearchLimit" which can be used to turn off this new behaviour. **Reasons** - NetBeans supports searching for the build project information in the ancestor directories of the file or folder opened in a workspace. - Thus, NetBeans `org.netbeans.modules.projectapi.SimpleFileOwnerQueryImplementation` supports a JVM launch property (i.e. System property) called **project.limitScanRoot**. - This prevents searching for the build project information when the opened file or folder is outside this specified path. Signed-off-by: Siddharth Srinivasan <[email protected]>
1 parent a6ce838 commit 90d6615

File tree

6 files changed

+234
-1
lines changed

6 files changed

+234
-1
lines changed

build.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
patches/7654.diff
4141
patches/7670.diff
4242
patches/7690.diff
43+
patches/7722.diff
4344
patches/7733.diff
4445
patches/mvn-sh.diff
4546
patches/generate-dependencies.diff

patches/7722.diff

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
diff --git a/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java b/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java
2+
index 1d7ad89a8714..216291f86fd4 100644
3+
--- a/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java
4+
+++ b/extide/gradle/src/org/netbeans/modules/gradle/ProjectTrust.java
5+
@@ -25,10 +25,10 @@
6+
import java.security.InvalidKeyException;
7+
import java.security.Key;
8+
import java.security.NoSuchAlgorithmException;
9+
+import java.security.SecureRandom;
10+
import java.util.Collections;
11+
import java.util.HashSet;
12+
import java.util.List;
13+
-import java.util.Random;
14+
import java.util.Set;
15+
import java.util.logging.Level;
16+
import java.util.logging.Logger;
17+
@@ -69,7 +69,7 @@ public class ProjectTrust {
18+
byte[] buf = prefs.getByteArray(KEY_SALT, null);
19+
if (buf == null) {
20+
buf = new byte[16];
21+
- new Random().nextBytes(buf);
22+
+ new SecureRandom().nextBytes(buf);
23+
prefs.putByteArray(KEY_SALT, buf);
24+
}
25+
salt = buf;
26+
@@ -134,7 +134,7 @@ public void trustProject(Project project, boolean permanently) {
27+
if (permanently && !isTrustedPermanently(project)) {
28+
Path trustFile = getProjectTrustFile(project);
29+
byte[] rnd = new byte[16];
30+
- new Random().nextBytes(rnd);
31+
+ new SecureRandom().nextBytes(rnd);
32+
String projectId = toHex(rnd);
33+
projectTrust.put(pathId, projectId);
34+
try {
35+
diff --git a/ide/projectapi/arch.xml b/ide/projectapi/arch.xml
36+
index e91502b7570b..81349ae3f7d0 100644
37+
--- a/ide/projectapi/arch.xml
38+
+++ b/ide/projectapi/arch.xml
39+
@@ -509,8 +509,9 @@ Nothing.
40+
<p>
41+
<api name="project.limitScanRoot" category="friend" group="systemproperty" type="export">
42+
<p>
43+
- If defined, limits search for a parent project to a certain subtree. The property defines <b>absolute path</b> of a folder
44+
- where upwards search for a project in parent folders is terminated. Queries outside of the root will not find any project.
45+
+ If defined, limits search for a parent project to a certain subtree. The property defines the <b>absolute path</b> of a folder
46+
+ where upwards search for a project in parent folders is terminated. Queries outside the root will not find any project.
47+
+ Multiple folders may be specified when delimited with OS-specific path separators (':' on *nix, ';' on Windows).
48+
Currently used for tests so the tested runtime does not escape the workdir.
49+
</p>
50+
</api>
51+
diff --git a/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java b/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java
52+
index 05b887129a52..bf4b435f4dbf 100644
53+
--- a/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java
54+
+++ b/ide/projectapi/src/org/netbeans/modules/projectapi/SimpleFileOwnerQueryImplementation.java
55+
@@ -19,6 +19,7 @@
56+
57+
package org.netbeans.modules.projectapi;
58+
59+
+import java.io.File;
60+
import java.io.IOException;
61+
import java.lang.ref.Reference;
62+
import java.lang.ref.WeakReference;
63+
@@ -27,10 +28,9 @@
64+
import java.net.URISyntaxException;
65+
import java.net.URL;
66+
import java.util.ArrayList;
67+
-import java.util.Arrays;
68+
import java.util.Collections;
69+
import java.util.HashMap;
70+
-import java.util.HashSet;
71+
+import java.util.LinkedHashSet;
72+
import java.util.List;
73+
import java.util.Map;
74+
import java.util.Set;
75+
@@ -45,6 +45,7 @@
76+
import org.netbeans.api.project.ProjectManager;
77+
import org.netbeans.spi.project.FileOwnerQueryImplementation;
78+
import org.openide.filesystems.FileObject;
79+
+import org.openide.filesystems.FileUtil;
80+
import org.openide.filesystems.URLMapper;
81+
import org.openide.util.BaseUtilities;
82+
import org.openide.util.NbPreferences;
83+
@@ -59,21 +60,19 @@ public class SimpleFileOwnerQueryImplementation implements FileOwnerQueryImpleme
84+
private static final Logger LOG = Logger.getLogger(SimpleFileOwnerQueryImplementation.class.getName());
85+
private static final URI UNOWNED_URI = URI.create("http:unowned");
86+
private static final Set<String> forbiddenFolders;
87+
- private static final String projectScanRoot;
88+
+ private static final Set<String> projectScanRoots;
89+
90+
static {
91+
- Set<String> files = new HashSet<String>();
92+
- String root = null;
93+
+ Set<String> folders = null;
94+
+ Set<String> roots = null;
95+
try {
96+
- root = System.getProperty("project.limitScanRoot"); // NOI18N
97+
- String forbidden = System.getProperty("project.forbiddenFolders", System.getProperty("versioning.forbiddenFolders", "")); //NOI18N
98+
- files.addAll(Arrays.asList(forbidden.split("\\;"))); //NOI18N
99+
- files.remove(""); //NOI18N
100+
+ roots = separatePaths(System.getProperty("project.limitScanRoot"), File.pathSeparator); //NOI18N
101+
+ folders = separatePaths(System.getProperty("project.forbiddenFolders", System.getProperty("versioning.forbiddenFolders")), ";"); //NOI18N
102+
} catch (Exception e) {
103+
LOG.log(Level.INFO, e.getMessage(), e);
104+
}
105+
- forbiddenFolders = files;
106+
- projectScanRoot = root;
107+
+ forbiddenFolders = folders == null ? Collections.emptySet() : folders;
108+
+ projectScanRoots = roots;
109+
}
110+
111+
/** Do nothing */
112+
@@ -113,7 +112,7 @@ public Project getOwner(FileObject f) {
113+
114+
deserialize();
115+
while (f != null) {
116+
- if (projectScanRoot != null && !f.getPath().startsWith(projectScanRoot)) {
117+
+ if (projectScanRoots != null && projectScanRoots.stream().noneMatch(f.getPath()::startsWith)) {
118+
break;
119+
}
120+
boolean folder = f.isFolder();
121+
@@ -137,8 +136,8 @@ public Project getOwner(FileObject f) {
122+
}
123+
folders.add(f);
124+
if (!forbiddenFolders.contains(f.getPath()) &&
125+
- !hasRoot(externalOwners.keySet(), f, folder, furi) &&
126+
- !hasRoot(deserializedExternalOwners.keySet(), f, folder, furi)) {
127+
+ !hasRoot(externalOwners.keySet(), f, true, furi) &&
128+
+ !hasRoot(deserializedExternalOwners.keySet(), f, true, furi)) {
129+
Project p;
130+
try {
131+
p = ProjectManager.getDefault().findProject(f);
132+
@@ -414,6 +413,40 @@ private static URI goUp(URI u) {
133+
assert u.toString().startsWith(nue.toString()) : "not a parent: " + nue + " of " + u;
134+
return nue;
135+
}
136+
+
137+
+ private static Set<String> separatePaths(String joinedPaths, String pathSeparator) {
138+
+ if (joinedPaths == null || joinedPaths.isEmpty())
139+
+ return null;
140+
+
141+
+ Set<String> paths = null;
142+
+ for (String split : joinedPaths.split(pathSeparator)) {
143+
+ if ((split = split.trim()).isEmpty()) continue;
144+
+
145+
+ // Ensure that variations in terms of ".." or "." or windows drive-letter case differences are removed.
146+
+ // File.getCanonicalFile() will additionally resolve symlinks, which is not required.
147+
+ File file = FileUtil.normalizeFile(new File(split));
148+
+
149+
+ // Store FileObject.getPath(); because getOwner() compares these with FileObject.getPath() strings.
150+
+ // This has some peculiarities as compared to File.getAbsolutePath(); such as return "" for File("/").
151+
+ FileObject fileObject = FileUtil.toFileObject(file);
152+
+ // This conversion may get rid of non-existent paths.
153+
+ if (fileObject == null) continue;
154+
+
155+
+ String path = fileObject.getPath();
156+
+ if (path == null || path.isEmpty()) continue;
157+
+
158+
+ if (paths == null) {
159+
+ paths = Collections.singleton(path); // more performant in usage when only a single element is present.
160+
+ } else {
161+
+ if (paths.size() == 1) {
162+
+ paths = new LinkedHashSet<>(paths); // more performant in iteration
163+
+ }
164+
+ paths.add(path);
165+
+ }
166+
+ }
167+
+ return paths;
168+
+ }
169+
+
170+
private static final boolean WINDOWS = BaseUtilities.isWindows();
171+
172+
}
173+
diff --git a/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java b/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
174+
index 633ee72340de..3140aba8eafa 100644
175+
--- a/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
176+
+++ b/platform/o.n.bootstrap/src/org/netbeans/CLIHandler.java
177+
@@ -45,7 +45,6 @@
178+
import java.util.Collection;
179+
import java.util.Collections;
180+
import java.util.List;
181+
-import java.util.Random;
182+
import java.util.logging.Level;
183+
import java.util.logging.Logger;
184+
import org.openide.util.RequestProcessor;
185+
@@ -580,7 +579,7 @@ static Status initialize(
186+
enterState(10, block);
187+
188+
final byte[] arr = new byte[KEY_LENGTH];
189+
- new Random().nextBytes(arr);
190+
+ new SecureRandom().nextBytes(arr);
191+
192+
193+
final RandomAccessFile os = raf;

vscode/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@
231231
"type": "boolean",
232232
"default": false,
233233
"description": "%jdk.configuration.disableNbJavac.description%"
234+
},
235+
"jdk.advanced.disable.projectSearchLimit": {
236+
"type": "boolean",
237+
"default": false,
238+
"description": "%jdk.configuration.disableProjectSearchLimit.description%"
234239
}
235240
}
236241
},

vscode/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"jdk.configuration.runConfig.env.description": "Environment variables",
4747
"jdk.configuration.runConfig.cwd.description": "Working directory",
4848
"jdk.configuration.disableNbJavac.description": "Advanced option: disable nb-javac library, javac from the selected JDK will be used. The selected JDK must be at least JDK 22.",
49+
"jdk.configuration.disableProjectSearchLimit.description": "Advanced option: disable limits on searching in containing folders for project information.",
4950
"jdk.debugger.configuration.mainClass.description": "Absolute path to the program main class.",
5051
"jdk.debugger.configuration.classPaths.description": "The classpaths for launching the JVM.",
5152
"jdk.debugger.configuration.console.description": "The specified console to launch the program.",

vscode/src/extension.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ import { InputStep, MultiStepInput } from './utils';
6868
import { PropertiesView } from './propertiesView/propertiesView';
6969
import { openJDKSelectionView } from './jdkDownloader';
7070
import { l10n } from './localiser';
71-
import { ORACLE_VSCODE_EXTENSION_ID,NODE_WINDOWS_LABEL } from './constants';
71+
import { ORACLE_VSCODE_EXTENSION_ID, NODE_WINDOWS_LABEL } from './constants';
7272
const API_VERSION : string = "1.0";
7373
const SERVER_NAME : string = "Oracle Java SE Language Server";
7474
export const COMMAND_PREFIX : string = "jdk";
@@ -918,11 +918,38 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
918918
enableModules.push('org.netbeans.libs.nbjavacapi');
919919
}
920920

921+
let projectSearchRoots:string = '';
922+
const isProjectFolderSearchLimited : boolean = !netbeansConfig.get('advanced.disable.projectSearchLimit', false);
923+
if (isProjectFolderSearchLimited) {
924+
try {
925+
projectSearchRoots = os.homedir() as string;
926+
} catch (err:any) {
927+
handleLog(log, `Failed to obtain the user home directory due to: ${err}`);
928+
}
929+
if (!projectSearchRoots) {
930+
projectSearchRoots = os.type() === NODE_WINDOWS_LABEL ? '%USERPROFILE%' : '$HOME'; // The launcher script may perform the env variable substitution
931+
handleLog(log, `Using userHomeDir = "${projectSearchRoots}" as the launcher script may perform env var substitution to get its value.`);
932+
}
933+
const workspaces = workspace.workspaceFolders;
934+
if (workspaces) {
935+
workspaces.forEach(workspace => {
936+
if (workspace.uri) {
937+
try {
938+
projectSearchRoots = projectSearchRoots + path.delimiter + path.normalize(workspace.uri.fsPath);
939+
} catch (err:any) {
940+
handleLog(log, `Failed to get the workspace path: ${err}`);
941+
}
942+
}
943+
});
944+
}
945+
}
946+
921947
let info = {
922948
clusters : findClusters(context.extensionPath),
923949
extensionPath: context.extensionPath,
924950
storagePath : userdir,
925951
jdkHome : specifiedJDK,
952+
projectSearchRoots: projectSearchRoots,
926953
verbose: beVerbose,
927954
disableModules : disableModules,
928955
enableModules : enableModules,

vscode/src/nbcode.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface LaunchInfo {
3232
extensionPath: string;
3333
storagePath: string;
3434
jdkHome: string | unknown;
35+
projectSearchRoots? : string;
3536
verbose? : boolean;
3637
enableModules? : string[];
3738
disableModules? : string[];
@@ -102,6 +103,11 @@ export function launch(
102103
if (info.jdkHome) {
103104
ideArgs.push('--jdkhome', info.jdkHome as string);
104105
}
106+
107+
if (info.projectSearchRoots) {
108+
ideArgs.push(`-J-Dproject.limitScanRoot="${info.projectSearchRoots}"`);
109+
}
110+
105111
if (info.verbose) {
106112
ideArgs.push('-J-Dnetbeans.logger.console=true');
107113
}

0 commit comments

Comments
 (0)