diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java index 64ce1f6ed..bca63b536 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/InstallersPage.java @@ -30,8 +30,11 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.WizardController; import org.jackhuang.hmcl.ui.wizard.WizardPage; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; +import org.jackhuang.hmcl.util.platform.OperatingSystem; +import java.nio.file.Paths; import java.util.Map; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -83,7 +86,9 @@ public class InstallersPage extends StackPane implements WizardPage { String gameVersion = ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(); Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s)); hasVersion.setMessage(i18n("install.new_game.already_exists")); - txtName.getValidators().add(hasVersion); + Validator nameValidator = new Validator(OperatingSystem::isNameValid); + nameValidator.setMessage(i18n("install.new_game.malformed")); + txtName.getValidators().addAll(hasVersion, nameValidator); txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate())); txtName.setText(gameVersion); diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 7fea8ad54..bc4f07bef 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -152,6 +152,7 @@ install.modpack=Install a modpack install.new_game=Install a New Game install.new_game.already_exists=This version has already been existing. install.new_game.current_game_version=Current Game Version +install.new_game.malformed=Name malformed install.select=Select an operation install.success=Installed successfully diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 359a83d7d..d777528c3 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -150,6 +150,7 @@ install.modpack=安裝整合包 install.new_game=安裝新遊戲版本 install.new_game.already_exists=此版本已經存在,請換一個名字 install.new_game.current_game_version=目前遊戲版本 +install.new_game.malformed=名字不合法 install.select=請選擇安裝方式 install.success=安裝成功 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index ca9f87cee..e11a6e665 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -151,6 +151,7 @@ install.modpack=安装整合包 install.new_game=安装新游戏版本 install.new_game.already_exists=此版本已经存在,请换一个名字 install.new_game.current_game_version=当前游戏版本 +install.new_game.malformed=名字不合法 install.select=请选择安装方式 install.success=安装成功 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java index 7398f0345..ff0890a8d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/FileDownloadTask.java @@ -316,6 +316,9 @@ public class FileDownloadTask extends Task { if (temp != null) temp.toFile().delete(); exception = e; + + if (e instanceof ResponseCodeException && ((ResponseCodeException) e).getResponseCode() == 404) + break; } finally { closeFiles(); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/CompressingUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/CompressingUtils.java index 6b5a198bd..8bcfc85c0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/CompressingUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/io/CompressingUtils.java @@ -179,6 +179,8 @@ public final class CompressingUtils { throw new ZipException(error.getMessage()); } catch (UnsupportedOperationException ex) { throw new IOException("Not a zip file", ex); + } catch (FileSystemNotFoundException ex) { + throw new IOException("Java Environment is broken"); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java index 792d0c952..8d9bd447d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/OperatingSystem.java @@ -28,8 +28,10 @@ import java.lang.management.ManagementFactory; import java.nio.charset.Charset; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.Locale; import java.util.Optional; +import java.util.regex.Pattern; /** * Represents the operating system. @@ -98,6 +100,10 @@ public enum OperatingSystem { */ public static final String SYSTEM_ARCHITECTURE; + public static final Pattern INVALID_RESOURCE_CHARACTERS; + private static final String[] INVALID_RESOURCE_BASENAMES; + private static final String[] INVALID_RESOURCE_FULLNAMES; + static { String name = System.getProperty("os.name").toLowerCase(Locale.US); if (name.contains("win")) @@ -119,6 +125,24 @@ public enum OperatingSystem { if (arch == null) arch = System.getProperty("os.arch"); SYSTEM_ARCHITECTURE = arch; + + // setup the invalid names + if (CURRENT_OS == WINDOWS) { + // valid names and characters taken from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/fileio/fs/naming_a_file.asp + INVALID_RESOURCE_CHARACTERS = Pattern.compile("[/\"<>|?*:\\\\]"); + INVALID_RESOURCE_BASENAMES = new String[]{"aux", "com1", "com2", "com3", "com4", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ + "com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ + "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"}; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ //$NON-NLS-8$ //$NON-NLS-9$ + Arrays.sort(INVALID_RESOURCE_BASENAMES); + //CLOCK$ may be used if an extension is provided + INVALID_RESOURCE_FULLNAMES = new String[]{"clock$"}; //$NON-NLS-1$ + } else { + //only front slash and null char are invalid on UNIXes + //taken from http://www.faqs.org/faqs/unix-faq/faq/part2/section-2.html + INVALID_RESOURCE_CHARACTERS = null; + INVALID_RESOURCE_BASENAMES = null; + INVALID_RESOURCE_FULLNAMES = null; + } } private static Optional getTotalPhysicalMemorySize() { @@ -154,4 +178,38 @@ public enum OperatingSystem { return Paths.get(home, folder); } } + + /** + * Returns true if the given name is a valid resource name on this operating system, + * and false otherwise. + */ + public static boolean isNameValid(String name) { + //. and .. have special meaning on all platforms + if (name.equals(".") || name.equals("..") || name.indexOf('/') == 0 || name.indexOf('\0') >= 0) //$NON-NLS-1$ //$NON-NLS-2$ + return false; + if (CURRENT_OS == WINDOWS) { + //empty names are not valid + final int length = name.length(); + if (length == 0) + return false; + final char lastChar = name.charAt(length - 1); + // filenames ending in dot are not valid + if (lastChar == '.') + return false; + // file names ending with whitespace are truncated (bug 118997) + if (Character.isWhitespace(lastChar)) + return false; + int dot = name.indexOf('.'); + // on windows, filename suffixes are not relevant to name validity + String basename = dot == -1 ? name : name.substring(0, dot); + if (Arrays.binarySearch(INVALID_RESOURCE_BASENAMES, basename.toLowerCase()) >= 0) + return false; + if (Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase()) >= 0) + return false; + if (INVALID_RESOURCE_CHARACTERS != null && INVALID_RESOURCE_CHARACTERS.matcher(name).find()) + return false; + } + + return true; + } }