16

如果我将http://localhost:9000/space testURL 放到 Web 浏览器的地址栏,它会使用http://localhost:9000/space%20test. http://localhost:9000/specÁÉÍtest也将被编码为http://localhost:9000/spec%C3%81%C3%89%C3%8Dtest.

如果将编码的 URL 放到地址栏(即http://localhost:9000/space%20testhttp://localhost:9000/spec%C3%81%C3%89%C3%8Dtest),它们保持不变(它们不会被双重编码)。

是否有任何 Java API 或库可以进行这种编码?URL 来自用户,所以我不知道它们是否经过编码。

(如果没有在输入字符串中搜索并%在未找到时进行编码就足够了,或者是否有任何特殊情况不起作用?)

编辑:

URLEncoder.encode("space%20test", "UTF-8")返回space%2520testwhich 不是我想要的,因为它是双重编码的。

编辑2:

此外,浏览器可以处理部分编码的 URL,例如http://localhost:9000/specÁÉ%C3%8Dtest,好吧,而不需要对它们进行双重编码。在这种情况下,服务器会收到以下 URL:http://localhost:9000/spec%C3%81%C3%89%C3%8Dtest。它与 的编码形式相同...specÁÉÍtest

4

5 回答 5

12

每个 Web 开发人员必须了解的有关 URL 编码的知识

网址编码解释

为什么需要 URL 编码?

The URL specification RFC 1738 specifies that only a small set of characters 
can be used in a URL. Those characters are:

A to Z (ABCDEFGHIJKLMNOPQRSTUVWXYZ)
a to z (abcdefghijklmnopqrstuvwxyz)
0 to 9 (0123456789)
$ (Dollar Sign)
- (Hyphen / Dash)
_ (Underscore)
. (Period)
+ (Plus sign)
! (Exclamation / Bang)
* (Asterisk / Star)
' (Single Quote)
( (Open Bracket)
) (Closing Bracket)

URL 编码是如何工作的?

All offending characters are replaced by a % and a two digit hexadecimal value 
that represents the character in the proper ISO character set. Here are a 
couple of examples:

$ (Dollar Sign) becomes %24
& (Ampersand) becomes %26
+ (Plus) becomes %2B
, (Comma) becomes %2C
: (Colon) becomes %3A
; (Semi-Colon) becomes %3B
= (Equals) becomes %3D
? (Question Mark) becomes %3F
@ (Commercial A / At) becomes %40

简单示例:

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class TextHelper {
    private static ScriptEngine engine = new ScriptEngineManager()
        .getEngineByName("JavaScript");

/**
 * Encoding if need escaping %$&+,/:;=?@<>#%
 *
 * @param str should be encoded
 * @return encoded Result 
 */
public static String escapeJavascript(String str) {
    try {
        return engine.eval(String.format("escape(\"%s\")", 
            str.replaceAll("%20", " "))).toString()
                .replaceAll("%3A", ":")
                .replaceAll("%2F", "/")
                .replaceAll("%3B", ";")
                .replaceAll("%40", "@")
                .replaceAll("%3C", "<")
                .replaceAll("%3E", ">")
                .replaceAll("%3D", "=")
                .replaceAll("%26", "&")
                .replaceAll("%25", "%")
                .replaceAll("%24", "$")
                .replaceAll("%23", "#")
                .replaceAll("%2B", "+")
                .replaceAll("%2C", ",")
                .replaceAll("%3F", "?");
    } catch (ScriptException ex) {
        Logger.getLogger(TextHelper.class.getName())
            .log(Level.SEVERE, null, ex);
        return null;
    }
}
于 2013-08-21T16:28:46.567 回答
8

使用 java java.net.URLEncoder#encode()

String page = "space test";
String ecodedURL = "http://localhost:9000/" + URLEncoder.encode(page, "UTF-8");

注意:编码完整的 URL 会导致不希望的情况,例如http://编码为http%3A%2F%2F!

编辑:为了防止对 URL 进行两次编码,您可以检查 URL 是否包含 a%因为它仅对编码有效。但是,如果用户错误地弄乱了编码(例如,仅对 URL 进行部分编码或%在 URL 中使用 a 而未将其用于编码某些内容),那么使用此方法就没什么可做的了……

于 2013-01-16T12:03:53.900 回答
4

最后,我检查了 Firefox 和 Chrome 的功能。我在两个浏览器中都使用了以下 URL,并使用 netcat ( nc -l -p 9000) 捕获 HTTP 请求:

http://localhost:9000/!"$%&'()*+,-./:;<=>?@[\]^_`{|}~

此 URL 包含从 ASCII 32 到 127 的每个字符,除了[0-9A-Za-z#].

使用 Firefox 18.0.1 捕获的请求如下:

GET /!%22$%&%27()*+,-./:;%3C=%3E?@[\]^_%60{|}~%7F HTTP/1.1

使用铬:

GET /!%22$%&'()*+,-./:;%3C=%3E?@[\]^_`{|}~%7F HTTP/1.1

Firefox 编码的字符比 Chrome 多。这是在一个表中:

Char | Hex    | Dec     | Encoded by
-----------------------------------------
"    | %22    | 34      | Firefox, Chrome
'    | %27    | 39      | Firefox
<    | %3C    | 60      | Firefox, Chrome
>    | %3E    | 62      | Firefox, Chrome
`    | %60    | 96      | Firefox
     | %7F    | 127     | Firefox, Chrome

我在他们的源代码树中发现了一些类似的代码,但我不太确定这些是否是实际使用的算法:

无论如何,这是 Java 中的概念验证代码:

// does not handle "#"
public static String encode(final String input) {
    final StringBuilder result = new StringBuilder();
    for (final char c: input.toCharArray()) {
        if (shouldEncode(c)) {
            result.append(encodeChar(c));
        } else {
            result.append(c);
        }
    }
    return result.toString();
}

private static String encodeChar(final char c) {
    if (c == ' ') {
        return "%20"; // URLEncode.encode returns "+"
    }
    try {
        return URLEncoder.encode(String.valueOf(c), "UTF-8");
    } catch (final UnsupportedEncodingException e) {
        throw new IllegalStateException(e);
    }
}

private static boolean shouldEncode(final char c) {
    if (c <= 32 || c >= 127) {
        return true;
    }
    if (c == '"' || c == '<' || c == '>') {
        return true;
    }
    return false;
}

由于它使用URLEncoder.encode,它处理ÁÉÍ字符以及 ASCII 字符。

于 2013-11-03T15:53:11.647 回答
2

这是一个 Scala 代码片段。此编码器将对 URL 中的非 ascii 字符和保留字符进行编码。此外,由于操作是幂等的,因此 URL 不会被双重编码。

import java.net.URL
import scala.util.parsing.combinator.RegexParsers

object IdempotentURLEncoder extends RegexParsers {
  override def skipWhitespace = false
  private def segment = rep(char)
  private def char = unreserved | escape | any ^^ { java.net.URLEncoder.encode(_, "UTF-8") }
  private def unreserved = """[A-Za-z0-9._~!$&'()*+,;=:@-]""".r
  private def escape = """%[A-Fa-f0-9]{2}""".r
  private def any = """.""".r
  private def encodeSegment(input: String): String = parseAll(segment, input).get.mkString
  private def encodeSearch(input: String): String = encodeSegment(input)
  def encode(url: String): String = {
    val u = new URL(url)
    val path = u.getPath.split("/").map(encodeSegment).mkString("/")
    val query = u.getQuery match {
      case null      => ""
      case q: String => "?" + encodeSearch(q)
    }
    val hash = u.getRef match {
      case null      => ""
      case h: String => "#" + encodeSegment(h)
    }
    s"${u.getProtocol}://${u.getAuthority}$path$query$hash"
  }
}

示例用法(测试代码)

import org.scalatest.{ FunSuite, Matchers }

class IdempotentURLEncoderSpec extends FunSuite with Matchers {
  import IdempotentURLEncoder._

  test("Idempotent operation") {
    val url = "http://ja.wikipedia.org/wiki/文字"
    assert(encode(url) == encode(encode(url)))
    assert(encode(url) == encode(encode(encode(url))))
  }

  test("Segment encoding") {
    encode("http://ja.wikipedia.org/wiki/文字")
      .shouldBe("http://ja.wikipedia.org/wiki/%E6%96%87%E5%AD%97")
  }

  test("Query string encoding") {
    encode("http://qiita.com/search?utf8=✓&amp;sort=rel&q=開発&sort=rel")
      .shouldBe("http://qiita.com/search?utf8=%E2%9C%93&sort=rel&q=%E9%96%8B%E7%99%BA&sort=rel")
  }

  test("Hash encoding") {
    encode("https://www.google.co.jp/#q=文字")
      .shouldBe("https://www.google.co.jp/#q=文字")
  }

  test("Partial encoding") {
    encode("http://en.wiktionary.org/wiki/français")
      .shouldBe("http://en.wiktionary.org/wiki/fran%C3%A7ais")
  }

  test("Space is encoded as +") {
    encode("http://example.com/foo bar buz")
      .shouldBe("http://example.com/foo+bar+buz")
  }

  test("Multibyte domain names are not supported yet :(") {
    encode("http://日本語.jp")
      .shouldBe("http://日本語.jp")
  }
}

此代码来自Qiita

于 2014-12-25T03:00:20.907 回答
-1

标准的 Java api 会自己进行 URL 编码和解码。

java.net.URI

尝试课程URLDecoderURLEncoder

对文本进行编码以安全通过互联网:

import java.net.*;
...
try {
    encodedValue= URLEncoder.encode(rawValue, "UTF-8");
} catch (UnsupportedEncodingException uee) { }

并解码:

try {
    decodedValue = URLDecoder.decode(rawValue, "UTF-8");
} catch (UnsupportedEncodingException uee) { }
于 2013-01-16T12:03:28.580 回答