在 Java 中规范化可能编码的 URI 字符串

Posted

技术标签:

【中文标题】在 Java 中规范化可能编码的 URI 字符串【英文标题】:Normalising possibly encoded URI strings in Java 【发布时间】:2012-03-14 05:57:44 【问题描述】:

使用 Java,我想去除片段标识符并对一组不同的 URI 进行一些简单的规范化(例如,小写方案、主机)。输入和输出 URI 在一般 HTTP 意义上应该是等效的。

通常,这应该很简单。但是,对于像 http://blah.org/A_%28Secret%29.xml#blah 这样的 URI,它对 (Secret) 进行了百分比编码,java.util.URI 的行为让生活变得困难。

规范化方法应该返回 http://blah.org/A_%28Secret%29.xml,因为 URI http://blah.org/A_%28Secret%29.xmlhttp://blah.org/A_(Secret).xml 在解释上是不等价的 [§2.2; RFC3968]

所以我们有以下两种归一化方法:

URI u = new URI("http://blah.org/A_%28Secret%29.xml#blah");
System.out.println(u);
        // prints "http://blah.org/A_%28Secret%29.xml#blah"

String path1 = u.getPath();      //gives "A_(Secret).xml"
String path2 = u.getRawPath();   //gives "A_%28Secret%29.xml"


//NORMALISE METHOD 1
URI norm1 = new URI(u.getScheme().toLowerCase(), u.getUserInfo(), 
                      u.getHost().toLowerCase(), u.getPort(), path1, 
                      u.getQuery(), null);
System.out.println(norm1);
// prints "http://blah.org/A_(Secret).xml"

//NORMALISE METHOD 2
URI norm2 = new URI(u.getScheme().toLowerCase(), u.getUserInfo(),
                      u.getHost().toLowerCase(), u.getPort(), path2, 
                      u.getQuery(), null);
System.out.println(norm2);
// prints "http://blah.org/A_%2528Secret%2529.xml"

正如我们所见,URI 在没有片段标识符的情况下被解析和重建。

但是,对于方法 1,u.getPath() 返回一个未编码的 URI,这会改变最终的 URI。

对于方法 2,u.getRawPath() 返回原始路径,但是当传递给 URI 构造函数时,Java 决定添加双重编码。

这感觉就像一个中国手指陷阱。

所以两个主要问题:

为什么java.util.URI 觉得有必要玩编码? 如何在不修改原始百分比编码的情况下实现这种标准化方法?

(我宁愿不必实现java.util.URI 的解析/连接方法,这些方法很重要。)


编辑:以下是来自URI javadoc 的更多信息。

单参数构造函数要求在其参数中引用任何非法字符,并保留所有转义的八位字节和其他存在的字符。

多参数构造函数 引用出现它们的组件所要求的非法字符。 百分号字符('%')总是被这些构造函数引用。任何其他字符都被保留。

getRawUserInfo、getRawPath、getRawQuery、getRawFragment、getRawAuthority 和 getRawSchemeSpecificPart 方法以原始形式返回其对应组件的值,不解释任何转义的八位位组 .这些方法返回的字符串可能同时包含转义的八位字节和其他字符,并且不会包含任何非法字符。

getUserInfo、getPath、getQuery、getFragment、getAuthority 和 getSchemeSpecificPart 方法解码相应组件中的任何转义八位字节。这些方法返回的字符串可能同时包含其他字符和非法字符,并且不会包含任何转义的八位字节。

toString 方法返回一个包含所有必要引号但可能包含其他字符的 URI 字符串。

toASCIIString 方法返回不包含任何其他字符的完全引用和编码的 URI 字符串。

因此,如果没有 URI 类在内部弄乱 URL 编码,我就无法使用多参数构造函数。呸!

【问题讨论】:

用例是一个爬虫。我们希望提取一组提取的 URI 并将它们“规范化”为尽可能小的一组,同时确保检索到的内容保证是相同的。 (问题***.com/questions/2993649/… 是相关的,但没有解决剥离片段ID 的问题,URL 编码发生变化。) 我远离 URI 的东西,不确定您是否需要以标准方式使用 URI API,但如果我只是想以某种方式实现此功能,我会选择 1 ) 获取原始 url 的子字符串,直到第一次出现 #?& 因为这实际上是将 url 与额外信息分开或 2) 让 URI 创建正常的 uri(例如 norm2)然后将所有 % 替换为位置序列中的原始值(norm2 的第一个与原始的第一个等)。当然,这只是在标准方式不可用的情况下。 【参考方案1】:

因为java.net.URI 是在 java 1.4(2002 年推出)中引入的,它基于 RFC2396,它将 '(' 和 ')' 视为 不需要 转义的字符,并且语义即使被转义也不会改变,而且它甚至说除非必要,否则不应转义(§2.3,RFC2396)。

但是 RFC3986(2005 年发布)改变了这一点,我猜 JDK 的开发人员决定不改变 java.net.URI 的行为以兼容现有代码。

通过随机搜索,我发现Jena IRI 看起来不错。

public class IRITest 
public static void main(String[] args) 
    IRIFactory factory = IRIFactory.uriImplementation();
    IRI iri = factory.construct("http://blah.org/A_%28Secret%29.xml#blah");
    ArrayList<String> a = new ArrayList<String>();
    a.add(iri.getScheme());
    a.add(iri.getRawUserinfo());
    a.add(iri.getRawHost());
    a.add(iri.getRawPath());
    a.add(iri.getRawQuery());
    a.add(iri.getRawFragment());
    IRI iri2 = factory.construct("http://blah.org/A_(Secret).xml#blah");
    ArrayList<String> b = new ArrayList<String>();
    b.add(iri2.getScheme());
    b.add(iri2.getRawUserinfo());
    b.add(iri2.getRawHost());
    b.add(iri2.getRawPath());
    b.add(iri2.getRawQuery());
    b.add(iri2.getRawFragment());

    System.out.println(a);
    //[http, null, blah.org, /A_%28Secret%29.xml, null, blah]
    System.out.println(b);
    //[http, null, blah.org, /A_(Secret).xml, null, blah]


【讨论】:

【参考方案2】:

请注意 [§2.2 末尾的这段话; RFC3968]

URI 生成应用程序应该对数据字节进行百分比编码 对应于保留集中的字符,除非这些字符 URI 方案特别允许在其中表示数据 零件。如果在 URI 组件中找到保留字符并且 该字符没有已知的分隔角色,那么它必须是 解释为表示对应的数据八位字节 US-ASCII 字符的编码。

所以,只要方案是http或者https,编码就是正确的行为。

尝试使用toASCIIString 方法而不是toString 来打印URI。例如:

System.put.println(norm1.toASCIIString());

【讨论】:

感谢您的信息!不确定我是否同意你对这段话的解释。这部分:“除非 URI 方案明确允许这些字符表示该组件中的数据”表明 HTTP/HTTPS 没有必要允许,例如,"()" 字符。在任何情况下,如果您考虑以下段落“对保留字符进行百分比编码,或对与保留字符相对应的百分比编码八位字节进行解码,将改变大多数人解释 URI 的方式,那么对于爬虫来说,这个问题将变得毫无意义。应用程序。”。 (toASCIIString方法在这里没有作用。)

以上是关于在 Java 中规范化可能编码的 URI 字符串的主要内容,如果未能解决你的问题,请参考以下文章

1.关键字保留字标识符命名规范

Java 和 RFC 3986 URI 编码

在Tomcat中自动检测URI编码

java 字符流 字节流

URL 规范 整理

冒号是不是需要在 URI 查询参数中进行编码?