mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-18 12:34:16 +08:00
GP-6402 Improved project name/path error checking. Added support for
Windows UNC project paths. Refactor of GhidraURL utility methods and URI use. Other related cleanup.
This commit is contained in:
+19
-24
@@ -44,6 +44,7 @@ import ghidra.trace.model.time.schedule.TraceSchedule;
|
||||
import ghidra.trace.model.time.schedule.TraceSchedule.TimeRadix;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.NotOwnerException;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
|
||||
public class DebuggerCoordinates {
|
||||
|
||||
@@ -68,8 +69,7 @@ public class DebuggerCoordinates {
|
||||
private static final String KEY_FRAME = "Frame";
|
||||
private static final String KEY_OBJ_PATH = "ObjectPath";
|
||||
|
||||
public static boolean equalsIgnoreTargetAndView(DebuggerCoordinates a,
|
||||
DebuggerCoordinates b) {
|
||||
public static boolean equalsIgnoreTargetAndView(DebuggerCoordinates a, DebuggerCoordinates b) {
|
||||
if (!Objects.equals(a.trace, b.trace)) {
|
||||
return false;
|
||||
}
|
||||
@@ -124,8 +124,8 @@ public class DebuggerCoordinates {
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"Coords(trace=%s,target=%s,thread=%s,view=%s,time=%s,frame=%d,path=%s)",
|
||||
trace, target, thread, view, time, frame, path);
|
||||
"Coords(trace=%s,target=%s,thread=%s,view=%s,time=%s,frame=%d,path=%s)", trace, target,
|
||||
thread, view, time, frame, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -244,15 +244,14 @@ public class DebuggerCoordinates {
|
||||
}
|
||||
|
||||
private static Integer resolveFrame(Target target, TraceThread thread, TraceSchedule time) {
|
||||
if (target == null || target.getSnap() != time.getSnap() ||
|
||||
!target.isSupportsFocus()) {
|
||||
if (target == null || target.getSnap() != time.getSnap() || !target.isSupportsFocus()) {
|
||||
return resolveFrame(thread, time);
|
||||
}
|
||||
return resolveFrame(target, target.getFocus());
|
||||
}
|
||||
|
||||
private static KeyPath resolvePath(Target target, TraceThread thread,
|
||||
Integer frame, TraceSchedule time) {
|
||||
private static KeyPath resolvePath(Target target, TraceThread thread, Integer frame,
|
||||
TraceSchedule time) {
|
||||
if (target.getSnap() != time.getSnap() || !target.isSupportsFocus()) {
|
||||
return resolvePath(target.getTrace(), thread, frame, time);
|
||||
}
|
||||
@@ -424,8 +423,8 @@ public class DebuggerCoordinates {
|
||||
Integer newFrame = resolveFrame(newThread, newTime);
|
||||
KeyPath threadOrFramePath = resolvePath(newThread, newFrame, newTime);
|
||||
KeyPath newPath = choose(path, threadOrFramePath);
|
||||
return new DebuggerCoordinates(trace, platform, target, newThread, view, newTime,
|
||||
newFrame, newPath);
|
||||
return new DebuggerCoordinates(trace, platform, target, newThread, view, newTime, newFrame,
|
||||
newPath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,8 +477,7 @@ public class DebuggerCoordinates {
|
||||
}
|
||||
|
||||
private DebuggerCoordinates replaceView(TraceProgramView newView) {
|
||||
return new DebuggerCoordinates(trace, platform, target, thread, newView, time, frame,
|
||||
path);
|
||||
return new DebuggerCoordinates(trace, platform, target, thread, newView, time, frame, path);
|
||||
}
|
||||
|
||||
private static TraceSchedule resolveTime(TraceProgramView view) {
|
||||
@@ -532,9 +530,8 @@ public class DebuggerCoordinates {
|
||||
if (object == null) {
|
||||
return null;
|
||||
}
|
||||
TraceStackFrame frame = object.queryCanonicalAncestorsInterface(TraceStackFrame.class)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
TraceStackFrame frame =
|
||||
object.queryCanonicalAncestorsInterface(TraceStackFrame.class).findFirst().orElse(null);
|
||||
return frame == null ? null : frame.getLevel();
|
||||
}
|
||||
|
||||
@@ -549,15 +546,13 @@ public class DebuggerCoordinates {
|
||||
return new DebuggerCoordinates(trace, platform, target, thread, view, time, frame,
|
||||
newPath);
|
||||
}
|
||||
TraceThread newThread = target != null
|
||||
? resolveThread(target, newPath)
|
||||
: resolveThread(trace, newPath);
|
||||
Integer newFrame = target != null
|
||||
? resolveFrame(target, newPath)
|
||||
: resolveFrame(trace, newPath);
|
||||
TraceThread newThread =
|
||||
target != null ? resolveThread(target, newPath) : resolveThread(trace, newPath);
|
||||
Integer newFrame =
|
||||
target != null ? resolveFrame(target, newPath) : resolveFrame(trace, newPath);
|
||||
|
||||
return new DebuggerCoordinates(trace, platform, target, newThread, view, time,
|
||||
newFrame, newPath);
|
||||
return new DebuggerCoordinates(trace, platform, target, newThread, view, time, newFrame,
|
||||
newPath);
|
||||
}
|
||||
|
||||
public DebuggerCoordinates pathNonCanonical(KeyPath newPath) {
|
||||
@@ -747,7 +742,7 @@ public class DebuggerCoordinates {
|
||||
"Not project owner: " + projLoc + "(" + pathname + ")");
|
||||
return null;
|
||||
}
|
||||
catch (IOException | LockException e) {
|
||||
catch (NotFoundException | IOException | LockException e) {
|
||||
Msg.error(DebuggerCoordinates.class, "Project error: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
-6
@@ -20,8 +20,7 @@ import static ghidra.program.util.ProgramEvent.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.function.IntSupplier;
|
||||
@@ -462,10 +461,10 @@ public class BSimSearchResultsProvider extends ComponentProviderAdapter {
|
||||
ProgramManager service = tool.getService(ProgramManager.class);
|
||||
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
URL url = new URI(urlString).toURL();
|
||||
return service.openProgram(url, ProgramManager.OPEN_CURRENT);
|
||||
}
|
||||
catch (MalformedURLException exc) {
|
||||
catch (MalformedURLException | URISyntaxException exc) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -474,7 +473,7 @@ public class BSimSearchResultsProvider extends ComponentProviderAdapter {
|
||||
ProgramManager service = tool.getService(ProgramManager.class);
|
||||
|
||||
try {
|
||||
URL url = new URL(urlString);
|
||||
URL url = new URI(urlString).toURL();
|
||||
Program remote = service.openCachedProgram(url, this);
|
||||
if (remote == null) {
|
||||
return null;
|
||||
@@ -486,7 +485,7 @@ public class BSimSearchResultsProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
return remote;
|
||||
}
|
||||
catch (MalformedURLException exc) {
|
||||
catch (MalformedURLException | URISyntaxException exc) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-6
@@ -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.
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package ghidra.features.bsim.gui.search.results.apply;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import docking.DockingWindowManager;
|
||||
@@ -199,9 +198,9 @@ public abstract class AbstractBSimApplyTask extends ProgramTask {
|
||||
private URL getRemoteProgramURL(BSimMatchResult result) {
|
||||
String urlString = result.getExecutableURLString();
|
||||
try {
|
||||
return new URL(urlString);
|
||||
return new URI(urlString).toURL();
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (MalformedURLException | URISyntaxException e) {
|
||||
error("Bad URL: " + urlString, result);
|
||||
}
|
||||
return null;
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ public class BSimClientFactory {
|
||||
// "ghidra://host/repo?service=bsim"
|
||||
// String repositoryURL = "ghidra://" + ghidraURL.getAuthority() + "?service=bsim";
|
||||
|
||||
// Currenly all we do is assume that the BSim server is a PostgreSQL server
|
||||
// Currently, all we do is assume that the BSim server is a PostgreSQL server
|
||||
// on the same host and with the same repo name as the ghidra server
|
||||
repositoryURL = "postgresql://" + url.getHost(); // Just use the hostname
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
||||
* @param userinfo connection user info, {@code username[:password]} (ignored for {@link DBType#file}).
|
||||
* If blank, {@link ClientUtil#getUserName()} is used.
|
||||
* @param host host name (ignored for {@link DBType#file})
|
||||
* @param port port number (ignored for {@link DBType#file})
|
||||
* @param port port number (ignored for {@link DBType#file}, -1 for default)
|
||||
* @param dbName name of database (simple database name except for {@link DBType#file}
|
||||
* which should reflect an absolute file path. On Windows OS the path may start with a
|
||||
* drive letter.
|
||||
@@ -118,7 +118,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
|
||||
*
|
||||
* @param dbType BSim DB type
|
||||
* @param host host name (ignored for {@link DBType#file})
|
||||
* @param port port number (ignored for {@link DBType#file})
|
||||
* @param port port number (ignored for {@link DBType#file}, -1 for default)
|
||||
* @param dbName name of database (simple database name except for {@link DBType#file}
|
||||
* which should reflect an absolute file path. On Windows OS the path may start with a
|
||||
* drive letter.
|
||||
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.bsim.query.client;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.*;
|
||||
|
||||
import org.xml.sax.*;
|
||||
|
||||
import generic.lsh.vector.LSHVectorFactory;
|
||||
import ghidra.features.bsim.query.BSimServerInfo;
|
||||
import ghidra.features.bsim.query.FunctionDatabase;
|
||||
import ghidra.features.bsim.query.description.DatabaseInformation;
|
||||
import ghidra.features.bsim.query.protocol.*;
|
||||
import ghidra.framework.client.ClientUtil;
|
||||
import ghidra.xml.NonThreadedXmlPullParserImpl;
|
||||
import ghidra.xml.XmlPullParser;
|
||||
|
||||
public class FunctionDatabaseProxy implements FunctionDatabase {
|
||||
private DatabaseInformation info;
|
||||
private LSHVectorFactory vectorFactory;
|
||||
private URL httpURL;
|
||||
private BSimError lasterror;
|
||||
private Status status;
|
||||
private boolean isinit;
|
||||
private XmlErrorHandler xmlErrorHandler;
|
||||
|
||||
static class XmlErrorHandler implements ErrorHandler {
|
||||
|
||||
@Override
|
||||
public void warning(SAXParseException exception) throws SAXException {
|
||||
// Ignore warnings
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException exception) throws SAXException {
|
||||
throw exception;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException exception) throws SAXException {
|
||||
throw exception;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public FunctionDatabaseProxy(URL url) throws MalformedURLException {
|
||||
httpURL = new URL(url.toString()); // Make sure URL has a real handler
|
||||
lasterror = null;
|
||||
info = null;
|
||||
vectorFactory = FunctionDatabase.generateLSHVectorFactory();
|
||||
status = Status.Unconnected;
|
||||
isinit = false;
|
||||
xmlErrorHandler = new XmlErrorHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionType getConnectionType() {
|
||||
return ConnectionType.Unencrypted_No_Authentication;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return ClientUtil.getUserName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LSHVectorFactory getLSHVectorFactory() {
|
||||
return vectorFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseInformation getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareLayout() {
|
||||
if (info.layout_version == PostgresFunctionDatabase.LAYOUT_VERSION) {
|
||||
return 0;
|
||||
}
|
||||
return (info.layout_version < PostgresFunctionDatabase.LAYOUT_VERSION) ? -1 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getURLString() {
|
||||
return httpURL.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BSimServerInfo getServerInfo() {
|
||||
return new BSimServerInfo(httpURL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
if (isinit) {
|
||||
return true;
|
||||
}
|
||||
if (httpURL == null) {
|
||||
status = Status.Error;
|
||||
lasterror =
|
||||
new FunctionDatabase.BSimError(ErrorCategory.Initialization, "MalformedURL");
|
||||
return false;
|
||||
}
|
||||
QueryInfo queryInfo = new QueryInfo();
|
||||
QueryResponseRecord response = query(queryInfo);
|
||||
if (response == null) {
|
||||
return false;
|
||||
}
|
||||
info = ((ResponseInfo) response).info;
|
||||
status = Status.Ready;
|
||||
isinit = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
status = Status.Unconnected;
|
||||
isinit = false;
|
||||
info = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BSimError getLastError() {
|
||||
return lasterror;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryResponseRecord query(BSimQuery<?> query) {
|
||||
HttpURLConnection connection;
|
||||
query.buildResponseTemplate();
|
||||
try {
|
||||
lasterror = null;
|
||||
connection = (HttpURLConnection) httpURL.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setDoOutput(true);
|
||||
BufferedWriter writer =
|
||||
new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
|
||||
query.saveXml(writer);
|
||||
writer.close();
|
||||
XmlPullParser parser = new NonThreadedXmlPullParserImpl(connection.getInputStream(),
|
||||
"response", xmlErrorHandler, false);
|
||||
if (parser.peek().getName().equals("error")) {
|
||||
ResponseError respError = new ResponseError();
|
||||
respError.restoreXml(parser, vectorFactory);
|
||||
parser.dispose();
|
||||
lasterror =
|
||||
new FunctionDatabase.BSimError(ErrorCategory.Fatal, respError.errorMessage);
|
||||
query.clearResponse();
|
||||
return null;
|
||||
}
|
||||
QueryResponseRecord response = query.getResponse();
|
||||
response.restoreXml(parser, vectorFactory);
|
||||
parser.dispose();
|
||||
if (response instanceof ResponseInfo) {
|
||||
// Query is one of CreateDatabase, InstallCategoryRequest, InstallMetadataRequest, or QueryInfo
|
||||
info = ((ResponseInfo) response).info;
|
||||
status = Status.Ready;
|
||||
isinit = true;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
lasterror = new FunctionDatabase.BSimError(ErrorCategory.Connection, ex.getMessage());
|
||||
status = Status.Error;
|
||||
query.clearResponse();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+9
-15
@@ -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.
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package ghidra.features.bsim.query.description;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
@@ -222,16 +222,10 @@ public class ExecutableRecord implements Comparable<ExecutableRecord> {
|
||||
protected void setRepository(String repo, String newpath) {
|
||||
repository = null;
|
||||
if (repo != null) {
|
||||
URL ghidraURL;
|
||||
try {
|
||||
ghidraURL = new URL(repo);
|
||||
if (!GhidraURL.isGhidraURL(repo) || (!GhidraURL.isServerRepositoryURL(ghidraURL) &&
|
||||
!GhidraURL.isLocalProjectURL(ghidraURL))) {
|
||||
throw new IllegalArgumentException("Unsupported repository URL: " + repo);
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException("Unsupported repository URL: " + repo, e);
|
||||
URL ghidraURL = GhidraURL.toURL(repo);
|
||||
if (!GhidraURL.isServerRepositoryURL(ghidraURL) &&
|
||||
!GhidraURL.isLocalURL(ghidraURL)) {
|
||||
throw new IllegalArgumentException("Unsupported repository URL: " + repo);
|
||||
}
|
||||
URL projectURL = GhidraURL.getProjectURL(ghidraURL);
|
||||
repository = projectURL.toExternalForm();
|
||||
@@ -473,7 +467,7 @@ public class ExecutableRecord implements Comparable<ExecutableRecord> {
|
||||
}
|
||||
final StringBuffer buf = new StringBuffer();
|
||||
buf.append(repository);
|
||||
if (GhidraURL.isLocalGhidraURL(repository)) {
|
||||
if (GhidraURL.isLocalURL(repository)) {
|
||||
if (!repository.endsWith("?")) {
|
||||
// local URLs add path as a query string
|
||||
buf.append("?");
|
||||
|
||||
+21
-12
@@ -15,10 +15,8 @@
|
||||
*/
|
||||
package ghidra.features.bsim.query.ingest;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -221,19 +219,19 @@ public class BSimLaunchable implements GhidraLaunchable {
|
||||
return new BulkSignatures(serverInfo, connectingUserName);
|
||||
}
|
||||
|
||||
private void setupGhidraURL(String ghidraURLString) throws MalformedURLException {
|
||||
private void setupGhidraURL(String ghidraURLString) throws IllegalArgumentException {
|
||||
|
||||
if (ghidraURLString == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GhidraURL.isGhidraURL(ghidraURLString)) {
|
||||
throw new MalformedURLException("URL is not ghidra protocol: " + ghidraURLString);
|
||||
throw new IllegalArgumentException("URL is not ghidra protocol: " + ghidraURLString);
|
||||
}
|
||||
ghidraURL = new URL(ghidraURLString);
|
||||
ghidraURL = GhidraURL.toURL(ghidraURLString);
|
||||
if (!GhidraURL.isServerRepositoryURL(ghidraURL) &&
|
||||
!GhidraURL.isLocalProjectURL(ghidraURL)) {
|
||||
throw new MalformedURLException("Invalid repository URL: " + ghidraURLString);
|
||||
!GhidraURL.isLocalURL(ghidraURL)) {
|
||||
throw new IllegalArgumentException("Invalid repository URL: " + ghidraURLString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,8 +256,18 @@ public class BSimLaunchable implements GhidraLaunchable {
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to infer ghidra URL from BSim file DB URL");
|
||||
}
|
||||
ghidraURLString = "ghidra://" + bsimURL.getHost() + bsimURL.getPath();
|
||||
setupGhidraURL(ghidraURLString);
|
||||
|
||||
// Derive Ghidra URL from BSim DB host and dbName
|
||||
String path = bsimURL.getPath();
|
||||
try {
|
||||
path = URLDecoder.decode(path, "UTF-8");
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new MalformedURLException(e.getMessage());
|
||||
}
|
||||
String dbName = path.substring(path.lastIndexOf('/') + 1);
|
||||
|
||||
ghidraURL = GhidraURL.makeURL(bsimURL.getHost(), -1, dbName);
|
||||
}
|
||||
}
|
||||
else if (ghidraURLString != null) {
|
||||
@@ -476,7 +484,8 @@ public class BSimLaunchable implements GhidraLaunchable {
|
||||
}
|
||||
}
|
||||
|
||||
private void processSigAndUpdateOptions(String urlstring) throws MalformedURLException {
|
||||
private void processSigAndUpdateOptions(String urlstring)
|
||||
throws IllegalArgumentException, MalformedURLException {
|
||||
String bsimURLOption = optionValueMap.get(BSIM_URL_OPTION);
|
||||
String configOption = optionValueMap.get(CONFIG_OPTION);
|
||||
if (configOption != null) {
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ public abstract class IterateRepository {
|
||||
public void process(URL ghidraURL, TaskMonitor monitor) throws Exception, CancelledException {
|
||||
|
||||
if (!GhidraURL.isServerRepositoryURL(ghidraURL) &&
|
||||
!GhidraURL.isLocalProjectURL(ghidraURL)) {
|
||||
!GhidraURL.isLocalURL(ghidraURL)) {
|
||||
throw new MalformedURLException("Unsupported repository URL: " + ghidraURL);
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -20,6 +20,8 @@ import java.io.File;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.ReusableDialogComponentProvider;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.button.BrowseButton;
|
||||
@@ -234,17 +236,23 @@ public class ArchiveDialog extends ReusableDialogComponentProvider {
|
||||
*/
|
||||
private boolean checkInput() {
|
||||
String pathname = getArchivePathName();
|
||||
if ((pathname == null) || (pathname.equals(""))) {
|
||||
if (StringUtils.isBlank(pathname)) {
|
||||
setStatusText("Specify an archive file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
File file = new File(pathname);
|
||||
String name = file.getName();
|
||||
if (!NamingUtilities.isValidProjectName(name)) {
|
||||
setStatusText("Archive name contains invalid characters.");
|
||||
|
||||
// Impose same naming restrictions as Project name uses
|
||||
try {
|
||||
NamingUtilities.checkName(name, "Archive name");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
setStatusText(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -322,7 +330,7 @@ public class ArchiveDialog extends ReusableDialogComponentProvider {
|
||||
jarFile = new File(archivePathName);
|
||||
}
|
||||
else if (projectLocator != null) {
|
||||
jarFile = new File(projectLocator.toString() + ArchivePlugin.ARCHIVE_EXTENSION);
|
||||
jarFile = new File(projectLocator.getName() + ArchivePlugin.ARCHIVE_EXTENSION);
|
||||
}
|
||||
jarFileChooser.setSelectedFile(jarFile);
|
||||
jarFileChooser.setApproveButtonText(approveButtonText);
|
||||
@@ -341,15 +349,7 @@ public class ArchiveDialog extends ReusableDialogComponentProvider {
|
||||
String name = file.getName();
|
||||
if (!NamingUtilities.isValidProjectName(name)) {
|
||||
Msg.showError(getClass(), null, "Invalid Archive Name",
|
||||
name + " is not a valid archive name");
|
||||
continue;
|
||||
}
|
||||
|
||||
File f = projectLocator.getProjectDir();
|
||||
String filename = f.getAbsolutePath();
|
||||
if (chosenPathname.indexOf(filename) >= 0) {
|
||||
Msg.showError(getClass(), null, "Invalid Archive Name",
|
||||
"Output file cannot be inside of Project");
|
||||
"Archive name contains invalid characters or is too long");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
+21
-20
@@ -60,7 +60,7 @@ public class RestoreDialog extends ReusableDialogComponentProvider {
|
||||
private JTextField projectNameField;
|
||||
|
||||
private String archivePathName;
|
||||
private ProjectLocator restoreURL;
|
||||
private ProjectLocator restoreLocator;
|
||||
|
||||
public RestoreDialog(ArchivePlugin plugin) {
|
||||
super("Restore Project Archive");
|
||||
@@ -246,14 +246,14 @@ public class RestoreDialog extends ReusableDialogComponentProvider {
|
||||
/**
|
||||
* Display this dialog.
|
||||
* @param pathName The pathname of the archive file containing the data to restore.
|
||||
* @param projectLocator The project URL of the location to which the restore archive will be
|
||||
* @param projectLocator The project locator of the location to which the restore archive will be
|
||||
* extracted.
|
||||
*
|
||||
* @return true if the user submitted a valid value, false if user cancelled.
|
||||
*/
|
||||
public boolean showDialog(String pathName, ProjectLocator projectLocator) {
|
||||
this.archivePathName = pathName;
|
||||
this.restoreURL = projectLocator;
|
||||
this.restoreLocator = projectLocator;
|
||||
String projectName = projectNameField.getText();
|
||||
if (projectName == null || projectName.equals("")) {
|
||||
projectName = ArchivePlugin.getProjectName(pathName);
|
||||
@@ -287,11 +287,11 @@ public class RestoreDialog extends ReusableDialogComponentProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the restore directory.
|
||||
* @return the URL for the restore directory.
|
||||
* Get the project locator for the restore directory.
|
||||
* @return the project locator for the restore directory.
|
||||
*/
|
||||
ProjectLocator getRestoreURL() {
|
||||
return restoreURL;
|
||||
return restoreLocator;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
@@ -316,17 +316,24 @@ public class RestoreDialog extends ReusableDialogComponentProvider {
|
||||
return false;
|
||||
}
|
||||
String restoreProjectName = projectNameField.getText().trim();
|
||||
if (restoreProjectName == null || restoreProjectName.equals("") ||
|
||||
!NamingUtilities.isValidName(restoreProjectName)) {
|
||||
if (restoreProjectName == null || restoreProjectName.equals("")) {
|
||||
setStatusText("Specify a valid project name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
archivePathName = archiveName;
|
||||
restoreURL = new ProjectLocator(restoreDir, restoreProjectName);
|
||||
try {
|
||||
NamingUtilities.checkProjectName(restoreProjectName);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
setStatusText(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
File projFile = restoreURL.getMarkerFile();
|
||||
File projDir = restoreURL.getProjectDir();
|
||||
archivePathName = archiveName;
|
||||
restoreLocator = new ProjectLocator(restoreDir, restoreProjectName);
|
||||
|
||||
File projFile = restoreLocator.getMarkerFile();
|
||||
File projDir = restoreLocator.getProjectDir();
|
||||
setStatusText("");
|
||||
if (projFile.exists() || projDir.exists()) {
|
||||
Msg.showInfo(getClass(), getComponent(), "Project Exists",
|
||||
@@ -426,12 +433,6 @@ public class RestoreDialog extends ReusableDialogComponentProvider {
|
||||
}
|
||||
|
||||
File file = selectedFile;
|
||||
String chosenName = file.getName();
|
||||
if (!NamingUtilities.isValidProjectName(chosenName)) {
|
||||
Msg.showError(getClass(), null, "Invalid Archive Name",
|
||||
chosenName + " is not a valid archive name");
|
||||
continue;
|
||||
}
|
||||
|
||||
Preferences.setProperty(ArchivePlugin.LAST_ARCHIVE_DIR, file.getParent());
|
||||
pathname = file.getAbsolutePath();
|
||||
@@ -452,8 +453,8 @@ public class RestoreDialog extends ReusableDialogComponentProvider {
|
||||
String chooseDirectory(String approveButtonText, String approveToolTip) {
|
||||
GhidraFileChooser dirChooser = createDirectoryChooser();
|
||||
dirChooser.setTitle("Restore a Ghidra Project - Directory");
|
||||
if (restoreURL != null) {
|
||||
dirChooser.setSelectedFile(new File(restoreURL.getLocation()));
|
||||
if (restoreLocator != null) {
|
||||
dirChooser.setSelectedFile(new File(restoreLocator.getLocation()));
|
||||
}
|
||||
dirChooser.setApproveButtonText(approveButtonText);
|
||||
dirChooser.setApproveButtonToolTipText(approveToolTip);
|
||||
|
||||
+5
-6
@@ -21,6 +21,7 @@ import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -238,16 +239,14 @@ public class ShowInstructionInfoPlugin extends ProgramPlugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
URL url = new File(filename).toURI().toURL();
|
||||
|
||||
URI uri = new File(filename).toURI();
|
||||
String pageNumber = entry.getPageNumber();
|
||||
if (pageNumber != null) {
|
||||
// include manual page as query string (respected by PDF readers)
|
||||
String fileNameAndPage = url.getFile() + "#page=" + pageNumber;
|
||||
url = new URL(url.getProtocol(), null, fileNameAndPage);
|
||||
String pageRef = "#page=" + pageNumber;
|
||||
uri = uri.resolve(pageRef);
|
||||
}
|
||||
|
||||
return url;
|
||||
return uri.toURL();
|
||||
}
|
||||
|
||||
ManualEntry locateManualEntry(ProgramActionContext context, Language language) {
|
||||
|
||||
+6
-7
@@ -16,8 +16,7 @@
|
||||
package ghidra.app.plugin.core.progmgr;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -44,6 +43,7 @@ import ghidra.framework.model.*;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
@@ -187,11 +187,10 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager, Opti
|
||||
|
||||
@Override
|
||||
public Program openProgram(URL ghidraURL, int state) {
|
||||
String location = ghidraURL.getRef();
|
||||
Program program = openProgram(new ProgramLocator(ghidraURL), state);
|
||||
|
||||
String location = GhidraURL.getDecodedReference(ghidraURL);
|
||||
if (program != null && location != null && state == OPEN_CURRENT) {
|
||||
gotoProgramRef(program, ghidraURL.getRef());
|
||||
gotoProgramRef(program, location);
|
||||
programMgr.saveLocation();
|
||||
}
|
||||
return program;
|
||||
@@ -904,9 +903,9 @@ public class ProgramManagerPlugin extends Plugin implements ProgramManager, Opti
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return new URL(url);
|
||||
return new URI(url).toURL();
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (MalformedURLException | URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ public class HeadlessAnalyzer {
|
||||
throw new MalformedURLException("Unsupported repository URL: " + ghidraURL);
|
||||
}
|
||||
|
||||
if (GhidraURL.isLocalProjectURL(ghidraURL)) {
|
||||
if (GhidraURL.isLocalURL(ghidraURL)) {
|
||||
Msg.error(this,
|
||||
"Ghidra URL command form does not supported local project URLs (ghidra:/path...)");
|
||||
return;
|
||||
@@ -1808,10 +1808,7 @@ public class HeadlessAnalyzer {
|
||||
try {
|
||||
tempProject = new HeadlessProject(getProjectManager(), locator);
|
||||
}
|
||||
catch (NotOwnerException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
catch (LockException e) {
|
||||
catch (NotFoundException | NotOwnerException | LockException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
@@ -1852,7 +1849,7 @@ public class HeadlessAnalyzer {
|
||||
private static class HeadlessProject extends DefaultProject {
|
||||
|
||||
HeadlessProject(HeadlessGhidraProjectManager projectManager, ProjectLocator projectLocator)
|
||||
throws NotOwnerException, LockException, IOException {
|
||||
throws NotFoundException, NotOwnerException, LockException, IOException {
|
||||
super(projectManager, projectLocator, false);
|
||||
AppInfo.setActiveProject(this);
|
||||
}
|
||||
|
||||
+6
-10
@@ -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.
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
|
||||
import docking.widgets.fieldpanel.field.AttributedString;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
@@ -78,15 +77,12 @@ public class URLAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
}
|
||||
|
||||
private URL getURLForString(String urlString) {
|
||||
URL url = null;
|
||||
try {
|
||||
url = new URL(urlString);
|
||||
return new URI(urlString).toURL();
|
||||
}
|
||||
catch (MalformedURLException exc) {
|
||||
// we return null
|
||||
catch (MalformedURLException | URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
-5
@@ -18,8 +18,8 @@ package ghidra.framework.main.datatree;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.dnd.DropTargetDropEvent;
|
||||
import java.io.File;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@@ -64,12 +64,12 @@ public final class LinuxFileUrlHandler extends AbstractFileListFlavorHandler {
|
||||
|
||||
return toFiles(transferData, s -> {
|
||||
try {
|
||||
return new File(new URL(s.replaceAll(" ", "%20")).toURI()); // fixup spaces
|
||||
return new File(new URI(s));
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (URISyntaxException e) {
|
||||
// this could be the case that this handler is attempting to process an arbitrary
|
||||
// String that is not actually a URL
|
||||
Msg.trace(this, "Not a URL: '" + s + "'", e);
|
||||
Msg.trace(this, "Not a valid URL: '" + s + "'", e);
|
||||
return null;
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
+17
-9
@@ -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.
|
||||
@@ -133,15 +133,23 @@ public class SaveToolConfigDialogTest extends AbstractGhidraHeadedIntegrationTes
|
||||
|
||||
@Test
|
||||
public void testInvalidName() throws Exception {
|
||||
setText(toolNameField, "My Test Tool", true);
|
||||
|
||||
JLabel statusLabel = (JLabel) findComponentByName(saveDialog, "statusLabel");
|
||||
String msg = statusLabel.getText();
|
||||
pressButtonByText(saveDialog, "Cancel");
|
||||
while (saveDialog.isVisible()) {
|
||||
Thread.sleep(5);
|
||||
}
|
||||
|
||||
setText(toolNameField, "My Test Tool", true);
|
||||
waitForSwing();
|
||||
assertEquals("Name cannot have spaces.", msg);
|
||||
String msg = statusLabel.getText();
|
||||
assertEquals("Name cannot have spaces", msg);
|
||||
|
||||
setText(toolNameField, ".MyTestTool", true);
|
||||
waitForSwing();
|
||||
msg = statusLabel.getText();
|
||||
assertEquals("Name cannot start with a '.'", msg);
|
||||
|
||||
setText(toolNameField, "My`Tool`", true);
|
||||
waitForSwing();
|
||||
msg = statusLabel.getText();
|
||||
assertEquals("Invalid character in name: '`'", msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
package ghidra;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import docking.framework.DockingApplicationConfiguration;
|
||||
@@ -59,8 +59,10 @@ public class GhidraGo implements GhidraLaunchable {
|
||||
if (args != null && args.length > 0) {
|
||||
ghidra.framework.protocol.ghidra.Handler.registerHandler();
|
||||
sender = new GhidraGoSender();
|
||||
|
||||
// check if ghidra url is valid
|
||||
GhidraURL.getProjectURL(new URL(args[0]));
|
||||
URL ghidraUrl = new URI(args[0]).toURL();
|
||||
GhidraURL.getProjectURL(ghidraUrl); // perform Ghidra URL validation only
|
||||
|
||||
startGhidraIfNeeded(layout);
|
||||
|
||||
@@ -76,7 +78,7 @@ public class GhidraGo implements GhidraLaunchable {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
catch (URISyntaxException | MalformedURLException | IllegalArgumentException e) {
|
||||
System.err.println("\n" + "USAGE: ghidraGo <ghidraURL>\n\n" +
|
||||
"Ghidra URL Forms (ghidraURL):\n" +
|
||||
" ghidra://<hostname>[:<port>]/<repo-name>[/<folder-path>[/<program-name>]]\n" +
|
||||
@@ -172,6 +174,6 @@ public class GhidraGo implements GhidraLaunchable {
|
||||
}
|
||||
|
||||
Msg.info(this, "Starting new Ghidra using ghidraRun script at " + ghidraRunPath);
|
||||
return Runtime.getRuntime().exec(ghidraRunPath.toString());
|
||||
return Runtime.getRuntime().exec(new String[] { ghidraRunPath.toString() });
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import ghidra.util.Msg;
|
||||
packageName = CorePluginPackage.NAME,
|
||||
shortDescription = "Listens for new GhidraURL's to launch using FrontEndTool's" +
|
||||
" accept method",
|
||||
description = "Polls the ghidraGo directory for any url files written by the " +
|
||||
description = "Polls the ghidraGo directory for any URL files written by the " +
|
||||
"GhidraGoSender and processes them in Ghidra",
|
||||
eventsConsumed = {ProjectPluginEvent.class})
|
||||
//@formatter:on
|
||||
|
||||
@@ -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.
|
||||
@@ -130,9 +130,13 @@ public class RepositoryManager {
|
||||
|
||||
validateUser(currentUser);
|
||||
|
||||
if (!NamingUtilities.isValidProjectName(name)) {
|
||||
throw new IOException("Invalid repository name: " + name);
|
||||
try {
|
||||
NamingUtilities.checkName(name, "Repository name");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
|
||||
if (repositoryMap.containsKey(name)) {
|
||||
throw new DuplicateFileException("Repository named " + name + " already exists");
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.apache.logging.log4j.Logger;
|
||||
import generic.hash.HashUtilities;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.util.NamingUtilities;
|
||||
import ghidra.util.NumericUtilities;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
@@ -758,14 +757,13 @@ public class UserManager {
|
||||
Pattern.compile("[a-zA-Z0-9][a-zA-Z0-9.\\-_/\\\\]*");
|
||||
|
||||
/**
|
||||
* Ensures a name only contains valid characters and meets length limitations.
|
||||
* Ensures a name only contains valid characters.
|
||||
*
|
||||
* @param s name string
|
||||
* @return boolean true if valid name, false if not valid
|
||||
*/
|
||||
public static boolean isValidUserName(String s) {
|
||||
return VALID_USERNAME_REGEX.matcher(s).matches() &&
|
||||
s.length() <= NamingUtilities.MAX_NAME_LENGTH;
|
||||
return VALID_USERNAME_REGEX.matcher(s).matches();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-2
@@ -110,9 +110,14 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
|
||||
*/
|
||||
public static URL getMinimalURL(URL url) {
|
||||
try {
|
||||
return new URL(url, "/");
|
||||
URI uri = url.toURI();
|
||||
if (uri.isOpaque()) {
|
||||
// Can't easily simplify - GhidraURL could help but is not visible
|
||||
return url;
|
||||
}
|
||||
return uri.resolve("/").toURL();
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (MalformedURLException | URISyntaxException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -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.
|
||||
@@ -15,129 +15,135 @@
|
||||
*/
|
||||
package ghidra.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import ghidra.framework.store.local.LocalFileSystem;
|
||||
import util.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class with static methods for validating project file names.
|
||||
* {@link NamingUtilities} is a static utility class with methods for validating project file
|
||||
* names or constrained file path elements.
|
||||
*/
|
||||
public final class NamingUtilities {
|
||||
|
||||
/**
|
||||
* Max length for a name.
|
||||
*/
|
||||
public final static int MAX_NAME_LENGTH = 60;
|
||||
|
||||
private final static char MANGLE_CHAR = '_';
|
||||
|
||||
private final static Set<Character> VALID_NAME_SET = CollectionUtils.asSet('.', '-', ' ', '_');
|
||||
// Restricted character set for Ghidra related file paths.
|
||||
//
|
||||
// NOTE: When adding additional characters great care must be taken with Ghidra URI/URL encode/decode
|
||||
// to ensure that proper roundtrip for path, query and ref/fragment fields work as expected.
|
||||
// This is particularly a concern with the '+' character.
|
||||
|
||||
public final static Set<Character> VALID_NAME_CHARSET =
|
||||
Collections.unmodifiableSet(
|
||||
Set.of('.', '-', '=', '@', ' ', '_', '(', ')', '[', ']'));
|
||||
|
||||
private NamingUtilities() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the given string is a valid.
|
||||
* Rules:
|
||||
* <ul>
|
||||
* <li>All characters must be a letter, digit (0..9), period, hyphen, underscore or space</li>
|
||||
* <li>May not exceed a length of 60 characters</li>
|
||||
* </ul>
|
||||
* @param name name to validate
|
||||
* @return true if specified name is valid, else false
|
||||
* @deprecated method has been deprecated due to improper and widespread use.
|
||||
* New methods include {@link NamingUtilities#isValidProjectName(String)} and
|
||||
* {@link LocalFileSystem#testValidName(String,boolean)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isValidName(String name) {
|
||||
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((name.length() < 1) || (name.length() > MAX_NAME_LENGTH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (!Character.isLetterOrDigit(c) && !VALID_NAME_SET.contains(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the given string is a valid project name.
|
||||
* <p>
|
||||
* Rules:
|
||||
* <ul>
|
||||
* <li>Name may not be blank (i.e., no characters or all space characters)</li>
|
||||
* <li>Name may not start with period</li>
|
||||
* <li>All characters must be a letter, digit (0..9), period, hyphen, underscore or space</li>
|
||||
* <li>May not exceed a length of 60 characters</li>
|
||||
* <li>All characters must be a letter, digit (0..9), or within the allowed character set:
|
||||
* '.', '-', '=', '@', ' ', '_', '(', ')', '[', ']' </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param name name to validate
|
||||
* @return true if specified name is valid, else false
|
||||
*/
|
||||
public static boolean isValidProjectName(String name) {
|
||||
if (name == null) {
|
||||
try {
|
||||
checkProjectName(name);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name.startsWith(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((name.length() < 1) || (name.length() > MAX_NAME_LENGTH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (!Character.isLetterOrDigit(c) && !VALID_NAME_SET.contains(c)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the invalid character in the given name.
|
||||
* <p>
|
||||
* This method should only be used with {@link #isValidName(String)}} and <b>not</b>
|
||||
* {@link #isValidProjectName(String)}
|
||||
* Check the specified project name for character restrictions.
|
||||
*
|
||||
* @param name the name with an invalid character
|
||||
* @return the invalid character or 0 if no invalid character can be found
|
||||
* @see #isValidName(String)
|
||||
* @deprecated this method may be removed in a subsequent release due to
|
||||
* limited use and applicability (project names and project file names have
|
||||
* different naming restrictions).
|
||||
* @param name project name
|
||||
* @throws IllegalArgumentException if name restrictions are violated
|
||||
*/
|
||||
@Deprecated
|
||||
public static char findInvalidChar(String name) {
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
char c = name.charAt(i);
|
||||
if (!Character.isLetterOrDigit(c) && !VALID_NAME_SET.contains(c)) {
|
||||
return c;
|
||||
}
|
||||
public static void checkProjectName(String name) throws IllegalArgumentException {
|
||||
checkName(name, "Project name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the specified project or file path element name for character restrictions.
|
||||
* The specified path element must exclude any path separators and must nut include any Windows
|
||||
* drive specification (e.g., {@code C:}). If this naming restriction needs to be imposed on
|
||||
* an entire path, it must be invoked on each path element separately.
|
||||
* <p>
|
||||
* Restrictions include:
|
||||
* <ul>
|
||||
* <li>Path element may not be blank (i.e., no characters or all space characters).</li>
|
||||
* <li>Path element may not start with a '.' which may result in path traversal or hidden
|
||||
* file/folder use.</li>
|
||||
* <li>Path element may only contain the letters, numbers, or the following characters:
|
||||
* '.', '-', '=', '@', ' ', '_', '(', ')', '[', ']'</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param pathElement project or file path element (use of leading and trailing spaces should be
|
||||
* avoided but is not prohibited).
|
||||
* @param elementType descriptive name for type of path element or null for default: "Path element"
|
||||
* @throws IllegalArgumentException if name restrictions are violated
|
||||
*/
|
||||
public static void checkName(String pathElement, String elementType)
|
||||
throws IllegalArgumentException {
|
||||
|
||||
String type = StringUtils.isBlank(elementType) ? "Path element" : elementType;
|
||||
|
||||
if (StringUtils.isBlank(pathElement)) {
|
||||
throw new IllegalArgumentException("A blank " + type + " is not allowed");
|
||||
}
|
||||
return (char) 0;
|
||||
if (pathElement.startsWith(".")) { // also prevents '.' and '..' path elements
|
||||
throw new IllegalArgumentException(type + " starting with '.' is not permitted");
|
||||
}
|
||||
String invalidChar = findInvalidChar(pathElement);
|
||||
if (invalidChar != null) {
|
||||
throw new IllegalArgumentException(
|
||||
type + " contains invalid character: '" + invalidChar + "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify an invalid/unsupported character which may be present in the specific name.
|
||||
* This method applies to project and individual path name elements only.
|
||||
*
|
||||
* @param name string to be scanned
|
||||
* @return an invalid/unsupported character found or null. A string is used to allow for
|
||||
* rendering of non-ASCII characters.
|
||||
*/
|
||||
public static String findInvalidChar(String name) {
|
||||
AtomicReference<String> invalidChar = new AtomicReference<>();
|
||||
name.codePoints().forEach(cp -> {
|
||||
if (Character.isLetterOrDigit(cp)) {
|
||||
return;
|
||||
}
|
||||
// Allow only ASCII symbols from the whitelist
|
||||
if (cp <= 0x7F && VALID_NAME_CHARSET.contains((char) cp)) {
|
||||
return;
|
||||
}
|
||||
invalidChar.set(new String(Character.toChars(cp)));
|
||||
});
|
||||
return invalidChar.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string such that all uppercase characters in the given string are
|
||||
* replaced by the MANGLE_CHAR followed by the lowercase version of the character.
|
||||
* MANGLE_CHARs are replaced by 2 MANGLE_CHARs.
|
||||
*
|
||||
* This method is to get around the STUPID windows problem where filenames are
|
||||
* not case sensitive. Under Windows, Foo.exe and foo.exe represent
|
||||
* the same filename. To fix this we mangle names first such that Foo.exe becomes
|
||||
* _foo.exe.
|
||||
* <p>
|
||||
* This method is to get around case-insensitive filesystems since Ghidra is case-sensitive.
|
||||
* To fix this we mangle names first such that "Foo.exe" becomes "_foo.exe".
|
||||
*
|
||||
* @param name name string to be mangled
|
||||
* @return mangled name
|
||||
@@ -199,6 +205,7 @@ public final class NamingUtilities {
|
||||
|
||||
/**
|
||||
* Performs a validity check on a mangled name
|
||||
*
|
||||
* @param name mangled name
|
||||
* @return true if name can be demangled else false
|
||||
*/
|
||||
|
||||
+59
-6
@@ -47,14 +47,18 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
|
||||
this.useIndexedFileSystem = useIndexedFileSystem;
|
||||
}
|
||||
|
||||
protected File createEmptyProjectDir() {
|
||||
File tempDir = new File(AbstractGTest.getTestDirectoryPath());
|
||||
File dir = new File(tempDir, "testproject");
|
||||
FileUtilities.deleteDir(dir);
|
||||
dir.mkdir();
|
||||
return dir;
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
File tempDir = new File(AbstractGTest.getTestDirectoryPath());
|
||||
projectDir = new File(tempDir, "testproject");
|
||||
FileUtilities.deleteDir(projectDir);
|
||||
projectDir.mkdir();
|
||||
|
||||
projectDir = createEmptyProjectDir();
|
||||
System.out.println(projectDir.getAbsolutePath());
|
||||
|
||||
fs = LocalFileSystem.getLocalFileSystem(projectDir.getAbsolutePath(), useIndexedFileSystem,
|
||||
@@ -299,6 +303,20 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateTextData() throws Exception {
|
||||
fs.createFolder("/", "abc");
|
||||
|
||||
String data = "This is a test";
|
||||
LocalTextDataItem textItem =
|
||||
fs.createTextDataItem("/abc", "fred", "MyID", "Text", data, null, null);
|
||||
assertEquals(data, textItem.getTextData());
|
||||
|
||||
flushFileSystemEvents();
|
||||
assertEquals(2, events.size());
|
||||
checkEvent("Item Created", "/abc", "fred", null, null, events.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileNames() throws Exception {
|
||||
|
||||
@@ -615,17 +633,23 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
|
||||
|
||||
DataFileItem df = fs.createDataFile("/abc", "fred", new ByteArrayInputStream(dataBytes),
|
||||
null, "Data", null);
|
||||
LocalTextDataItem textItem =
|
||||
fs.createTextDataItem("/abc", "ted", "MyID", "Text", data, null, null);
|
||||
assertEquals(data, textItem.getTextData());
|
||||
|
||||
createDatabase("/abc", "greg", "123");
|
||||
|
||||
String[] items = fs.getItemNames("/abc");
|
||||
assertEquals(3, items.length);
|
||||
assertEquals(4, items.length);
|
||||
assertEquals("bob", items[0]);
|
||||
assertEquals("fred", items[1]);
|
||||
assertEquals("greg", items[2]);
|
||||
assertEquals("ted", items[3]);
|
||||
|
||||
assertEquals(LocalDataFileItem.class, fs.getItem("/abc", items[0]).getClass());
|
||||
assertEquals(LocalDataFileItem.class, fs.getItem("/abc", items[1]).getClass());
|
||||
assertEquals(LocalDatabaseItem.class, fs.getItem("/abc", items[2]).getClass());
|
||||
assertEquals(LocalTextDataItem.class, fs.getItem("/abc", items[3]).getClass());
|
||||
|
||||
df = (DataFileItem) fs.getItem("/abc", items[0]);
|
||||
InputStream is = df.getInputStream();
|
||||
@@ -642,6 +666,9 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
|
||||
assertNotNull(dbh.getTable("test"));
|
||||
dbh.close();
|
||||
|
||||
textItem = (LocalTextDataItem) fs.getItem("/abc", items[3]);
|
||||
assertEquals(data, textItem.getTextData());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -673,6 +700,32 @@ public abstract class AbstractLocalFileSystemTest extends AbstractGenericTest {
|
||||
checkEvent("Item Moved", "/abc", "fred", "/xyz", "bob", events.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveTextDataFile() throws Exception {
|
||||
fs.createFolder("/", "abc");
|
||||
String data = "This is a test";
|
||||
LocalTextDataItem textItem =
|
||||
fs.createTextDataItem("/abc", "fred", "MyID", "Text", data, null, null);
|
||||
assertEquals(data, textItem.getTextData());
|
||||
flushFileSystemEvents();
|
||||
events.clear();
|
||||
|
||||
FolderItem item = fs.getItem("/abc", "fred");
|
||||
assertNotNull(item);
|
||||
|
||||
fs.moveItem("/abc", "fred", "/xyz", "bob");
|
||||
|
||||
assertNull(fs.getItem("/abc", "fred"));
|
||||
LocalTextDataItem df = (LocalTextDataItem) fs.getItem("/xyz", "bob");
|
||||
assertNotNull(df);
|
||||
assertEquals(data, df.getTextData());
|
||||
|
||||
flushFileSystemEvents();
|
||||
assertEquals(2, events.size());
|
||||
checkEvent("Folder Created", "/", "xyz", null, null, events.get(0));
|
||||
checkEvent("Item Moved", "/abc", "fred", "/xyz", "bob", events.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveDatabase() throws Exception {
|
||||
fs.createFolder("/", "abc");
|
||||
|
||||
@@ -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.
|
||||
@@ -15,8 +15,7 @@
|
||||
*/
|
||||
package ghidra.util;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
|
||||
import resources.ResourceManager;
|
||||
|
||||
@@ -151,9 +150,9 @@ public class HelpLocation {
|
||||
// try creating a URL with the given anchor
|
||||
if (localURL != null && localAnchor != null) {
|
||||
try {
|
||||
localURL = new URL(localURL.toExternalForm() + "#" + localAnchor);
|
||||
localURL = localURL.toURI().resolve("#" + localAnchor).toURL();
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (MalformedURLException | URISyntaxException e) {
|
||||
// we tried
|
||||
}
|
||||
}
|
||||
|
||||
+7
-8
@@ -33,8 +33,7 @@ import ghidra.framework.store.local.LocalFileSystem;
|
||||
import ghidra.framework.store.local.LocalFolderItem;
|
||||
import ghidra.framework.store.remote.RemoteFileSystem;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.DuplicateFileException;
|
||||
import ghidra.util.exception.*;
|
||||
import ghidra.util.task.*;
|
||||
import utilities.util.FileUtilities;
|
||||
|
||||
@@ -105,14 +104,16 @@ public class DefaultProjectData implements ProjectData {
|
||||
* @param isInWritableProject true if project content is writable, false if project is read-only
|
||||
* @param resetOwner true to reset the project owner
|
||||
* @throws IOException if an i/o error occurs
|
||||
* @throws NotFoundException if project does not exist
|
||||
* @throws NotOwnerException if inProject is true and user is not owner
|
||||
* @throws LockException if {@code isInWritableProject} is true and unable to establish project
|
||||
* write lock (i.e., project in-use)
|
||||
* @throws FileNotFoundException if project directory not found
|
||||
*/
|
||||
public DefaultProjectData(ProjectLocator localStorageLocator, boolean isInWritableProject,
|
||||
boolean resetOwner) throws NotOwnerException, IOException, LockException {
|
||||
|
||||
boolean resetOwner)
|
||||
throws NotFoundException, NotOwnerException, IOException, LockException {
|
||||
localStorageLocator.checkProjectExistence();
|
||||
this.localStorageLocator = localStorageLocator;
|
||||
boolean success = false;
|
||||
try {
|
||||
@@ -158,6 +159,7 @@ public class DefaultProjectData implements ProjectData {
|
||||
*/
|
||||
public DefaultProjectData(ProjectLocator localStorageLocator, RepositoryAdapter repository,
|
||||
boolean isInWritableProject) throws IOException, LockException {
|
||||
localStorageLocator.checkLocationExistence();
|
||||
this.localStorageLocator = localStorageLocator;
|
||||
this.repository = repository;
|
||||
boolean success = false;
|
||||
@@ -248,7 +250,7 @@ public class DefaultProjectData implements ProjectData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the contents of the project properties file to include the following values if relavent:
|
||||
* Read the contents of the project properties file to include the following values if relevant:
|
||||
* {@value #OWNER}, {@value #SERVER_NAME}, {@value #REPOSITORY_NAME}, {@value #PORT_NUMBER}
|
||||
* @param projectDir project directory (*.rep)
|
||||
* @return project properties or null if invalid project directory specified
|
||||
@@ -297,9 +299,6 @@ public class DefaultProjectData implements ProjectData {
|
||||
localStorageLocator.getMarkerFile().createNewFile();
|
||||
}
|
||||
else {
|
||||
if (!projectDir.isDirectory()) {
|
||||
throw new FileNotFoundException("Project directory not found: " + projectDir);
|
||||
}
|
||||
if (properties.exists()) {
|
||||
if (isInWritableProject && properties.isReadOnly()) {
|
||||
throw new ReadOnlyException(
|
||||
|
||||
@@ -17,7 +17,6 @@ package ghidra.framework.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
@@ -27,7 +26,8 @@ import ghidra.framework.client.*;
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.RepositoryItem;
|
||||
import ghidra.framework.store.*;
|
||||
import ghidra.framework.store.ItemCheckoutStatus;
|
||||
import ghidra.framework.store.Version;
|
||||
import ghidra.framework.store.db.PackedDatabase;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.ReadOnlyException;
|
||||
@@ -115,22 +115,6 @@ public class DomainFileProxy implements DomainFile {
|
||||
return parentPath + DomainFolder.SEPARATOR + getName();
|
||||
}
|
||||
|
||||
private URL getSharedFileURL(URL sharedProjectURL, String ref) {
|
||||
try {
|
||||
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||
String urlStr = sharedProjectURL.toExternalForm();
|
||||
if (urlStr.endsWith(FileSystem.SEPARATOR)) {
|
||||
urlStr = urlStr.substring(0, urlStr.length() - 1);
|
||||
}
|
||||
urlStr += getPathname();
|
||||
return new URL(urlStr);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private URL getSharedFileURL(Properties properties, String ref) {
|
||||
if (properties == null) {
|
||||
return null;
|
||||
@@ -183,7 +167,7 @@ public class DomainFileProxy implements DomainFile {
|
||||
if (projectLocation != null && version == DomainFile.DEFAULT_VERSION) {
|
||||
URL projectURL = projectLocation.getURL();
|
||||
if (GhidraURL.isServerRepositoryURL(projectURL)) {
|
||||
return getSharedFileURL(projectURL, ref);
|
||||
return GhidraURL.resolve(projectURL, getPathname(), ref);
|
||||
}
|
||||
Properties properties =
|
||||
DefaultProjectData.readProjectProperties(projectLocation.getProjectDir());
|
||||
|
||||
@@ -17,7 +17,6 @@ package ghidra.framework.data;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -398,16 +397,7 @@ public class GhidraFileData {
|
||||
RepositoryAdapter repository = projectData.getRepository();
|
||||
if (versionedFolderItem != null && repository != null) {
|
||||
URL folderURL = parent.getDomainFolder().getSharedProjectURL();
|
||||
try {
|
||||
String spec = name;
|
||||
if (!StringUtils.isEmpty(ref)) {
|
||||
spec += "#" + ref;
|
||||
}
|
||||
return new URL(folderURL, spec);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
}
|
||||
return GhidraURL.resolve(folderURL, getPathname(), ref);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -751,8 +741,15 @@ public class GhidraFileData {
|
||||
}
|
||||
|
||||
// Handle link to Ghidra URL
|
||||
URL ghidraUrl = new URL(resolvedLinkPath);
|
||||
doa = linkHandler.getObject(ghidraUrl, version, consumer, monitor, false);
|
||||
try {
|
||||
URL ghidraUrl = GhidraURL.toURL(resolvedLinkPath);
|
||||
doa = linkHandler.getObject(ghidraUrl, version, consumer, monitor, false);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// Bad URL from link path
|
||||
throw new IOException(
|
||||
"Failed to form valid URL from linkPath: " + resolvedLinkPath, e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
doa = contentHandler.getReadOnlyObject(item, version, true, consumer, monitor);
|
||||
@@ -819,8 +816,15 @@ public class GhidraFileData {
|
||||
}
|
||||
|
||||
// Handle link to Ghidra URL
|
||||
URL ghidraUrl = new URL(resolvedLinkPath);
|
||||
doa = linkHandler.getObject(ghidraUrl, version, consumer, monitor, true);
|
||||
try {
|
||||
URL ghidraUrl = GhidraURL.toURL(resolvedLinkPath);
|
||||
doa = linkHandler.getObject(ghidraUrl, version, consumer, monitor, true);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
// Bad URL from link path
|
||||
throw new IOException(
|
||||
"Failed to form valid URL from linkPath: " + resolvedLinkPath, e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
doa = contentHandler.getImmutableObject(item, consumer, version, -1, monitor);
|
||||
@@ -939,8 +943,6 @@ public class GhidraFileData {
|
||||
}
|
||||
synchronized (fileSystem) {
|
||||
|
||||
boolean isLink = isLink();
|
||||
|
||||
FolderItem item = getFolderItem(DomainFile.DEFAULT_VERSION);
|
||||
|
||||
Icon baseIcon = new TranslateIcon(getBaseIcon(item), 1, 1);
|
||||
@@ -2372,7 +2374,7 @@ public class GhidraFileData {
|
||||
return linkPath;
|
||||
}
|
||||
|
||||
private String getAbsolutePath(String path) throws IOException {
|
||||
private String getAbsolutePath(String path) {
|
||||
String absPath = path;
|
||||
if (!path.startsWith(FileSystem.SEPARATOR)) {
|
||||
absPath = getParent().getPathname();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package ghidra.framework.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
@@ -183,22 +182,11 @@ public class GhidraFolder implements DomainFolder {
|
||||
if (projectURL == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// Direct URL construction done so that ghidra protocol extension may be supported
|
||||
String urlStr = projectURL.toExternalForm();
|
||||
if (urlStr.endsWith(FileSystem.SEPARATOR)) {
|
||||
urlStr = urlStr.substring(0, urlStr.length() - 1);
|
||||
}
|
||||
String path = getPathname();
|
||||
if (!path.endsWith(FileSystem.SEPARATOR)) {
|
||||
path += FileSystem.SEPARATOR;
|
||||
}
|
||||
urlStr += path;
|
||||
return new URL(urlStr);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
return null;
|
||||
String path = getPathname();
|
||||
if (!path.endsWith(FileSystem.SEPARATOR)) {
|
||||
path += FileSystem.SEPARATOR;
|
||||
}
|
||||
return GhidraURL.resolve(projectURL, path, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1505,16 +1505,19 @@ class GhidraFolderData {
|
||||
DomainFile createLinkFile(String ghidraUrl, String linkFilename, LinkHandler<?> lh)
|
||||
throws IOException {
|
||||
|
||||
URL url = new URL(ghidraUrl);
|
||||
if (!GhidraURL.isLocalGhidraURL(ghidraUrl) && !GhidraURL.isServerRepositoryURL(url)) {
|
||||
URL url = GhidraURL.toURL(ghidraUrl);
|
||||
if (!GhidraURL.isLocalURL(ghidraUrl) && !GhidraURL.isServerRepositoryURL(url)) {
|
||||
throw new IllegalArgumentException("Invalid Ghidra URL specified");
|
||||
}
|
||||
if (url.getRef() != null) {
|
||||
throw new IllegalArgumentException("URL must not include #reference");
|
||||
}
|
||||
|
||||
// Force use of unique link-file name
|
||||
String newName = getUniqueName(linkFilename);
|
||||
|
||||
try {
|
||||
lh.createLink(ghidraUrl, fileSystem, getPathname(), newName);
|
||||
lh.createLink(url.toExternalForm(), fileSystem, getPathname(), newName);
|
||||
}
|
||||
catch (InvalidNameException e) {
|
||||
throw new IOException(e); // unexpected
|
||||
@@ -1567,6 +1570,9 @@ class GhidraFolderData {
|
||||
Path relativePath = linkParentPath.relativize(referencedPath);
|
||||
String path = relativePath.toString();
|
||||
|
||||
// When running on Windows path separator may get switched on us. Be sure to switch back.
|
||||
path = path.replace('\\', '/');
|
||||
|
||||
// Re-apply preserved finalRefElement to relative path
|
||||
if (finalRefElement != null) {
|
||||
if (!path.isBlank()) {
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package ghidra.framework.data;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -240,7 +239,7 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> implements Co
|
||||
static boolean canShareLink(FolderItem linkFile) {
|
||||
try {
|
||||
String linkPath = getLinkPath(linkFile);
|
||||
return !GhidraURL.isLocalGhidraURL(linkPath);
|
||||
return !GhidraURL.isLocalURL(linkPath);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
@@ -304,11 +303,11 @@ public abstract class LinkHandler<T extends DomainObjectAdapterDB> implements Co
|
||||
ProjectData projectData = linkFile.getParent().getProjectData();
|
||||
return GhidraURL.makeURL(projectData.getProjectLocator(), linkPath, null);
|
||||
}
|
||||
return new URL(linkPath);
|
||||
return GhidraURL.toURL(linkPath);
|
||||
}
|
||||
catch (MalformedURLException | IllegalArgumentException e) {
|
||||
catch (IllegalArgumentException e) {
|
||||
// Bad URL from link path
|
||||
throw new IOException("Failed to form URL from linkPath: " + linkPath, e);
|
||||
throw new IOException("Failed to form valid URL from linkPath: " + linkPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ package ghidra.framework.data;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -25,8 +24,6 @@ import java.util.Map;
|
||||
import javax.help.UnsupportedOperationException;
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.model.*;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.store.*;
|
||||
@@ -125,16 +122,7 @@ class LinkedGhidraFile implements LinkedDomainFile {
|
||||
public URL getSharedProjectURL(String ref) {
|
||||
URL folderURL = parent.getSharedProjectURL();
|
||||
if (GhidraURL.isServerRepositoryURL(folderURL)) {
|
||||
try {
|
||||
String spec = fileName;
|
||||
if (!StringUtils.isEmpty(ref)) {
|
||||
spec += "#" + ref;
|
||||
}
|
||||
return new URL(folderURL, spec);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
}
|
||||
return GhidraURL.resolve(folderURL, getLinkedPathname(), ref);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class LinkedGhidraFolder extends LinkedGhidraSubFolder {
|
||||
super(folderLinkFile.getName());
|
||||
|
||||
if (!GhidraURL.isServerRepositoryURL(linkedFolderUrl) &&
|
||||
!GhidraURL.isLocalProjectURL(linkedFolderUrl)) {
|
||||
!GhidraURL.isLocalURL(linkedFolderUrl)) {
|
||||
throw new IllegalArgumentException("Invalid Ghidra URL: " + linkedFolderUrl);
|
||||
}
|
||||
|
||||
|
||||
+1
-11
@@ -16,7 +16,6 @@
|
||||
package ghidra.framework.data;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.Icon;
|
||||
@@ -167,20 +166,11 @@ class LinkedGhidraSubFolder implements LinkedDomainFolder {
|
||||
public URL getSharedProjectURL() {
|
||||
URL projectURL = getLinkedRootFolder().getProjectURL();
|
||||
if (GhidraURL.isServerRepositoryURL(projectURL)) {
|
||||
String urlStr = projectURL.toExternalForm();
|
||||
if (urlStr.endsWith(FileSystem.SEPARATOR)) {
|
||||
urlStr = urlStr.substring(0, urlStr.length() - 1);
|
||||
}
|
||||
String path = getLinkedPathname();
|
||||
if (!path.endsWith(FileSystem.SEPARATOR)) {
|
||||
path += FileSystem.SEPARATOR;
|
||||
}
|
||||
try {
|
||||
return new URL(urlStr + path);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
// ignore
|
||||
}
|
||||
return GhidraURL.resolve(projectURL, path, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -517,25 +517,29 @@ public class FrontEndPlugin extends Plugin
|
||||
// the extension, try to open or create using the extension
|
||||
else if (!create && filename.lastIndexOf(".") > path.lastIndexOf(File.separator)) {
|
||||
// treat opening a file without the ghidra extension as an error
|
||||
Msg.showError(getClass(), tool.getToolFrame(), "Invalid Project File",
|
||||
Msg.showError(this, tool.getToolFrame(), "Invalid Project File",
|
||||
"Cannot open '" + file.getName() + "' as a Ghidra Project");
|
||||
continue;
|
||||
}
|
||||
if (!NamingUtilities.isValidProjectName(filename)) {
|
||||
Msg.showError(getClass(), tool.getToolFrame(), "Invalid Project Name",
|
||||
filename + " is not a valid project name");
|
||||
continue;
|
||||
}
|
||||
Preferences.setProperty(preferenceName, path);
|
||||
|
||||
try {
|
||||
ProjectLocator projectLocator = new ProjectLocator(path, filename);
|
||||
|
||||
Preferences.setProperty(preferenceName, path);
|
||||
Preferences.store();
|
||||
|
||||
return projectLocator;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.showError(this, tool.getToolFrame(), "Invalid Project Name",
|
||||
e.getMessage());
|
||||
continue;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.debug(this,
|
||||
"Unexpected exception storing preferences to" + Preferences.getFilename(),
|
||||
e);
|
||||
}
|
||||
return new ProjectLocator(path, filename);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
|
||||
|
||||
@Override
|
||||
public boolean accept(URL url) {
|
||||
if (!GhidraURL.isLocalProjectURL(url) && !GhidraURL.isServerRepositoryURL(url)) {
|
||||
if (!GhidraURL.isLocalURL(url) && !GhidraURL.isServerRepositoryURL(url)) {
|
||||
return false;
|
||||
}
|
||||
Swing.runLater(() -> execute(new AcceptUrlContentTask(url, true, plugin)));
|
||||
|
||||
+5
-5
@@ -16,7 +16,6 @@
|
||||
package ghidra.framework.main;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
@@ -35,7 +34,8 @@ import ghidra.framework.model.*;
|
||||
import ghidra.framework.preferences.Preferences;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.framework.remote.User;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
class ProjectActionManager {
|
||||
private final static String CLOSE_ALL_OPEN_VIEWS = "Close All Read-Only Views";
|
||||
@@ -536,11 +536,11 @@ class ProjectActionManager {
|
||||
|
||||
String urlStr = Preferences.getProperty(LAST_VIEWED_REPOSITORY_URL);
|
||||
URL lastURL = null;
|
||||
if (urlStr != null) {
|
||||
if (GhidraURL.isGhidraURL(urlStr)) {
|
||||
try {
|
||||
lastURL = new URL(urlStr);
|
||||
lastURL = GhidraURL.toURL(urlStr);
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (IllegalArgumentException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -16,8 +16,6 @@
|
||||
package ghidra.framework.main;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.MenuData;
|
||||
@@ -73,10 +71,10 @@ public class ProjectDataFollowLinkAction extends FrontendProjectTreeAction {
|
||||
if (GhidraURL.isGhidraURL(linkPath)) {
|
||||
// Follow URL using a project view
|
||||
try {
|
||||
plugin.showInViewedProject(new URL(linkPath), isFolderLink);
|
||||
plugin.showInViewedProject(GhidraURL.toURL(linkPath), isFolderLink);
|
||||
return;
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.error(this, "Invalid link URL: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.awt.CardLayout;
|
||||
import java.awt.event.ItemListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -255,15 +254,16 @@ class RepositoryChooser extends ReusableDialogComponentProvider {
|
||||
setOkEnabled(false);
|
||||
|
||||
try {
|
||||
URL url = new URL(urlTextField.getText());
|
||||
if (!GhidraURL.PROTOCOL.equals(url.getProtocol())) {
|
||||
String urlText = urlTextField.getText();
|
||||
if (!GhidraURL.isGhidraURL(urlText)) {
|
||||
setStatusText("URL must specify 'ghidra:' protocol", MessageType.ERROR);
|
||||
}
|
||||
else {
|
||||
GhidraURL.toURL(urlText); // check ability to form URL instance
|
||||
setOkEnabled(true);
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (IllegalArgumentException e) {
|
||||
setStatusText(e.getMessage(), MessageType.ERROR);
|
||||
}
|
||||
|
||||
@@ -294,9 +294,9 @@ class RepositoryChooser extends ReusableDialogComponentProvider {
|
||||
// TODO: How do we restrict URL to repository only - not sure we can
|
||||
|
||||
try {
|
||||
return new URL(urlTextField.getText());
|
||||
return GhidraURL.toURL(urlTextField.getText());
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.error(this, e.getMessage());
|
||||
}
|
||||
return null;
|
||||
|
||||
+3
-4
@@ -16,7 +16,6 @@
|
||||
package ghidra.framework.main.datatree;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
@@ -324,8 +323,8 @@ public class DomainFileNode extends DataTreeNode {
|
||||
|
||||
if (GhidraURL.isGhidraURL(linkPath)) {
|
||||
try {
|
||||
URL url = new URL(linkPath);
|
||||
if (GhidraURL.isLocalGhidraURL(linkPath)) {
|
||||
URL url = GhidraURL.toURL(linkPath);
|
||||
if (GhidraURL.isLocalURL(linkPath)) {
|
||||
ProjectLocator loc = GhidraURL.getProjectStorageLocator(url);
|
||||
if (loc != null) {
|
||||
String projectPath = GhidraURL.getProjectPathname(url);
|
||||
@@ -341,7 +340,7 @@ public class DomainFileNode extends DataTreeNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (MalformedURLException e) {
|
||||
catch (IllegalArgumentException e) {
|
||||
// ignore - use original linkPath
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -20,6 +20,8 @@ import java.util.List;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.wizard.WizardModel;
|
||||
import docking.wizard.WizardStep;
|
||||
import ghidra.app.util.GenericHelpTopics;
|
||||
@@ -77,10 +79,20 @@ public class RepositoryStep extends WizardStep<ProjectWizardData> {
|
||||
if (repositoryName.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
if (!NamingUtilities.isValidProjectName(repositoryName)) {
|
||||
setStatusMessage("Invalid project repository name");
|
||||
if (StringUtils.isBlank(repositoryName)) {
|
||||
setStatusMessage("Enter project repository name");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
NamingUtilities.checkName(repositoryName,
|
||||
"Repository name");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
setStatusMessage(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (List.of(repositoryNames).contains(repositoryName)) {
|
||||
setStatusMessage("Repository " + repositoryName + " already exists");
|
||||
return false;
|
||||
|
||||
+3
-2
@@ -21,11 +21,12 @@ import java.io.File;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.wizard.WizardModel;
|
||||
import docking.wizard.WizardStep;
|
||||
import ghidra.framework.model.ProjectLocator;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.NamingUtilities;
|
||||
|
||||
/**
|
||||
* Wizard step in the new project wizard for choosing the new project's root folder location and
|
||||
@@ -76,7 +77,7 @@ public class SelectProjectStep extends WizardStep<ProjectWizardData> {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!NamingUtilities.isValidProjectName(projectName)) {
|
||||
if (StringUtils.isBlank(projectName)) {
|
||||
setStatusMessage("Please specify valid project name");
|
||||
return false;
|
||||
}
|
||||
|
||||
+115
-91
@@ -16,13 +16,16 @@
|
||||
package ghidra.framework.model;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.framework.Application;
|
||||
import ghidra.framework.OperatingSystem;
|
||||
import ghidra.framework.protocol.ghidra.GhidraURL;
|
||||
import ghidra.util.NamingUtilities;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
|
||||
/**
|
||||
* Lightweight descriptor of a local Project storage location.
|
||||
@@ -36,20 +39,15 @@ public class ProjectLocator {
|
||||
|
||||
private final String name;
|
||||
private final String location;
|
||||
private final boolean isWindowsOnlyLocation; // drive letter or UNC path specified
|
||||
|
||||
private URL url;
|
||||
|
||||
/**
|
||||
* Set of characters specifically disallowed in project name or path.
|
||||
* These characters may interfere with path and URL parsing.
|
||||
*/
|
||||
public static Set<Character> DISALLOWED_CHARS = Set.of(':', ';', '&', '?', '#');
|
||||
|
||||
/**
|
||||
* Construct a project locator object.
|
||||
* @param path absolute path to parent directory (may or may not exist). The user's temp directory
|
||||
* will be used if this value is null or blank. The use of "\" characters will always be replaced
|
||||
* with "/".
|
||||
* with "/". A path starting with either "//" or "\\" will be treated as a Windows UNC path.
|
||||
* WARNING: Use of a relative paths should be avoided (e.g., on a windows platform
|
||||
* an absolute path should start with a drive letter specification such as C:\path).
|
||||
* A path such as "/path" on windows will utilize the current default drive and will
|
||||
@@ -58,15 +56,15 @@ public class ProjectLocator {
|
||||
* @param name name of the project (may only contain alphanumeric characters or
|
||||
* @throws IllegalArgumentException if an absolute path is not specified or invalid project name
|
||||
*/
|
||||
public ProjectLocator(String path, String name) {
|
||||
public ProjectLocator(String path, String name) throws IllegalArgumentException {
|
||||
this(path, name, null);
|
||||
}
|
||||
|
||||
protected ProjectLocator(String path, String name, URL url) {
|
||||
protected ProjectLocator(String path, String name, URL url) throws IllegalArgumentException {
|
||||
if (name.contains("/") || name.contains("\\")) {
|
||||
throw new IllegalArgumentException("name contains path separator character: " + name);
|
||||
}
|
||||
checkInvalidChar("name", name, 0);
|
||||
NamingUtilities.checkProjectName(name);
|
||||
if (name.endsWith(PROJECT_FILE_SUFFIX)) {
|
||||
name = name.substring(0, name.length() - PROJECT_FILE_SUFFIX.length());
|
||||
}
|
||||
@@ -74,78 +72,11 @@ public class ProjectLocator {
|
||||
if (StringUtils.isBlank(path)) {
|
||||
path = Application.getUserTempDirectory().getAbsolutePath();
|
||||
}
|
||||
this.location = checkAbsolutePath(path);
|
||||
location = GhidraURL.checkLocalAbsolutePath(path, true);
|
||||
isWindowsOnlyLocation = GhidraURL.isWindowsOnlyPath(location);
|
||||
this.url = url != null ? url : GhidraURL.makeURL(location, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for characters explicitly disallowed in path or project name.
|
||||
* @param type type of string to include in exception
|
||||
* @param str string to check
|
||||
* @param startIndex index at which to start checking
|
||||
* @throws IllegalArgumentException if str contains invalid character
|
||||
*/
|
||||
private static void checkInvalidChar(String type, String str, int startIndex) {
|
||||
for (int i = startIndex; i < str.length(); i++) {
|
||||
char c = str.charAt(i);
|
||||
if (DISALLOWED_CHARS.contains(c)) {
|
||||
throw new IllegalArgumentException(
|
||||
type + " contains invalid character: '" + c + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that absolute path is specified and normalize its format.
|
||||
* An absolute path may start with a windows drive letter (e.g., c:/a/b, /c:/a/b)
|
||||
* or without (e.g., /a/b). Although for Windows the lack of a drive letter is
|
||||
* not absolute, for consistency with Linux we permit this form which on
|
||||
* Windows will use the default drive for the process. The resulting path
|
||||
* may be transormed to always end with a "/" and if started with a drive letter
|
||||
* (e.g., "c:/") it will have a "/" prepended (e.g., "/c:/", both forms
|
||||
* are treated the same by the {@link File} class under Windows).
|
||||
* @param path path to be checked and possibly modified.
|
||||
* @return path to be used
|
||||
* @throws IllegalArgumentException if an invalid path is specified
|
||||
*/
|
||||
private static String checkAbsolutePath(String path) {
|
||||
int scanIndex = 0;
|
||||
path = path.replace('\\', '/');
|
||||
int len = path.length();
|
||||
if (!path.startsWith("/")) {
|
||||
// Allow paths to start with windows drive letter (e.g., c:/a/b)
|
||||
if (len >= 3 && hasAbsoluteDriveLetter(path, 0)) {
|
||||
path = "/" + path;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("absolute path required");
|
||||
}
|
||||
scanIndex = 3;
|
||||
}
|
||||
else if (len >= 3 && hasDriveLetter(path, 1)) {
|
||||
if (len < 4 || path.charAt(3) != '/') {
|
||||
// path such as "/c:" not permitted
|
||||
throw new IllegalArgumentException("absolute path required");
|
||||
}
|
||||
scanIndex = 4;
|
||||
}
|
||||
checkInvalidChar("path", path, scanIndex);
|
||||
if (!path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private static boolean hasDriveLetter(String path, int index) {
|
||||
return Character.isLetter(path.charAt(index++)) && path.charAt(index) == ':';
|
||||
}
|
||||
|
||||
private static boolean hasAbsoluteDriveLetter(String path, int index) {
|
||||
int pathIndex = index + 2;
|
||||
return path.length() > pathIndex && hasDriveLetter(path, index) &&
|
||||
path.charAt(pathIndex) == '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if this project URL corresponds to a transient project
|
||||
* (e.g., corresponds to remote Ghidra URL)}
|
||||
@@ -155,8 +86,9 @@ public class ProjectLocator {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the URL associated with this local project. If using a temporary transient
|
||||
* project location this URL should not be used.}
|
||||
* {@return the URL associated with this local project.}
|
||||
* If using a temporary transient project location this URL will refer to the
|
||||
* remote server repository.
|
||||
*/
|
||||
public URL getURL() {
|
||||
return url;
|
||||
@@ -170,9 +102,18 @@ public class ProjectLocator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location of the project which will contain marker file
|
||||
* ({@link #getMarkerFile()}) and project directory ({@link #getProjectDir()}).
|
||||
* Get the absolute path of the directory which contains the project marker file
|
||||
* ({@link #getMarkerFile()}) and project directory ({@link #getProjectDir()})
|
||||
* (i.e., parent directory).
|
||||
* <p>
|
||||
* Directory path will always use '/' as a path separator and may have one of the following
|
||||
* forms:
|
||||
* <ul>
|
||||
* <li>Standard path: /a/b/</li>
|
||||
* <li>Windows path: /C:/a/b</li>
|
||||
* <li>Windows UNC path: //server/share/a/b</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: directory may or may not exist.
|
||||
* @return project location directory
|
||||
*/
|
||||
@@ -181,24 +122,45 @@ public class ProjectLocator {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the project directory}
|
||||
* {@return true if this project location is only valid on a Windows platform.}
|
||||
*/
|
||||
public File getProjectDir() {
|
||||
return new File(location, name + PROJECT_DIR_SUFFIX);
|
||||
public boolean isWindowsOnlyLocation() {
|
||||
return isWindowsOnlyLocation;
|
||||
}
|
||||
|
||||
private File getParentDir() {
|
||||
return new File(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the file that indicates a Ghidra project.}
|
||||
* Get the project storage directory associated with this project locator.
|
||||
* <p>
|
||||
* NOTE: {@link #exists()} or {@link #checkProjectExistence()} method should be used prior to the
|
||||
* returned file.
|
||||
*
|
||||
* @return the project directory
|
||||
*/
|
||||
public File getProjectDir() {
|
||||
return new File(getParentDir(), name + PROJECT_DIR_SUFFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the project marker file associated with this project locator.
|
||||
* <p>
|
||||
* NOTE: {@link #exists()} or {@link #checkProjectExistence()} method should be used prior to the
|
||||
* returned file.
|
||||
*
|
||||
* @return the marker file that indicates a Ghidra project.
|
||||
*/
|
||||
public File getMarkerFile() {
|
||||
return new File(location, name + PROJECT_FILE_SUFFIX);
|
||||
return new File(getParentDir(), name + PROJECT_FILE_SUFFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return project lock file to prevent multiple accesses to the same project at once.}
|
||||
*/
|
||||
public File getProjectLockFile() {
|
||||
return new File(location, name + LOCK_FILE_SUFFIX);
|
||||
return new File(getParentDir(), name + LOCK_FILE_SUFFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,10 +211,72 @@ public class ProjectLocator {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return true if project storage exists}
|
||||
* Determine if the project exists.
|
||||
* <p>
|
||||
* IMPORTANT: This method or {@link #checkProjectExistence()} method should always be used prior to
|
||||
* using File object returned by {@link #getParentDir()}, {@link #getMarkerFile()} or
|
||||
* {@link #getProjectLockFile()} since OS-specific checks are performed which may not
|
||||
* be considered when using the returned File objects.
|
||||
*
|
||||
* @return true if project storage exists.
|
||||
*/
|
||||
public boolean exists() {
|
||||
if (isWindowsOnlyLocation &&
|
||||
OperatingSystem.CURRENT_OPERATING_SYSTEM != OperatingSystem.WINDOWS) {
|
||||
// Do not try to evaluate a Windows-only path on other platforms
|
||||
return false;
|
||||
}
|
||||
return getMarkerFile().isFile() && getProjectDir().isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that this project exists with its required marker file and data storage directory.
|
||||
* <p>
|
||||
* IMPORTANT: This method or {@link #exists()} method should always be used prior to
|
||||
* using File object returned by {@link #getParentDir()}, {@link #getMarkerFile()} or
|
||||
* {@link #getProjectLockFile()} since OS-specific checks are performed which may not
|
||||
* be considered when using the returned File objects.
|
||||
*
|
||||
* @throws NotFoundException if project does not exist
|
||||
*/
|
||||
public void checkProjectExistence() throws NotFoundException {
|
||||
if (isWindowsOnlyLocation &&
|
||||
OperatingSystem.CURRENT_OPERATING_SYSTEM != OperatingSystem.WINDOWS) {
|
||||
throw new NotFoundException(
|
||||
"The project location is only valid on Windows: " + location);
|
||||
}
|
||||
|
||||
File markerFile = getMarkerFile();
|
||||
if (!markerFile.isFile()) {
|
||||
throw new NotFoundException("Project marker file not found: " + markerFile);
|
||||
}
|
||||
|
||||
File projectDir = getProjectDir();
|
||||
if (!projectDir.isDirectory()) {
|
||||
throw new NotFoundException("Project directory not found: " + projectDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the specified project location directory exists in preparation for creating
|
||||
* a new project.
|
||||
* <p>
|
||||
* IMPORTANT: This method or {@link #exists()} method should always be used prior to
|
||||
* using File object returned by {@link #getParentDir()}, {@link #getMarkerFile()} or
|
||||
* {@link #getProjectLockFile()} since OS-specific checks are performed which may not
|
||||
* be considered when using the returned File objects.
|
||||
*
|
||||
* @throws IOException if project location does not exist
|
||||
*/
|
||||
public void checkLocationExistence() throws IOException {
|
||||
if (isWindowsOnlyLocation &&
|
||||
OperatingSystem.CURRENT_OPERATING_SYSTEM != OperatingSystem.WINDOWS) {
|
||||
throw new IOException("The project location is only valid on Windows: " + location);
|
||||
}
|
||||
|
||||
File dir = getParentDir();
|
||||
if (!dir.isDirectory()) {
|
||||
throw new IOException("Project location not found: " + dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -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.
|
||||
@@ -207,13 +207,20 @@ public class SaveToolConfigDialog extends DialogComponentProvider implements Lis
|
||||
return;
|
||||
}
|
||||
|
||||
// Impose same naming restrictions as Project uses - other than the blank space
|
||||
if (newName.indexOf(" ") >= 0) {
|
||||
setStatusText("Name cannot have spaces.");
|
||||
setStatusText("Name cannot have spaces");
|
||||
nameField.requestFocus();
|
||||
return;
|
||||
}
|
||||
if (!NamingUtilities.isValidName(newName)) {
|
||||
setStatusText("Invalid character in name: " + NamingUtilities.findInvalidChar(newName));
|
||||
if (newName.startsWith(".")) {
|
||||
setStatusText("Name cannot start with a '.'");
|
||||
nameField.requestFocus();
|
||||
return;
|
||||
}
|
||||
String invalidChar = NamingUtilities.findInvalidChar(newName);
|
||||
if (invalidChar != null) {
|
||||
setStatusText("Invalid character in name: '" + invalidChar + "'");
|
||||
nameField.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import ghidra.util.*;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
import ghidra.util.xml.GenericXMLOutputter;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
|
||||
@@ -103,11 +104,13 @@ public class DefaultProject implements Project {
|
||||
* @param resetOwner if true, set the owner to the current user
|
||||
* @throws FileNotFoundException project directory not found
|
||||
* @throws IOException if I/O error occurs.
|
||||
* @throws NotFoundException if project does not exist
|
||||
* @throws NotOwnerException if userName is not the owner of the project.
|
||||
* @throws LockException if unable to establish project lock
|
||||
*/
|
||||
protected DefaultProject(DefaultProjectManager projectManager, ProjectLocator projectLocator,
|
||||
boolean resetOwner) throws IOException, NotOwnerException, LockException {
|
||||
boolean resetOwner)
|
||||
throws IOException, NotFoundException, NotOwnerException, LockException {
|
||||
|
||||
this.projectManager = projectManager;
|
||||
this.projectLocator = projectLocator;
|
||||
@@ -485,7 +488,7 @@ public class DefaultProject implements Project {
|
||||
while (it.hasNext()) {
|
||||
Element elem = (Element) it.next();
|
||||
String urlStr = elem.getAttributeValue("URL");
|
||||
URL url = new URL(urlStr);
|
||||
URL url = GhidraURL.toURL(urlStr);
|
||||
try {
|
||||
addProjectView(url, true);
|
||||
}
|
||||
|
||||
+2
-14
@@ -127,18 +127,6 @@ public class DefaultProjectManager implements ProjectManager {
|
||||
throw new LockException(msg);
|
||||
}
|
||||
|
||||
if (!projectLocator.getMarkerFile().exists()) {
|
||||
forgetProject(projectLocator);
|
||||
throw new NotFoundException(
|
||||
"Project marker file not found: " + projectLocator.getMarkerFile());
|
||||
}
|
||||
|
||||
if (!projectLocator.getProjectDir().isDirectory()) {
|
||||
forgetProject(projectLocator);
|
||||
throw new NotFoundException(
|
||||
"Project directory not found: " + projectLocator.getProjectDir());
|
||||
}
|
||||
|
||||
try {
|
||||
currentProject = new DefaultProject(this, projectLocator, resetOwner);
|
||||
AppInfo.setActiveProject(currentProject);
|
||||
@@ -546,7 +534,7 @@ public class DefaultProjectManager implements ProjectManager {
|
||||
}
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
Msg.error(this, "Invalid project path: " + path);
|
||||
Msg.error(this, "Invalid project path: " + path, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -562,7 +550,7 @@ public class DefaultProjectManager implements ProjectManager {
|
||||
String urlStr = (String) st.nextElement();
|
||||
try {
|
||||
URL url = GhidraURL.toURL(urlStr);
|
||||
if (GhidraURL.isLocalProjectURL(url) && !GhidraURL.localProjectExists(url)) {
|
||||
if (GhidraURL.isLocalURL(url) && !GhidraURL.localProjectExists(url)) {
|
||||
continue;
|
||||
}
|
||||
list.add(url);
|
||||
|
||||
+1
-1
@@ -255,7 +255,7 @@ class ToolServicesImpl implements ToolServices {
|
||||
@Override
|
||||
public PluginTool launchToolWithURL(String toolName, URL ghidraUrl)
|
||||
throws IllegalArgumentException {
|
||||
if (!GhidraURL.isLocalProjectURL(ghidraUrl) &&
|
||||
if (!GhidraURL.isLocalURL(ghidraUrl) &&
|
||||
!GhidraURL.isServerRepositoryURL(ghidraUrl)) {
|
||||
throw new IllegalArgumentException("unsupported URL");
|
||||
}
|
||||
|
||||
+4
@@ -27,6 +27,7 @@ import ghidra.framework.protocol.ghidra.GhidraURLConnection.StatusCode;
|
||||
import ghidra.framework.store.LockException;
|
||||
import ghidra.util.NotOwnerException;
|
||||
import ghidra.util.ReadOnlyException;
|
||||
import ghidra.util.exception.NotFoundException;
|
||||
|
||||
/**
|
||||
* <code>DefaultLocalGhidraProtocolConnector</code> provides support for the
|
||||
@@ -134,6 +135,9 @@ public class DefaultLocalGhidraProtocolConnector extends GhidraProtocolConnector
|
||||
try {
|
||||
return new DefaultProjectData(localStorageLocator, !readOnlyAccess, false);
|
||||
}
|
||||
catch (NotFoundException e) {
|
||||
statusCode = StatusCode.NOT_FOUND;
|
||||
}
|
||||
catch (NotOwnerException | ReadOnlyException e) {
|
||||
statusCode = StatusCode.UNAUTHORIZED;
|
||||
}
|
||||
|
||||
+13
-7
@@ -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.
|
||||
@@ -16,8 +16,7 @@
|
||||
package ghidra.framework.protocol.ghidra;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@@ -34,6 +33,7 @@ import ghidra.framework.store.FileSystem;
|
||||
public abstract class GhidraProtocolConnector {
|
||||
|
||||
protected final URL url;
|
||||
protected final URI uri;
|
||||
|
||||
protected String repositoryName; // only valid for server repository
|
||||
protected final String itemPath; // trailing "/" signifies explicit folder path
|
||||
@@ -53,6 +53,12 @@ public abstract class GhidraProtocolConnector {
|
||||
*/
|
||||
protected GhidraProtocolConnector(URL url) throws MalformedURLException {
|
||||
this.url = url;
|
||||
try {
|
||||
this.uri = url.toURI();
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new MalformedURLException(e.getMessage());
|
||||
}
|
||||
checkProtocol();
|
||||
checkUserInfo();
|
||||
checkHostInfo();
|
||||
@@ -63,7 +69,7 @@ public abstract class GhidraProtocolConnector {
|
||||
/**
|
||||
* Get the URL associated with the repository/project root folder.
|
||||
* This will be used as a key to its corresponding transient project data.
|
||||
* @return root folder URL
|
||||
* @return root folder URL or null if connection is a server-only URL
|
||||
*/
|
||||
protected abstract URL getRepositoryRootGhidraURL();
|
||||
|
||||
@@ -107,7 +113,7 @@ public abstract class GhidraProtocolConnector {
|
||||
*/
|
||||
private String parseRepositoryName() throws MalformedURLException {
|
||||
|
||||
String path = url.getPath();
|
||||
String path = uri.getPath();
|
||||
|
||||
// Divide path into pieces
|
||||
if (StringUtils.isBlank(path) || path.length() < 2 || path.charAt(0) != '/') {
|
||||
@@ -182,7 +188,7 @@ public abstract class GhidraProtocolConnector {
|
||||
*/
|
||||
protected String parseItemPath() throws MalformedURLException {
|
||||
|
||||
String path = url.getPath();
|
||||
String path = uri.getPath();
|
||||
|
||||
if (repositoryName == null) {
|
||||
if (!StringUtils.isEmpty(path) && !"/".equals(path)) {
|
||||
|
||||
+661
-408
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user