【问题标题】:Correctly encoding characters in a URL when using HttpClient使用 HttpClient 时正确编码 URL 中的字符
【发布时间】:2011-06-23 02:39:25
【问题描述】:

我有一个 URL 列表,我需要验证这些 URL 是否有效。我用 Java 编写了一个程序,它使用 Apache 的 HttpClient 来检查链接。由于存在默认策略未处理的无效字符(例如重定向 URL 中的 {}),我不得不实施自己的重定向策略。它在大多数情况下都可以正常工作,除了其中 2 种情况:

  1. 路径或查询参数中的转义字符,不应进一步编码。示例:

    String url = "http://www.example.com/chapter1/%3Fref%3Dsomething%26term%3D?ref=xyz"
    

    如果我使用 URI 对象,它会卡在“{”字符上。

    URI myUri = new URI(url) ==> This will fail. 
    

    如果我跑:

    URI myUri = new URI(UriUtils.encodeHttpUrl(url)) 
    

    它将 %3F 编码为 %253F。 但是,当我使用 Chrome 或 Fiddler 访问该链接时,我没有看到 %3F 再次被转义。如何防止过度编码路径或查询参数?

  2. URL 中的最后一个查询参数也有一个有效的 URL。例如。

    String url = "www.example.com/Chapter1/?param1=xyz&param2=http://www.google.com/?abc=1"
    

我当前的编码策略将查询参数拆分,然后在查询参数上调用 URLEncoder.encode。然而,这也会导致最后一个参数被编码(当我在 Fiddler 或 Chrome 中遵循它时不是这种情况)。

我尝试了很多方法(使用 UriUtils、将 URL 的特殊情况作为最后一个参数和其他技巧),但似乎没有什么是理想的。解决这个问题的最佳方法是什么?

【问题讨论】:

    标签: java httpclient


    【解决方案1】:

    如何防止路径或查询参数过度编码?

    您不能“防止过度编码”。您要么编码,要么不编码。对于任何给定的字符串,您应该始终知道它是否已编码。您应该只对尚未编码的字符串进行编码,并且永远不要对已经编码的字符串进行编码。

    那么这个字符串是否被编码?

    %3Fref%3Dsomething%26term%3D{keyword}
    

    在我看来这是错误的输入:显然这不是编码,因为它包含无效字符('{' 和'}')。然而,它似乎也不是未编码的字符串,因为它包含 '%xx' 序列。所以它是部分编码的。一旦字符串采用这种形式,就没有程序化的“解决方案”——您只需要首先避免将字符串变成这种形式。您可以构造一个“修复”此字符串的算法,方法是仔细查找看起来像“%”的部分,后跟两个十六进制数字,然后不理会它们。但这将在微妙的情况下打破。考虑一个未编码的字符串“42%23”,它应该是数学表达式“42 mod 23”的文字表示。当我将其放入 URI 时,我希望它编码为“42%2523”,因此它解码为“42%23”,但上述算法会中断并将其编码为“42%23”,然后解码为“ 42#”。所以没有办法修复上面的字符串。将“%3F”编码为“%253F”正是 URI 编码器应该做的事情。

    注意:话虽如此,浏览器通常允许您在 URI 中输入错误字符并自动对其进行编码。这不是很健壮,因此除非您试图非常宽容用户输入,否则不应使用它。在这种情况下,您可以通过首先解码 URI 然后重新编码来“尽最大努力”。在这种情况下,如果我想输入“42%23”,则必须手动输入“42%2523”。

    关于问题2:

    然而,这也会导致最后一个参数被编码

    同样,这正是您想要的。如果一个 URI 作为一个参数出现在另一个 URI 中,它应该是百分比编码的。否则,你怎么知道一个 URI 在哪里结束而另一个在哪里继续呢?我相信上面的 URI 实际上是有效的(因为 ':'、'/'、'&' 和 '=' 是保留字符,没有被禁止,因此只要它们不产生歧义,它们就被允许使用)。但是转义 URI-inside-a-URI 会更安全。

    【讨论】:

    • @mgiuca-thx 获取详细答案。我不控制输入,并试图尽可能多地复制浏览器的行为。我在第一季度修复了示例 URL。您使用的方法的问题是,当我对其进行编码时,它会沿着不正确的 10 级深度重定向路径,当我尝试通过 fiddler 或 chrome 跟踪它时,我看到我已经编码了浏览器没有的字符或参数。对于第二季度,我想我的 q 应该是编码查询参数的最佳方法应该是因为查询参数上的 URLEncode 工作正常,除非有最后一个参数中的 URL。
    • “除非最后一个参数中有 URL”是什么意思? URLEncoder.encode("http://www.google.com/?abc=1") 给出"http%3A%2F%2Fwww.google.com%2F%3Fabc%3D1",这是正确的。您不应该在没有先对其进行编码的情况下将 URL 作为查询参数放入,否则在极端情况下会发生奇怪的行为。
    • 我的整体方案中有一个错误。这个答案帮助我退后一步重新分析。
    【解决方案2】:

    我真的不知道,但是您可以尝试先对其进行解码,这样%3F 将返回原来的内容,然后再对其进行编码。

    所以:

    String decoded = URLDecoder.decode(url, "UTF-8");
    url = URLEncoder.encode(decoded, "UTF-8");
    

    【讨论】:

    • 我遇到了一个问题,我应该处理一个编码字符串,其中 %3F 被错误地编码为 %253F(即 '=' 已编码为 %3F,然后再次编码为 %253F )。通过首先解码几次来“撤消”编码提供了一个很好的解决方法。所以这里的答案有帮助。
    【解决方案3】:

    对未编码的 URL 字符串进行编码的正确方法是通过 URI.toASCIIString()。

    当然,由您决定 URL 是否已经编码。

    【讨论】:

      【解决方案4】:

      您是否尝试过使用 URLEncoder?

          URLEncoder.encode(URLString, "UTF-8")
      

      除此之外,您唯一的选择是将每个用作参数的 URL 分别编码,然后手动构建 URL。这是一个非常棘手的案例。

      【讨论】:

      • URLEncoder 对 URL 编码没有任何用处,奇怪的是。它用于编码 URL 参数。
      • @EJP 没有“编码 URL”之类的东西,只有编码 URL 参数。正如我在回答中所说,一旦你有了一个 URL,你就不能对其进行编码——它要么已经编码,要么你已经错过了机会。您需要在构建 URL 之前对部分 URL 进行编码。 URLEncoder 非常适合编码唯一有用的编码。
      • @mgiuca 你又误会了。肯定有编码 URL 之类的东西。这就是 %20 的用途,例如:编码空格。请参阅 RFC 2396 和 java.net.URI 的 Javadoc。
      • @EJP“再次”?我向您保证,我非常熟悉 RFC 3986(它已淘汰 2396)(我在 Python 3 中编写了 urllib.parse.quote/unquote)。我不争辩 %20 用于对 URL 中的八位字节进行编码。我说没有编码 URL 这样的东西,只有 URL 参数。 RFC 从未提及对 URL 进行编码,仅对 octets 进行编码。它说“冲突的数据必须在在形成 URI 之前进行百分比编码”(强调我的)。 java.net.URI(String) 需要一个已经编码的 URI——只有多参数构造函数执行编码。
      • @mgiuca 那么您将使用 % 编码的十六进制字符串添加到 URL 以代替带外字符的过程的名称是什么?
      猜你喜欢
      • 1970-01-01
      • 2010-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 2016-04-28
      相关资源
      最近更新 更多