【问题标题】:URL decoding in Java for non-ASCII charactersJava中非ASCII字符的URL解码
【发布时间】:2014-03-21 05:11:29
【问题描述】:

我正在尝试用 Java 解码包含 % 个编码字符的 URL

我尝试使用 java.net.URI 类来完成这项工作,但它并不总是正常工作。

String test = "https://fr.wikipedia.org/wiki/Fondation_Alliance_fran%C3%A7aise";
URI uri = new URI(test);
System.out.println(uri.getPath());

对于测试字符串“https://fr.wikipedia.org/wiki/Fondation_Alliance_fran%C3%A7aise”,结果是正确的“/wiki/Fondation_Alliance_française”(%C3%A7 正确替换为ç)。

但对于其他一些测试字符串,例如“http://sv.wikipedia.org/wiki/Anv%E4ndare:Lsjbot/Statistik#Drosophilidae”,它给出的结果不正确“/wiki/Anv�ndare:Lsjbot/Statistik”(%E4 被替换为 � 而不是 ä)。

我用 getRawPath() 和 URLDecoder 类做了一些测试。

System.out.println(URLDecoder.decode(uri.getRawPath(), "UTF8"));
System.out.println(URLDecoder.decode(uri.getRawPath(), "ISO-8859-1"));
System.out.println(URLDecoder.decode(uri.getRawPath(), "WINDOWS-1252"));

根据测试字符串,我得到不同编码的正确结果:

  • 对于 %C3%A7,使用“UTF-8”编码得到正确结果,使用“ISO-8859-1”或“WINDOWS-1252”编码得到错误结果
  • 对于 %E4,情况正好相反。

对于这两个测试 URL,如果我将它们放在 Chrome 地址栏中,我会得到正确的页面。

如何在所有情况下正确解码 URL? 感谢您的帮助

==== 回答====

感谢下面 McDowell 回答中的建议,它现在似乎有效。这是我现在拥有的代码:

private static void appendBytes(ByteArrayOutputStream buf, String data) throws UnsupportedEncodingException {
  byte[] b = data.getBytes("UTF8");
  buf.write(b, 0, b.length);
}

private static byte[] parseEncodedString(String segment) throws UnsupportedEncodingException {
  ByteArrayOutputStream buf = new ByteArrayOutputStream(segment.length());
  int last = 0;
  int index = 0;
  while (index < segment.length()) {
    if (segment.charAt(index) == '%') {
      appendBytes(buf, segment.substring(last, index));
      if ((index < segment.length() + 2) &&
          ("ABCDEFabcdef0123456789".indexOf(segment.charAt(index + 1)) >= 0) &&
          ("ABCDEFabcdef0123456789".indexOf(segment.charAt(index + 2)) >= 0)) {
        buf.write((byte) Integer.parseInt(segment.substring(index + 1, index + 3), 16));
        index += 3;
      } else if ((index < segment.length() + 1) &&
                 (segment.charAt(index + 1) == '%')) {
        buf.write((byte) '%');
        index += 2;
      } else {
        buf.write((byte) '%');
        index++;
      }
      last = index;
    } else {
      index++;
    }
  }
  appendBytes(buf, segment.substring(last));
  return buf.toByteArray();
}

private static String parseEncodedString(String segment, Charset... encodings) {
  if ((segment == null) || (segment.indexOf('%') < 0)) {
    return segment;
  }
  try {
    byte[] data = parseEncodedString(segment);
    for (Charset encoding : encodings) {
      try {
        if (encoding != null) {
          return encoding.newDecoder().
              onMalformedInput(CodingErrorAction.REPORT).
              decode(ByteBuffer.wrap(data)).toString();
        }
      } catch (CharacterCodingException e) {
        // Incorrect encoding, try next one
      }
    }
  } catch (UnsupportedEncodingException e) {
    // Nothing to do
  }
  return segment;
}

【问题讨论】:

  • 注意 URLDecoder 适合解码 URI 路径;它适用于大多数情况,但不是全部。
  • 我知道,我只是尝试使用它,因为 URI 类在所有情况下都没有给我正确的答案,并在这个问题中提供更多信息。

标签: java encoding url-encoding


【解决方案1】:

Anv%E4ndare

作为PopoFibo says,这不是一个有效的 UTF-8 编码序列。

你可以做一些宽容的最佳猜测解码:

public static String parse(String segment, Charset... encodings) {
  byte[] data = parse(segment);
  for (Charset encoding : encodings) {
    try {
      return encoding.newDecoder()
          .onMalformedInput(CodingErrorAction.REPORT)
          .decode(ByteBuffer.wrap(data))
          .toString();
    } catch (CharacterCodingException notThisCharset_ignore) {}
  }
  return segment;
}

private static byte[] parse(String segment) {
  ByteArrayOutputStream buf = new ByteArrayOutputStream();
  Matcher matcher = Pattern.compile("%([A-Fa-f0-9][A-Fa-f0-9])")
                          .matcher(segment);
  int last = 0;
  while (matcher.find()) {
    appendAscii(buf, segment.substring(last, matcher.start()));
    byte hex = (byte) Integer.parseInt(matcher.group(1), 16);
    buf.write(hex);
    last = matcher.end();
  }
  appendAscii(buf, segment.substring(last));
  return buf.toByteArray();
}

private static void appendAscii(ByteArrayOutputStream buf, String data) {
  byte[] b = data.getBytes(StandardCharsets.US_ASCII);
  buf.write(b, 0, b.length);
}

此代码将成功解码给定的字符串:

for (String test : Arrays.asList("Fondation_Alliance_fran%C3%A7aise",
    "Anv%E4ndare")) {
  String result = parse(test, StandardCharsets.UTF_8,
      StandardCharsets.ISO_8859_1);
  System.out.println(result);
}

请注意,这不是一个允许您忽略正确 URL 编码的万无一失的系统。它在这里工作是因为 v%E4n - 字节序列76 E4 6E - 不是the UTF-8 scheme 的有效序列,解码器可以检测到这一点。

如果您颠倒编码顺序,第一个字符串可以愉快地(但错误地)解码为 ISO-8859-1。


注意:HTTP doesn't care 关于百分比编码,您可以编写一个接受 http://foo/%%%%% 作为有效形式的 Web 服务器。 URI spec 要求使用 UTF-8,但这是追溯性的。真正由服务器来描述它的 URI 应该是什么形式,如果你必须处理任意 URI,你需要了解这个遗留问题。

我写了一点more about URLs and Java here

【讨论】:

  • @McDowell 非常感谢,回家后我会试试你的解决方案。
  • 效果很好,我已经用我现在使用的实际代码编辑了我最初的问题。
猜你喜欢
  • 2021-07-28
  • 1970-01-01
  • 2013-06-19
  • 1970-01-01
  • 2020-12-21
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
相关资源
最近更新 更多