Java中用于非ASCII字符的URL解码

我正在尝试使用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 = 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; } 

ANV%E4ndare

正如PopoFibo所说,这不是一个有效的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 – 不是UTF-8方案的有效序列,并且解码器可以检测到这一点。

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


注意: HTTP不关心百分比编码,您可以编写一个接受http://foo/%%%%%作为有效表单的Web服务器。 URI规范要求使用UTF-8,但这是追溯性的。 服务器真的应该描述它的URI应该是什么forms,如果你必须处理任意URI,你需要知道这个遗产。

我在这里写了更多关于URL和Java的文章 。