Merge remote-tracking branch

'origin/GP-5332_dev747368_FSRL_backslash_paths' (Closes #7278)
This commit is contained in:
Ryan Kurtz
2025-02-07 14:22:42 -05:00
12 changed files with 114 additions and 52 deletions
@@ -177,7 +177,6 @@ public class FSRL {
FSRLRoot fsRoot = FSRLRoot.nestedFS(containerFile, proto);
String decodedPath = FSUtilities.escapeDecode(path);
decodedPath = decodedPath.replace('\\', '/');
if (decodedPath.isEmpty()) {
decodedPath = null;
}
@@ -289,6 +288,11 @@ public class FSRL {
return null;
}
int cp = path.lastIndexOf('/');
if (cp > 0 && cp == path.length() - 1) {
// if the path ended with a '/', look for the slash before that
// typically only for windows drive letter path like "/c:/"
cp = path.lastIndexOf('/', cp - 1);
}
return cp >= 0 ? path.substring(cp + 1) : path;
}
@@ -273,10 +273,13 @@ class FileSystemInstanceManager implements FileSystemEventListener {
* @param ref {@link FileSystemRef} to close
*/
public synchronized void releaseImmediate(FileSystemRef ref) {
FSCacheInfo fsci = filesystems.get(ref.getFilesystem().getFSRL());
FSRLRoot fsFSRL = ref.getFilesystem().getFSRL();
FSCacheInfo fsci = filesystems.get(fsFSRL);
ref.close();
if (fsci == null) {
Msg.warn(this, "Unknown file system reference: " + ref.getFilesystem().getFSRL());
if (!rootFSRL.equals(fsFSRL) /* we don't store root FS refs */) {
Msg.warn(this, "Unknown file system reference: " + fsFSRL);
}
return;
}
FileSystemRefManager refManager = fsci.ref.getFilesystem().getRefManager();
@@ -22,6 +22,7 @@ import java.nio.file.AccessMode;
import java.util.*;
import org.apache.commons.collections4.map.ReferenceMap;
import org.apache.commons.io.FilenameUtils;
import ghidra.app.util.bin.ByteProvider;
import ghidra.app.util.bin.FileByteProvider;
@@ -61,7 +62,7 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
private final FileSystemRefManager refManager = new FileSystemRefManager(this);
private final ReferenceMap<FileFingerprintRec, String> fileFingerprintToMD5Map =
new ReferenceMap<>();
private final boolean needsListRoots =
private final boolean isWindows =
OperatingSystem.CURRENT_OPERATING_SYSTEM == OperatingSystem.WINDOWS;
private LocalFileSystem(FSRLRoot fsrl) {
@@ -126,7 +127,25 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
* @return The {@link FSRL}
*/
public FSRL getLocalFSRL(File f) {
return fsFSRL.withPath(FSUtilities.normalizeNativePath(f.getAbsolutePath()));
String absPath = f.getAbsolutePath();
if (isWindows) {
// only force on windows... unix paths can have backslashes as part of filenames
absPath = FilenameUtils.separatorsToUnix(absPath);
}
String fsrlPath = FSUtilities.appendPath("/", absPath);
return fsFSRL.withPath(fsrlPath);
}
private GFile getGFile(File f) {
List<File> parts = LocalFileSystem.getFilePathParts(f); // [/subdir/subroot/file, /subdir/subroot, /subdir, /]
GFile current = rootDir;
for (int i = parts.size() - 2; i >= 0; i--) {
File part = parts.get(i);
FSRL childFSRL = getLocalFSRL(part);
current =
GFileImpl.fromFSRL(this, current, childFSRL, part.isDirectory(), part.length());
}
return current;
}
@Override
@@ -149,10 +168,9 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
List<GFile> results = new ArrayList<>();
directory = Objects.requireNonNullElse(directory, rootDir);
if (directory.equals(rootDir) && needsListRoots) {
if (directory.equals(rootDir) && isWindows) {
for (File f : File.listRoots()) {
FSRL rootElemFSRL = fsFSRL.withPath(FSUtilities.normalizeNativePath(f.getName()));
results.add(GFileImpl.fromFSRL(this, null, rootElemFSRL, f.isDirectory(), -1));
results.add(GFileImpl.fromFSRL(this, null, getLocalFSRL(f), f.isDirectory(), -1));
}
}
else {
@@ -219,8 +237,7 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
@Override
public GFile lookup(String path, Comparator<String> nameComp) throws IOException {
File f = lookupFile(null, path, nameComp);
return f != null ? GFileImpl.fromPathString(this,
FSUtilities.normalizeNativePath(f.getPath()), null, f.isDirectory(), f.length()) : null;
return f != null ? getGFile(f) : null;
}
@Override
@@ -259,9 +276,7 @@ public class LocalFileSystem implements GFileSystem, GFileHashProvider {
if (f.equals(canonicalFile)) {
return file;
}
return GFileImpl.fromPathString(this,
FSUtilities.normalizeNativePath(canonicalFile.getPath()), null,
canonicalFile.isDirectory(), canonicalFile.length());
return getGFile(canonicalFile);
}
@Override
@@ -107,7 +107,7 @@ public class LocalFileSystemSub implements GFileSystem, GFileHashProvider {
List<GFile> tmp = new ArrayList<>(localFiles.length);
FSRL dirFSRL = directory.getFSRL();
String relPath = FSUtilities.normalizeNativePath(directory.getPath());
String relPath = directory.getPath(); // this is the clean relative path assigned to the dir GFile earlier
for (File f : localFiles) {
boolean isSymlink = FSUtilities.isSymlink(f); // check this manually to allow broken symlinks to appear in listing
@@ -83,14 +83,14 @@ public class FSBComponentProvider extends ComponentProviderAdapter
* @param fsRef {@link FileSystemRef} to a {@link GFileSystem}.
*/
public FSBComponentProvider(FileSystemBrowserPlugin plugin, FileSystemRef fsRef) {
super(plugin.getTool(), fsRef.getFilesystem().getName(), plugin.getName());
super(plugin.getTool(), getDescriptiveFSName(fsRef.getFilesystem()), plugin.getName());
this.plugin = plugin;
this.rootNode = new FSBRootNode(fsRef);
this.pm = plugin.getTool().getService(ProgramManager.class);
setTransient();
setIcon(FSBIcons.PHOTO);
setIcon(getFSIcon(fsRef.getFilesystem(), true, fsbIcons));
initTree();
fsRef.getFilesystem().getRefManager().addListener(this);
@@ -147,8 +147,8 @@ public class FSBComponentProvider extends ComponentProviderAdapter
super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row,
hasFocus);
if (value instanceof FSBRootNode fsRootNode) {
renderFS(fsRootNode, selected);
if (value instanceof FSBRootNode) {
// do nothing
}
else if (value instanceof FSBDirNode) {
// do nothing special, but exclude FSBFileNode
@@ -160,16 +160,6 @@ public class FSBComponentProvider extends ComponentProviderAdapter
return this;
}
private void renderFS(FSBRootNode node, boolean selected) {
FileSystemRef nodeFSRef = node.getFSRef();
if (nodeFSRef == null || nodeFSRef.getFilesystem() == null) {
return;
}
Icon image = fsbIcons.getIcon(node.getContainerName(),
List.of(FSBIcons.FILESYSTEM_OVERLAY_ICON));
setIcon(image);
}
private void renderFile(FSBFileNode node, boolean selected) {
FSRL fsrl = node.getFSRL();
String filename = fsrl.getName();
@@ -277,7 +267,10 @@ public class FSBComponentProvider extends ComponentProviderAdapter
public void setProject(Project project) {
gTree.runTask(monitor -> {
projectIndex.setProject(project, monitor);
Swing.runLater(() -> gTree.repaint()); // icons might need repainting after new info is available
Swing.runLater(() -> {
contextChanged();
gTree.repaint();
}); // icons might need repainting after new info is available
});
}
@@ -432,6 +425,9 @@ public class FSBComponentProvider extends ComponentProviderAdapter
if (nested) {
FSBFileNode modelFileNode =
(FSBFileNode) gTree.getModelNodeForPath(node.getTreePath());
if (modelFileNode == null) {
return;
}
FSBRootNode nestedRootNode = new FSBRootNode(ref, modelFileNode);
@@ -587,4 +583,21 @@ public class FSBComponentProvider extends ComponentProviderAdapter
}
}
static String getDescriptiveFSName(GFileSystem fs) {
return fs instanceof LocalFileSystem ? "My Computer" : fs.getName();
}
static Icon getFSIcon(GFileSystem fs, boolean isRootNode, FSBIcons fsbIcons) {
List<Icon> overlays = !isRootNode ? List.of(FSBIcons.FILESYSTEM_OVERLAY_ICON) : List.of();
FSRL container = fs.getFSRL().getContainer();
String containerName = container != null ? container.getName() : "/";
Icon image = fs instanceof LocalFileSystem || fs instanceof LocalFileSystemSub
? FSBIcons.MY_COMPUTER
: fsbIcons.getIcon(containerName, overlays);
if (image == FSBIcons.DEFAULT_ICON) {
image = FSBIcons.PHOTO;
}
return image;
}
}
@@ -41,6 +41,7 @@ public class FSBFileNode extends FSBNode {
protected String filenameExtOverride;
FSBFileNode(GFile file) {
super(file.getFSRL().getName());
this.file = file;
}
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -74,6 +74,8 @@ public class FSBIcons {
public static final Icon MISSING_PASSWORD_OVERLAY_ICON = new GIcon("icon.fsbrowser.file.overlay.missing.password");
public static final Icon LINK_OVERLAY_ICON = new GIcon("icon.fsbrowser.file.overlay.link");
public static final Icon DEFAULT_ICON = new GIcon("icon.fsbrowser.file.extension.default");
public static final Icon MY_COMPUTER = resources.ResourceManager.getScaledIcon(new GIcon("icon.filechooser.places.my.computer"), 16, 16);
//@formatter:on
public static FSBIcons getInstance() {
@@ -35,6 +35,12 @@ import ghidra.util.task.TaskMonitor;
*/
public abstract class FSBNode extends GTreeSlowLoadingNode {
protected String name;
protected FSBNode(String name) {
this.name = name;
}
/**
* Returns the {@link FSRL} of the filesystem object that this node represents.
* <p>
@@ -64,7 +70,7 @@ public abstract class FSBNode extends GTreeSlowLoadingNode {
@Override
public String getName() {
return getFSRL().getName();
return name;
}
/**
@@ -19,6 +19,8 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import docking.widgets.tree.GTreeNode;
import ghidra.formats.gfilesystem.*;
import ghidra.util.exception.CancelledException;
@@ -40,15 +42,20 @@ public class FSBRootNode extends FSBNode {
private FSBFileNode prevNode;
private FSBRootNode modelNode;
private boolean cryptoStatusUpdated;
private Icon icon;
FSBRootNode(FileSystemRef fsRef) {
this(fsRef, null);
}
FSBRootNode(FileSystemRef fsRef, FSBFileNode prevNode) {
super(FSBComponentProvider.getDescriptiveFSName(fsRef.getFilesystem()));
this.fsRef = fsRef;
this.prevNode = prevNode;
this.modelNode = this;
this.icon = FSBComponentProvider.getFSIcon(fsRef.getFilesystem(), prevNode == null,
FSBIcons.getInstance());
}
@Override
@@ -69,6 +76,11 @@ public class FSBRootNode extends FSBNode {
setChildren(generateChildren(monitor));
}
@Override
public Icon getIcon(boolean expanded) {
return icon;
}
public void setCryptoStatusUpdated(boolean cryptoStatusUpdated) {
this.cryptoStatusUpdated = cryptoStatusUpdated;
}
@@ -118,13 +130,6 @@ public class FSBRootNode extends FSBNode {
}
}
@Override
public String getName() {
return modelNode.fsRef != null && !modelNode.fsRef.isClosed()
? modelNode.fsRef.getFilesystem().getName()
: " Missing ";
}
@Override
public String getToolTip() {
return getName();
@@ -178,10 +183,6 @@ public class FSBRootNode extends FSBNode {
: null;
}
public String getContainerName() {
return prevNode != null ? prevNode.getName() : "/";
}
private List<GFile> splitGFilePath(GFile f) {
List<GFile> result = new ArrayList<>();
while (f != null) {
@@ -157,6 +157,7 @@ public class FileSystemBrowserPlugin extends Plugin
currentBrowsers.put(fsFSRL, provider);
getTool().addComponentProvider(provider, false);
provider.afterAddedToTool();
provider.contextChanged();
}
if (show) {
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -19,10 +19,13 @@ import java.util.List;
import docking.action.DockingAction;
import docking.action.builder.ActionBuilder;
import ghidra.formats.gfilesystem.FileSystemRef;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.plugins.fsbrowser.*;
public class OpenFsFSBFileHandler implements FSBFileHandler {
private static final String FSB_OPEN_ROOT_FS = "FSB Open My Computer";
public static final String FSB_OPEN_FILE_SYSTEM_CHOOSER = "FSB Open File System Chooser";
public static final String FSB_OPEN_FILE_SYSTEM_IN_NEW_WINDOW =
"FSB Open File System In New Window";
@@ -62,10 +65,22 @@ public class OpenFsFSBFileHandler implements FSBFileHandler {
ac -> ac.getComponentProvider().openFileSystem(ac.getSelectedNode(), false))
.build(),
new ActionBuilder(FSB_OPEN_ROOT_FS, context.plugin().getName())
.description("Show the 'My Computer' location")
.enabledWhen(ac -> !context.fsbComponent().getGTree().isBusy())
.toolBarIcon(FSBIcons.MY_COMPUTER)
.toolBarGroup("B")
.onAction(ac -> {
FileSystemService fsService = context.fsService();
FileSystemRef fsRef =
fsService.getMountedFilesystem(fsService.getLocalFS().getFSRL());
context.plugin().createNewFileSystemBrowser(fsRef, true);
})
.build(),
new ActionBuilder(FSB_OPEN_FILE_SYSTEM_CHOOSER, context.plugin().getName())
.description("Open File System Chooser")
.withContext(FSBActionContext.class)
.enabledWhen(FSBActionContext::notBusy)
.enabledWhen(ac -> !context.fsbComponent().getGTree().isBusy())
.toolBarIcon(FSBIcons.OPEN_FILE_SYSTEM)
.toolBarGroup("B")
.onAction(ac -> context.plugin().openFileSystem())
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -65,10 +65,11 @@ public class FSRLTest {
}
@Test
public void testDOSPaths() throws MalformedURLException {
FSRL fsrl = FSRL.fromString("fsrl://a:\\dir\\filename.txt");
public void testPathsWithBackslashes() throws MalformedURLException {
FSRL fsrl = FSRL.fromString("fsrl:///dir/filename\\with\\backslashes");
assertEquals("fsrl", fsrl.getFS().getProtocol());
assertEquals("a:/dir/filename.txt", fsrl.getPath());
assertEquals("/dir/filename\\with\\backslashes", fsrl.getPath());
assertEquals("filename\\with\\backslashes", fsrl.getName());
}
@Test