2014-02-20 76 views
3

我试图在Java中含有%编码字符URL在Java中的非ASCII字符

我一直在使用java.net.URI中的类来完成这项工作试图解码URL解码,但它并不总是工作正确。

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”,结果是正确的 “/维基/Fondation_Alliance_française”(%C3%A7被正确地被C取代)。

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

我用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,情况正好相反。

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

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

==== ====答案

由于在麦克道尔的建议回答以下,现在看来工作。这是我现在的代码:

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) && 
      ("ABCDEFabcdef".indexOf(segment.charAt(index + 1)) >= 0) && 
      ("ABCDEFabcdef".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; 
} 
+0

注意URLDecoder不适合解码URI路径;它适用于大多数情况,但不是全部。 – fge

+0

我知道,我只是试图使用它,因为在所有情况下,URI类并没有给我正确的答案,并且在这个问题中提供了更多的信息。 – NicoV

回答

2

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

+0

+1,非常详细和有帮助 – PopoFibo

+0

@McDowell非常感谢,我会在回家时尝试您的解决方案。 – NicoV

+0

很好用,我用我现在使用的实际代码编辑了我的最初问题。 – NicoV