在Java单元测试中使用无效的UTF-8字符串测试API输入验证

zazmityj  于 2023-01-11  发布在  Java
关注(0)|答案(1)|浏览(124)

我正在用Java维护一个后端服务,我使用Java 8代码的以下方法来验证服务API的输入:

private static boolean containsDisallowedChars(String toValidate) {
    return !StandardCharsets.US_ASCII.newEncoder().canEncode(toValidate);
}

我正在扩展它以支持印地语和其他非英语字符,因此我将它从ASCII改为UTF-8,如下所示:

private static boolean containsDisallowedChars(String toValidate) {
    return !StandardCharsets.UTF_8.newEncoder().canEncode(toValidate);
}

现在,我尝试更新相应的单元测试,以传入一个String toValidate,这将导致此方法返回false。
如何创建包含无法编码为UTF-8的内容的Java字符串?
我试过这个测试装置

// ref https://stackoverflow.com/questions/1301402/example-invalid-utf8-string
// test data byte values https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
// 3.5  Impossible bytes
// The following two bytes cannot appear in a correct UTF-8 string
// 3.5.1  fe = "�"
// 3.5.2  ff = "�"
// 3.5.3  fe fe ff ff = "����"
final byte[] bytes = {(byte)0xfe, (byte)0xfe, (byte)0xff, (byte)0xff};
log.info("bytes={}", bytes);
final String s = new String(bytes);
log.info("s={}", s);
log.info("s.length={}", s.length());
log.info("s.bytes={}", s.getBytes());

StandardCharsets.UTF_8.newEncoder().canEncode(s)返回true,日志输出显示String类构造函数正在更改字节数组,如下所示:

bytes=[-2, -2, -1, -1]
s=����
s.length=4
s.bytes=[-17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67]

我使用https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt中描述的其他无效UTF-8字节数组尝试了几种变体,得到了类似的结果
尽管我努力提供了无效的字节数组,但String类似乎仍能健壮地创建有效的UTF-8字符串。
我按照这里的建议尝试了Base64 How can I generate non-UTF-8 string / char in Java for testing purposes?

final byte[] bytes = {(byte)0xfe, (byte)0xfe, (byte)0xff, (byte)0xff};
log.info("bytes={}", bytes);
final String s = new String(Base64.getEncoder().encode(bytes));
log.info("s={}", s);
log.info("s.length={}", s.length());
log.info("s.bytes={}", s.getBytes());

Base64.getEncoder().encode不返回字符串,它返回byte[]。因此,我仍然必须调用new String(byte[]),它将字节数组更改为有效的UTF-8字节数组。StandardCharsets.UTF_8.newEncoder().canEncode仍然返回true,我得到以下日志输出:

bytes=[-2, -2, -1, -1]
s=/v7//w==
s.length=8
s.bytes=[47, 118, 55, 47, 47, 119, 61, 61]

是否可以创建一个Java String对象,其中包含一个不能编码为UTF-8的字符串?如果不可以,是否意味着我的containsDisallowedChars方法是不必要的,因为它永远不会返回true?或者我应该考虑一种不同的验证方法来代替StandardCharsets.UTF_8.newEncoder().canEncode?

nlejzf6q

nlejzf6q1#

你在问题中指出:
尽管我努力提供了无效的字节数组,但String类似乎仍能健壮地创建有效的UTF-8字符串。
如果要测试字节数组以查看它对于特定编码是否有效,则可以使用CharsetDecoder(而不是CharsetEncoder)。
CharsetDecoder可以:
将特定字符集中的字节序列转换为十六位Unicode字符序列。
如果将decode()方法传递给ByteBuffer,则可以按如下方式使用它:

private static boolean testBytes(byte[] bytes) {
    boolean isValid = true;
    try {
        StandardCharsets.UTF_8.newDecoder().decode(ByteBuffer.wrap(bytes));
    } catch (CharacterCodingException ex) {
        //Logger.getLogger(MyTester.class.getName()).log(Level.SEVERE, null, ex);
        isValid = false;
    }
    return isValid;
}

例如,下面的代码将打印false,因为0xFF不是一个有效的UTF-8字节序列。

byte[] b = HexFormat.of().parseHex("ff");
System.out.println(testBytes(b));

示例{(byte)0xfe, (byte)0xfe, (byte)0xff, (byte)0xff}也将返回false
在你的问题中,你问:
是否可以创建一个包含不能编码为UTF-8的字符串的Java String对象?
当您创建了一个JavaString时,要进行检查已经"太晚"了,因为,正如您所看到的,任何不受支持的字节序列都已经被Unicode replacement character所取代--Unicode replacement character本身是Java字符串中的有效字符(Java String对象本身"表示UTF-16格式的字符串"--UTF-8和UTF-16都覆盖了所有有效的Unicode代码点)。

相关问题