From 7b5d11bce39b76612773aa4d606ffbe55bb86a02 Mon Sep 17 00:00:00 2001 From: Ben Soares Date: Tue, 12 Sep 2023 00:19:12 +0100 Subject: [PATCH] JAL-4277 --mkdirs with cautious refusal for paths with .. --- src/jalview/bin/argparser/Arg.java | 23 ++++++---- src/jalview/gui/AlignFrame.java | 8 ++-- src/jalview/util/FileUtils.java | 45 +++++++++++++++++++ test/jalview/bin/CommandsTest.java | 83 ++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 11 deletions(-) diff --git a/src/jalview/bin/argparser/Arg.java b/src/jalview/bin/argparser/Arg.java index 2bd8e26..9dd5c93 100644 --- a/src/jalview/bin/argparser/Arg.java +++ b/src/jalview/bin/argparser/Arg.java @@ -68,14 +68,15 @@ public enum Arg true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION, Opt.SECRET), P(Type.CONFIG, "Set a Jalview preference value for this session.", Opt.PREFIXKEV, Opt.PRESERVECASE, Opt.STRING, Opt.BOOTSTRAP, - Opt.MULTIVALUE, Opt.NOACTION, Opt.SECRET), // keep this secret for now. + Opt.MULTIVALUE, Opt.NOACTION, Opt.SECRET), // keep this secret for + // now. // Opening an alignment OPEN(Type.OPENING, "Opens one or more alignment files or URLs in new alignment windows.", - Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER, Opt.MULTIVALUE, - Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.STORED, - Opt.PRIMARY), + Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER, + Opt.MULTIVALUE, Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, + Opt.STORED, Opt.PRIMARY), APPEND(Type.OPENING, "Appends one or more alignment files or URLs to the open alignment window (or opens a new alignment if none already open).", Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.GLOB, @@ -146,8 +147,9 @@ public enum Arg Opt.STRING, Opt.LINKED, Opt.MULTIVALUE, Opt.ALLOWMULTIID), NOTEMPFAC(Type.STRUCTURE, "Do not show the temperature factor annotation for the preceding --structure.", - Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID, Opt.SECRET), // keep this secret - // until it works! + Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID, Opt.SECRET), // keep this + // secret + // until it works! SHOWSSANNOTATIONS(Type.STRUCTURE, null, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWMULTIID), @@ -224,11 +226,15 @@ public enum Arg CLOSE(Type.OPENING, "Close the current open alignment window. This occurs after other output arguments. This applies to the current open alignment. To apply to all ‑‑output and ‑‑image files, use after ‑‑all.", Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID), + MKDIRS(Type.OUTPUT, + "Automatically create directories when outputting a file to a new directory.", + Opt.UNARY, Opt.LINKED, Opt.ALLOWMULTIID), // controlling flow of arguments NEW(Type.FLOW, "Move on to a new alignment window. This will ensure --append will start a new alignment window and other linked arguments will apply to the new alignment window.", - Opt.UNARY, Opt.MULTIVALUE, Opt.NOACTION, Opt.INCREMENTDEFAULTCOUNTER), + Opt.UNARY, Opt.MULTIVALUE, Opt.NOACTION, + Opt.INCREMENTDEFAULTCOUNTER), SUBSTITUTIONS(Type.FLOW, "The following argument values allow (or don't allow) subsituting filename parts. This is initially true. Valid substitutions are:\n" + "{basename} - the filename-without-extension of the currently --opened file (or first --appended file),\n" @@ -269,7 +275,8 @@ public enum Arg "Allow specific stdout information. For testing purposes only.", Opt.UNARY, Opt.BOOTSTRAP, Opt.SECRET), // do not show this to the user SETPROP(Type.CONFIG, "Set an individual Java System property.", - Opt.STRING, Opt.MULTIVALUE, Opt.BOOTSTRAP, Opt.SECRET), // not in use yet + Opt.STRING, Opt.MULTIVALUE, Opt.BOOTSTRAP, Opt.SECRET), // not in use + // yet NIL(Type.FLOW, "This argument does nothing on its own, but can be used with linkedIds.", Opt.UNARY, Opt.LINKED, Opt.MULTIVALUE, Opt.NOACTION, Opt.SECRET), diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index dba400e..f3cb012 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -1252,10 +1252,11 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, */ public void saveAlignment(String file, FileFormatI format) { - saveAlignment(file, format, false); + saveAlignment(file, format, false, false); } - public void saveAlignment(String file, FileFormatI format, boolean stdout) + public void saveAlignment(String file, FileFormatI format, boolean stdout, + boolean forceBackup) { lastSaveSuccessful = true; if (!stdout) @@ -1308,7 +1309,8 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, else { // create backupfiles object and get new temp filename destination - boolean doBackup = BackupFiles.getEnabled() && !stdout; + boolean doBackup = forceBackup + || (BackupFiles.getEnabled() && !stdout); BackupFiles backupfiles = null; if (doBackup) { diff --git a/src/jalview/util/FileUtils.java b/src/jalview/util/FileUtils.java index 79cac0a..983ba75 100644 --- a/src/jalview/util/FileUtils.java +++ b/src/jalview/util/FileUtils.java @@ -17,6 +17,8 @@ import java.util.EnumSet; import java.util.List; import java.util.stream.Collectors; +import jalview.bin.Console; + public class FileUtils { /* @@ -226,4 +228,47 @@ public class FileUtils } return path.toString(); } + + public static File getParentDir(File file) + { + if (file == null) + { + return null; + } + File parentDir = file.getAbsoluteFile().getParentFile(); + return parentDir; + } + + public static boolean checkParentDir(File file, boolean mkdirs) + { + if (file == null) + { + return false; + } + File parentDir = getParentDir(file); + if (parentDir.exists()) + { + // already exists, nothing to do so nothing to worry about! + return true; + } + + if (!mkdirs) + { + return false; + } + + Path path = file.toPath(); + for (int i = 0; i < path.getNameCount(); i++) + { + Path p = path.getName(i); + if ("..".equals(p.toString())) + { + Console.warn("Cautiously not running mkdirs on " + file.toString() + + " because the path to be made contains '..'"); + return false; + } + } + + return parentDir.mkdirs(); + } } diff --git a/test/jalview/bin/CommandsTest.java b/test/jalview/bin/CommandsTest.java index 36522c4..66250d8 100644 --- a/test/jalview/bin/CommandsTest.java +++ b/test/jalview/bin/CommandsTest.java @@ -331,6 +331,8 @@ public class CommandsTest } } + private final String deleteDir = "test/deleteAfter"; + @Test( groups = "Functional", dataProvider = "allLinkedIdsData", @@ -357,6 +359,12 @@ public class CommandsTest "File " + nonfilename + " exists when it shouldn't!"); } } + + File deleteDirF = new File(deleteDir); + if (deleteDirF.exists()) + { + deleteDirF.delete(); + } } @DataProvider(name = "allLinkedIdsData") @@ -506,6 +514,81 @@ public class CommandsTest "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.aln", "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.aln", "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.aln", }, }, + // --mkdirs + { "--headless --open=test/jalview/bin/argparser/testfiles/dir1/*.fa --output " + + deleteDir + + "/{dirname}/{basename}.stk --open=test/jalview/bin/argparser/testfiles/dir2/*.fa --output=" + + deleteDir + + "/{dirname}/{basename}.aln --close --all --mkdirs", + new String[] + { deleteDir + + "/test/jalview/bin/argparser/testfiles/dir1/test1.stk", + deleteDir + + "/test/jalview/bin/argparser/testfiles/dir1/test2.stk", + deleteDir + + "/test/jalview/bin/argparser/testfiles/dir2/test1.aln", + deleteDir + + "/test/jalview/bin/argparser/testfiles/dir2/test2.aln", + deleteDir + + "/test/jalview/bin/argparser/testfiles/dir2/test3.aln", }, + new String[] + { "test/jalview/bin/argparser/testfiles/test1.stk", + "test/jalview/bin/argparser/testfiles/test2.stk", + "test/jalview/bin/argparser/testfiles/test3.stk", + "test/jalview/bin/argparser/testfiles/dir2/test1.stk", + "test/jalview/bin/argparser/testfiles/dir2/test2.stk", + "test/jalview/bin/argparser/testfiles/dir2/test3.stk", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", + "test/jalview/bin/argparser/testfiles/test1.aln", + "test/jalview/bin/argparser/testfiles/test2.aln", + "test/jalview/bin/argparser/testfiles/test3.aln", + "test/jalview/bin/argparser/testfiles/dir1/test1.aln", + "test/jalview/bin/argparser/testfiles/dir1/test2.aln", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.aln", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.aln", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.aln", + "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/test1.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/test2.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/test3.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir2/test1.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir2/test2.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir2/test3.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.stk", + deleteDir + + "test/jalview/bin/argparser/testfiles/test1.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/test2.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/test3.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir1/test1.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir1/test2.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test0.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test1.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test2.aln", + deleteDir + + "test/jalview/bin/argparser/testfiles/dir3/subdir/test3.aln", }, }, // }; } -- 1.7.10.2