Java 解码双重编码的 UTF-8 字符

6

我正在解析一个websocket消息,由于特定socket.io版本中的一个错误(很不幸,我无法控制服务器端),其中一些有效负载以utf-8双重编码:

正确的值应该是Wrocławskiej(请注意小写字母L带有斜线),但实际上我收到的是WrocÅawskiej

我已经尝试使用java进行再次解码/编码

String str = new String(wrongEncoded.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);

很遗憾,字符串保持不变。有没有办法在Java中进行双重解码?我看到了一个Python版本,他们先将其转换为raw_unicode,然后再次解析它,但我不知道这是否有效或是否有类似的解决方案适用于Java。我已经阅读了一些关于该主题的帖子,但都没有帮助。

编辑:为了澄清,在Fiddler中,我收到了上述单词的以下字节序列:

WrocÃÂawskiej

byte[] arrOutput = { 0x57, 0x72, 0x6F, 0x63, 0xC3, 0x85, 0xC2, 0x82, 0x61, 0x77, 0x73, 0x6B, 0x69, 0x65, 0x6A };

2
你能解释一下什么是双重编码吗?你的意思是字符串使用编解码器A进行编码,然后使用B进行解码,再次使用A进行编码吗?看起来你只是两次都使用UTF-8对字符串进行编码和解码。 - Andrew Sun
在 Å 后应该有一个额外的字符。String(您称其为 wrongEncoded)是如何创建的?我想知道那里是否存在问题,以及服务器端是否也有问题。 - erickson
我认为你的意思是错误行为是原始字符数据以UTF-8编码,然后生成的字节序列被解释为另一种(可能是单字节)编码中的字符数据,并且该字符解释被编码为UTF-8以获得最终结果。假设没有数据丢失(这既不清楚也不确定),我们需要了解详细信息才能帮助您反转此过程。 - John Bollinger
我更新了我的问题,包括接收到的字节。双重编码是指这个socket.io bug fix。当我使用Fiddler进行调试时,我发现有问题的编码已经来自服务器。因此,我需要找到一个解决方案来在客户端解决这个问题 - 当我已经得到字符串消息时。 - Christoph S
3个回答

11

你的文本被编码为UTF-8,然后将这些字节解释为ISO-8859-1并重新编码为UTF-8。

Wrocławskiej是unicode:0057 0072 006f 0063 0142 0061 0077 0073 006b 0069 0065 006a
将其编码为UTF-8:57 72 6f 63 c5 82 61 77 73 6b 69 65 6a

ISO-8859-1 中,c5Å82未定义。
作为ISO-8859-1,这些字节是:WrocÅawskiej
将其编码为UTF-8: 57 72 6f 63 c3 85 c2 82 61 77 73 6b 69 65 6a
这可能是你收到的字节。

因此,要撤销它,你需要:

String s = new String(bytes, StandardCharsets.UTF_8);

// fix "double encoding"
s = new String(s.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);

1
啊,好的,我没有想到它可能已经重新编码为ISO-8859-1了。现在对我来说可以工作了。 - Christoph S

1

我遇到了一个问题,有时候我会收到双重编码的字符串,有时候是正确编码的字符串。下面的方法 fixDoubleUTF8Encoding 可以处理这两种情况:

public static void main(String[] args) {
  String input = "werewräüèö";
  String result = fixDoubleUTF8Encoding(input);
  System.out.println(result); // werewräüèö
  
  input = "üäöé";
  result = fixDoubleUTF8Encoding(input);
  System.out.println(result); // üäöé
}

private static String fixDoubleUTF8Encoding(String s) {
  // interpret the string as UTF_8
  byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
  // now check if the bytes contain 0x83 0xC2, meaning double encoded garbage
  if(isDoubleEncoded(bytes)) {
    // if so, lets fix the string by assuming it is ASCII extended and recode it once
    s = new String(s.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);            
  }
  return s;
}

private static boolean isDoubleEncoded(byte[] bytes) {
  for (int i = 0; i < bytes.length; i++) {
    if(bytes[i] == -125 && i+1 < bytes.length && bytes[i+1] == -62) {
      return true;
    }
  }
  return false;
}

0

好的,双重编码可能不是唯一需要处理的问题。这里有一个解决方案,有多个原因值得考虑。

String myString = "heartbroken ð";
                myString = new String(myString.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
                String cleanedText = StringEscapeUtils.unescapeJava(myString);
                byte[] bytes = cleanedText.getBytes(StandardCharsets.UTF_8);
                String text = new String(bytes, StandardCharsets.UTF_8);
                Charset charset = Charset.forName("UTF-8");
                CharsetDecoder decoder = charset.newDecoder();
                decoder.onMalformedInput(CodingErrorAction.IGNORE);
                decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
                CharsetEncoder encoder = charset.newEncoder();
                encoder.onMalformedInput(CodingErrorAction.IGNORE);
                encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
                try {
                    // The new ByteBuffer is ready to be read.
                    ByteBuffer bbuf = encoder.encode(CharBuffer.wrap(text));
                    // The new ByteBuffer is ready to be read.
                    CharBuffer cbuf = decoder.decode(bbuf);
                    String str = cbuf.toString();
                } catch (CharacterCodingException e) {
                    logger.error("Error Message if you want to");

                } 

A


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接