将 isNameValid 方法移动至 FileUtils (#4491)
This commit is contained in:
@@ -489,7 +489,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
|||||||
FORBIDDEN_VERSION_IDS.contains(id.toLowerCase(Locale.ROOT)))
|
FORBIDDEN_VERSION_IDS.contains(id.toLowerCase(Locale.ROOT)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return OperatingSystem.isNameValid(id);
|
return FileUtils.isNameValid(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import org.jackhuang.hmcl.ui.wizard.Navigation;
|
|||||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -148,7 +148,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
|||||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
|
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
|
||||||
|
|
||||||
Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> {
|
Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> {
|
||||||
if (!OperatingSystem.isNameValid(result)) {
|
if (!FileUtils.isNameValid(result)) {
|
||||||
reject.accept(i18n("install.new_game.malformed"));
|
reject.accept(i18n("install.new_game.malformed"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import org.jackhuang.hmcl.util.Lang;
|
|||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@@ -170,7 +169,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.contains("/") || result.contains("\\") || !OperatingSystem.isNameValid(result)) {
|
if (result.contains("/") || result.contains("\\") || !FileUtils.isNameValid(result)) {
|
||||||
reject.accept(i18n("schematics.create_directory.failed.invalid_name"));
|
reject.accept(i18n("schematics.create_directory.failed.invalid_name"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
|||||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||||
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
|
||||||
@@ -119,7 +120,7 @@ public final class Versions {
|
|||||||
|
|
||||||
public static CompletableFuture<String> renameVersion(Profile profile, String version) {
|
public static CompletableFuture<String> renameVersion(Profile profile, String version) {
|
||||||
return Controllers.prompt(i18n("version.manage.rename.message"), (newName, resolve, reject) -> {
|
return Controllers.prompt(i18n("version.manage.rename.message"), (newName, resolve, reject) -> {
|
||||||
if (!OperatingSystem.isNameValid(newName)) {
|
if (!FileUtils.isNameValid(newName)) {
|
||||||
reject.accept(i18n("install.new_game.malformed"));
|
reject.accept(i18n("install.new_game.malformed"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,84 @@ public final class FileUtils {
|
|||||||
else return getName(path);
|
else return getName(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/biztalk/core/restrictions-when-configuring-the-file-adapter
|
||||||
|
private static final Set<String> INVALID_WINDOWS_RESOURCE_BASE_NAMES = Set.of(
|
||||||
|
"aux", "con", "nul", "prn", "clock$",
|
||||||
|
"com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9",
|
||||||
|
"com¹", "com²", "com³",
|
||||||
|
"lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9",
|
||||||
|
"lpt¹", "lpt²", "lpt³"
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @see #isNameValid(OperatingSystem, String)
|
||||||
|
public static boolean isNameValid(String name) {
|
||||||
|
return isNameValid(OperatingSystem.CURRENT_OS, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given name is a valid file name on the given operating system,
|
||||||
|
/// and `false` otherwise.
|
||||||
|
public static boolean isNameValid(OperatingSystem os, String name) {
|
||||||
|
// empty filename is not allowed
|
||||||
|
if (name.isEmpty())
|
||||||
|
return false;
|
||||||
|
// '.', '..' and '~' have special meaning on all platforms
|
||||||
|
if (name.equals(".") || name.equals("..") || name.equals("~"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int i = 0; i < name.length(); i++) {
|
||||||
|
char ch = name.charAt(i);
|
||||||
|
int codePoint;
|
||||||
|
|
||||||
|
if (Character.isSurrogate(ch)) {
|
||||||
|
if (!Character.isHighSurrogate(ch))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (i == name.length() - 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
char ch2 = name.charAt(++i);
|
||||||
|
if (!Character.isLowSurrogate(ch2))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
codePoint = Character.toCodePoint(ch, ch2);
|
||||||
|
} else {
|
||||||
|
codePoint = ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Character.isValidCodePoint(codePoint)
|
||||||
|
|| Character.isISOControl(codePoint)
|
||||||
|
|| codePoint == '/' || codePoint == '\0'
|
||||||
|
// Unicode replacement character
|
||||||
|
|| codePoint == 0xfffd
|
||||||
|
// Not Unicode character
|
||||||
|
|| codePoint == 0xfffe || codePoint == 0xffff)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/windows/win32/fileio/naming-a-file
|
||||||
|
if (os == OperatingSystem.WINDOWS &&
|
||||||
|
(ch == '<' || ch == '>' || ch == ':' || ch == '"' || ch == '\\' || ch == '|' || ch == '?' || ch == '*')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os == OperatingSystem.WINDOWS) { // Windows only
|
||||||
|
char lastChar = name.charAt(name.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;
|
||||||
|
|
||||||
|
// on windows, filename suffixes are not relevant to name validity
|
||||||
|
String basename = StringUtils.substringBeforeLast(name, '.');
|
||||||
|
if (INVALID_WINDOWS_RESOURCE_BASE_NAMES.contains(basename.toLowerCase(Locale.ROOT)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public static String readTextMaybeNativeEncoding(Path file) throws IOException {
|
public static String readTextMaybeNativeEncoding(Path file) throws IOException {
|
||||||
byte[] bytes = Files.readAllBytes(file);
|
byte[] bytes = Files.readAllBytes(file);
|
||||||
|
|
||||||
|
|||||||
@@ -121,10 +121,6 @@ public enum OperatingSystem {
|
|||||||
|
|
||||||
public static final int CODE_PAGE;
|
public static final int CODE_PAGE;
|
||||||
|
|
||||||
public static final Pattern INVALID_RESOURCE_CHARACTERS;
|
|
||||||
private static final String[] INVALID_RESOURCE_BASENAMES;
|
|
||||||
private static final String[] INVALID_RESOURCE_FULLNAMES;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
String nativeEncoding = System.getProperty("native.encoding");
|
String nativeEncoding = System.getProperty("native.encoding");
|
||||||
String hmclNativeEncoding = System.getProperty("hmcl.native.encoding");
|
String hmclNativeEncoding = System.getProperty("hmcl.native.encoding");
|
||||||
@@ -229,24 +225,6 @@ public enum OperatingSystem {
|
|||||||
}
|
}
|
||||||
OS_RELEASE_NAME = osRelease.get("NAME");
|
OS_RELEASE_NAME = osRelease.get("NAME");
|
||||||
OS_RELEASE_PRETTY_NAME = osRelease.get("PRETTY_NAME");
|
OS_RELEASE_PRETTY_NAME = osRelease.get("PRETTY_NAME");
|
||||||
|
|
||||||
// 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",
|
|
||||||
"com5", "com6", "com7", "com8", "com9", "con", "lpt1", "lpt2",
|
|
||||||
"lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", "nul", "prn"};
|
|
||||||
Arrays.sort(INVALID_RESOURCE_BASENAMES);
|
|
||||||
//CLOCK$ may be used if an extension is provided
|
|
||||||
INVALID_RESOURCE_FULLNAMES = new String[]{"clock$"};
|
|
||||||
} 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OperatingSystem parseOSName(String name) {
|
public static OperatingSystem parseOSName(String name) {
|
||||||
@@ -288,68 +266,4 @@ public enum OperatingSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the given name is a valid file name on this operating system,
|
|
||||||
* and false otherwise.
|
|
||||||
*/
|
|
||||||
public static boolean isNameValid(String name) {
|
|
||||||
// empty filename is not allowed
|
|
||||||
if (name.isEmpty())
|
|
||||||
return false;
|
|
||||||
// . and .. have special meaning on all platforms
|
|
||||||
if (name.equals(".") || name.equals(".."))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (int i = 0; i < name.length(); i++) {
|
|
||||||
char ch = name.charAt(i);
|
|
||||||
int codePoint;
|
|
||||||
|
|
||||||
if (Character.isSurrogate(ch)) {
|
|
||||||
if (!Character.isHighSurrogate(ch))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (i == name.length() - 1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
char ch2 = name.charAt(++i);
|
|
||||||
if (!Character.isLowSurrogate(ch2))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
codePoint = Character.toCodePoint(ch, ch2);
|
|
||||||
} else {
|
|
||||||
codePoint = ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Character.isValidCodePoint(codePoint)
|
|
||||||
|| Character.isISOControl(codePoint)
|
|
||||||
|| codePoint == '/' || codePoint == '\0'
|
|
||||||
// Unicode replacement character
|
|
||||||
|| codePoint == 0xfffd
|
|
||||||
// Not Unicode character
|
|
||||||
|| codePoint == 0xfffe || codePoint == 0xffff)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CURRENT_OS == WINDOWS) { // Windows only
|
|
||||||
char lastChar = name.charAt(name.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(Locale.ROOT)) >= 0)
|
|
||||||
return false;
|
|
||||||
if (Arrays.binarySearch(INVALID_RESOURCE_FULLNAMES, name.toLowerCase(Locale.ROOT)) >= 0)
|
|
||||||
return false;
|
|
||||||
if (INVALID_RESOURCE_CHARACTERS.matcher(name).find())
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher
|
||||||
|
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.util.io;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.EnumSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/// @author Glavo
|
||||||
|
public class FileUtilsTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@EnumSource(OperatingSystem.class)
|
||||||
|
public void testIsNameValid(OperatingSystem os) {
|
||||||
|
assertTrue(FileUtils.isNameValid(os, "example"));
|
||||||
|
assertTrue(FileUtils.isNameValid(os, "example.zip"));
|
||||||
|
assertTrue(FileUtils.isNameValid(os, "example.tar.gz"));
|
||||||
|
assertTrue(FileUtils.isNameValid(os, "\uD83D\uDE00"));
|
||||||
|
assertTrue(FileUtils.isNameValid(os, "a\uD83D\uDE00b"));
|
||||||
|
|
||||||
|
assertFalse(FileUtils.isNameValid(os, ""));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "."));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, ".."));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "exam\0ple"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "example/0"));
|
||||||
|
|
||||||
|
// Test for invalid surrogate pair
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "\uD83D"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "\uDE00"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "\uDE00\uD83D"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "\uD83D\uD83D"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "a\uD83D"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "a\uDE00"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "a\uDE00\uD83D"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "a\uD83Db"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "a\uDE00b"));
|
||||||
|
assertFalse(FileUtils.isNameValid(os, "a\uDE00\uD83Db"));
|
||||||
|
|
||||||
|
// Platform-specific tests
|
||||||
|
boolean isWindows = os == OperatingSystem.WINDOWS;
|
||||||
|
boolean isNotWindows = !isWindows;
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "com1"));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "com1.txt"));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "foo."));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "foo "));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f<oo"));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f>oo"));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f:oo"));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f?oo"));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f*oo"));
|
||||||
|
assertEquals(isNotWindows, FileUtils.isNameValid(os, "f\\oo"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher
|
|
||||||
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.util.platform;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.platform.OperatingSystem.isNameValid;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Glavo
|
|
||||||
*/
|
|
||||||
public final class OperatingSystemTest {
|
|
||||||
@Test
|
|
||||||
public void testIsNameValid() {
|
|
||||||
assertTrue(isNameValid("example"));
|
|
||||||
assertTrue(isNameValid("example.zip"));
|
|
||||||
assertTrue(isNameValid("example.tar.gz"));
|
|
||||||
assertTrue(isNameValid("\uD83D\uDE00"));
|
|
||||||
assertTrue(isNameValid("a\uD83D\uDE00b"));
|
|
||||||
|
|
||||||
assertFalse(isNameValid("."));
|
|
||||||
assertFalse(isNameValid(".."));
|
|
||||||
assertFalse(isNameValid("exam\0ple"));
|
|
||||||
assertFalse(isNameValid("example/0"));
|
|
||||||
|
|
||||||
// Test for invalid surrogate pair
|
|
||||||
assertFalse(isNameValid("\uD83D"));
|
|
||||||
assertFalse(isNameValid("\uDE00"));
|
|
||||||
assertFalse(isNameValid("\uDE00\uD83D"));
|
|
||||||
assertFalse(isNameValid("\uD83D\uD83D"));
|
|
||||||
assertFalse(isNameValid("a\uD83D"));
|
|
||||||
assertFalse(isNameValid("a\uDE00"));
|
|
||||||
assertFalse(isNameValid("a\uDE00\uD83D"));
|
|
||||||
assertFalse(isNameValid("a\uD83Db"));
|
|
||||||
assertFalse(isNameValid("a\uDE00b"));
|
|
||||||
assertFalse(isNameValid("a\uDE00\uD83Db"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user