HTTP URL地址编码在Java

我的Java独立应用程序从用户那里获得一个URL(指向一个文件),我需要点击它并下载它。我面临的问题是,我不能正确编码HTTP URL地址…

例子:

URL:  http://search.barnesandnoble.com/booksearch/first book.pdf


java.net.URLEncoder.encode(url.toString(), "ISO-8859-1");

回报我。

http%3A%2F%2Fsearch.barnesandnoble.com%2Fbooksearch%2Ffirst+book.pdf

但是,我想要的是

http://search.barnesandnoble.com/booksearch/first%20book.pdf

(空格替换为%20)

我猜URLEncoder不是用来编码HTTP url的…JavaDoc说“HTML表单编码的实用程序类”…还有别的办法吗?

407860 次浏览

URL编码会对那个字符串进行编码这样它就能在URL中正确地传递到最终目的地。例如,你不能有http://stackoverflow.com?url=http://yyy.com。UrlEncoding参数将修复该参数值。

所以我给你两个选择:

  1. 您是否有权访问与域分离的路径?如果是这样,您可以简单地对路径进行UrlEncode。然而,如果情况并非如此,那么选择2可能适合你。

  2. < p > commons - httpclient 3.1。它有一个类URIUtil:

    System.out.println (URIUtil。encodePath("http://example.com/x y", "ISO-8859-1"));

这将输出您正在寻找的内容,因为它只对URI的路径部分进行编码。

供您参考,这个方法需要common -codec和common -logging才能在运行时工作。

URLEncoding可以很好地编码HTTP url,正如您不幸发现的那样。你传入的字符串“http://search.barnesandnoble.com/booksearch/first book.pdf”被正确且完整地编码为url编码的形式。你可以把你得到的整个冗长的字符串作为URL的参数传递回去,它可以被解码成你传递进去的字符串。

听起来,您想要做一些与将整个URL作为参数传递不同的事情。据我所知,你正在尝试创建一个看起来像“http://search.barnesandnoble.com/booksearch/whateverTheUserPassesIn”的搜索URL。你唯一需要编码的是“whateverTheUserPassesIn”位,所以也许你所需要做的就是这样:

String url = "http://search.barnesandnoble.com/booksearch/" +
URLEncoder.encode(userInput,"UTF-8");

这应该会产生一些对你更有效的东西。

java.net.URI类可以提供帮助;你可以在URL的文档中找到

注意,URI类在某些情况下确实执行组件字段的转义。建议使用URI来管理url的编码和解码

使用一个具有多个参数的构造函数,例如:

URI uri = new URI(
"http",
"search.barnesandnoble.com",
"/booksearch/first book.pdf",
null);
URL url = uri.toURL();
//or String request = uri.toString();

(URI的单参数构造函数不转义非法字符)


以上代码只转义非法字符-它不会转义非ascii字符(参见fatih的评论) toASCIIString方法可用于获取仅包含US-ASCII字符的String:

URI uri = new URI(
"http",
"search.barnesandnoble.com",
"/booksearch/é",
null);
String request = uri.toASCIIString();

对于带有http://www.google.com/ig/api?weather=São Paulo这样查询的URL,使用构造函数的5个参数版本:

URI uri = new URI(
"http",
"www.google.com",
"/ig/api",
"weather=São Paulo",
null);
String request = uri.toASCIIString();

请注意,上面的大部分答案都是不正确的。

URLEncoder类,不管它的名字,不是这里需要的。不幸的是,Sun给这个类命名得如此烦人。URLEncoder用于作为参数传递数据,而不是用于编码URL本身。

换句话说,"http://search.barnesandnoble.com/booksearch/first book.pdf"是URL。参数可以是,例如,"http://search.barnesandnoble.com/booksearch/first book.pdf?parameter1=this&param2=that"。参数就是你使用URLEncoder的目的。

下面两个例子强调了两者之间的区别。

根据HTTP标准,下面会产生错误的参数。注意&号(&)和加号(+)编码错误。

uri = new URI("http", null, "www.google.com", 80,
"/help/me/book name+me/", "MY CRZY QUERY! +&+ :)", null);


// URI: http://www.google.com:80/help/me/book%20name+me/?MY%20CRZY%20QUERY!%20+&+%20:)

下面的代码将生成正确的参数,并对查询进行正确编码。注意空格、&号和加号。

uri = new URI("http", null, "www.google.com", 80, "/help/me/book name+me/", URLEncoder.encode("MY CRZY QUERY! +&+ :)", "UTF-8"), null);


// URI: http://www.google.com:80/help/me/book%20name+me/?MY+CRZY+QUERY%2521+%252B%2526%252B+%253A%2529

如果你的URL中有一个编码的“/”(%2F),这仍然是一个问题。

RFC 3986 -章节2.2说:“如果URI组件的数据与保留字符作为分隔符的目的相冲突,那么冲突的数据必须在URI形成之前进行百分比编码。”(rfc3986 -第2.2节)

但是Tomcat有一个问题:

http://tomcat.apache.org/security-6.html -在Apache Tomcat 6.0.10中修复

重要:目录遍历CVE-2007-0450

Tomcat允许“\”,“%2F”和“%5C” […]。< / p >

Java系统属性 已添加到Tomcat提供 处理的附加控制 url中的路径分隔符(这两个选项 默认为false):

    <李> org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH: 李真|假< / > <李> org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH: 李真|假< / >

由于无法保证 所有url都被Tomcat处理为 它们在代理服务器、Tomcat中 应该总是安全的,如果没有 代理限制上下文访问 使用。< / p >

影响:6.0.0-6.0.9

因此,如果您有一个含有%2F字符的URL, Tomcat将返回:"400 Invalid URI: noSlash"

你可以在Tomcat启动脚本中切换bug修复:

set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%   -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true

我开发了一个比其他解决方案更稳定的解决方案:

public class URLParamEncoder {


public static String encode(String input) {
StringBuilder resultStr = new StringBuilder();
for (char ch : input.toCharArray()) {
if (isUnsafe(ch)) {
resultStr.append('%');
resultStr.append(toHex(ch / 16));
resultStr.append(toHex(ch % 16));
} else {
resultStr.append(ch);
}
}
return resultStr.toString();
}


private static char toHex(int ch) {
return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10);
}


private static boolean isUnsafe(char ch) {
if (ch > 128 || ch < 0)
return true;
return " %$&+,/:;=?@<>#%".indexOf(ch) >= 0;
}


}

我创建了一个新项目来帮助构建HTTP url。库将自动URL编码路径段和查询参数。

你可以在https://github.com/Widen/urlbuilder处查看源代码并下载二进制文件

这个问题中的URL示例:

new UrlBuilder("search.barnesandnoble.com", "booksearch/first book.pdf").toString()

生产

http://search.barnesandnoble.com/booksearch/first%20book.pdf

我同意马特的观点。事实上,我从未在教程中看到过很好的解释,但一个问题是如何编码URL路径,另一个非常不同的问题是如何编码附加到URL的参数(“?”符号后面的查询部分)。它们使用类似的编码,但并不相同。

专门用于空白字符的编码。URL路径需要编码为%20,而查询部分允许使用%20和“+”符号。最好的方法是使用Web浏览器对我们的Web服务器进行测试。

对于这两种情况,我总是将编码一个组件接一个组件,而不是整个字符串。实际上URLEncoder允许查询部分这样做。对于路径部分,您可以使用类URI,尽管在本例中它要求整个字符串,而不是单个组件。

无论如何,我相信避免这些问题的最好方法是使用个人无冲突的设计。如何?例如,我从来不使用a-Z, a-Z, 0-9和_以外的字符命名目录或参数。这样,唯一需要做的就是对每个参数的值进行编码,因为它可能来自用户输入,使用的字符是未知的。

不幸的是,org.apache.commons.httpclient.util.URIUtil已弃用,并且replacement org.apache.commons.codec.net.URLCodec编码适用于表单帖子,而不适用于实际的URL。所以我必须写我自己的函数,它只做一个组件(不适合有?'s和&'s的整个查询字符串)

public static String encodeURLComponent(final String s)
{
if (s == null)
{
return "";
}


final StringBuilder sb = new StringBuilder();


try
{
for (int i = 0; i < s.length(); i++)
{
final char c = s.charAt(i);


if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ||
((c >= '0') && (c <= '9')) ||
(c == '-') ||  (c == '.')  || (c == '_') || (c == '~'))
{
sb.append(c);
}
else
{
final byte[] bytes = ("" + c).getBytes("UTF-8");


for (byte b : bytes)
{
sb.append('%');


int upper = (((int) b) >> 4) & 0xf;
sb.append(Integer.toHexString(upper).toUpperCase(Locale.US));


int lower = ((int) b) & 0xf;
sb.append(Integer.toHexString(lower).toUpperCase(Locale.US));
}
}
}


return sb.toString();
}
catch (UnsupportedEncodingException uee)
{
throw new RuntimeException("UTF-8 unsupported!?", uee);
}
}

除了Carlos Heuberger的回复: 如果需要不同于默认值(80),则应该使用7参数构造函数:

URI uri = new URI(
"http",
null, // this is for userInfo
"www.google.com",
8080, // port number as int
"/ig/api",
"weather=São Paulo",
null);
String request = uri.toASCIIString();

在此我将针对Android用户添加一条建议。您可以这样做,从而避免获得任何外部库。此外,上面一些答案中建议的所有搜索/替换字符解决方案都是危险的,应该避免。

试一试:

String urlStr = "http://abc.dev.domain.com/0007AC/ads/800x480 15sec h.264.mp4";
URL url = new URL(urlStr);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
url = uri.toURL();

您可以看到,在这个特定的URL中,我需要对这些空格进行编码,以便我可以将其用于请求。

这利用了Android类中提供给你的几个功能。首先,URL类可以将URL分解为适当的组件,因此不需要进行任何字符串搜索/替换工作。其次,当您通过组件而不是从单个字符串构造URI时,这种方法利用了正确转义组件的URI类特性。

这种方法的美妙之处在于,您可以使用任何有效的url字符串并让它工作,而不需要您自己对它有任何特殊的了解。

如果你有一个URL,你可以将URL . tostring()传递给这个方法。首先解码,以避免双重编码(例如,编码空格会得到%20,编码百分号会得到%25,因此双重编码将把空格变成%2520)。然后,像上面解释的那样使用URI,添加URL的所有部分(这样就不会删除查询参数)。

public URL convertToURLEscapingIllegalCharacters(String string){
try {
String decodedURL = URLDecoder.decode(string, "UTF-8");
URL url = new URL(decodedURL);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
return uri.toURL();
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}

如何:

UrlEncode(String in_) {

String retVal = "";


try {
retVal = URLEncoder.encode(in_, "UTF8");
} catch (UnsupportedEncodingException ex) {
Log.get().exception(Log.Level.Error, "urlEncode ", ex);
}


return retVal;

也许可以试试org.springframework.web.util中的UriUtils

UriUtils.encodeUri(input, "UTF-8")

我开发了一个用于此目的的库:胡言乱语。它解析URL的方式与web浏览器相同。也就是说,如果一个URL在浏览器中工作,它将被胡言乱语正确解析。

在这种情况下:

// Parse
io.mola.galimatias.URL.parse(
"http://search.barnesandnoble.com/booksearch/first book.pdf"
).toString()

会给你:http://search.barnesandnoble.com/booksearch/first%20book.pdf。当然,这是最简单的情况,但它可以用于任何东西,远远超出java.net.URI

你可以在https://github.com/smola/galimatias查看它

你可以使用这样的函数。根据您的需要完成并修改:

/**
* Encode URL (except :, /, ?, &, =, ... characters)
* @param url to encode
* @param encodingCharset url encoding charset
* @return encoded URL
* @throws UnsupportedEncodingException
*/
public static String encodeUrl (String url, String encodingCharset) throws UnsupportedEncodingException{
return new URLCodec().encode(url, encodingCharset).replace("%3A", ":").replace("%2F", "/").replace("%3F", "?").replace("%3D", "=").replace("%26", "&");
}

使用示例:

String urlToEncode = ""http://www.growup.com/folder/intérieur-à_vendre?o=4";
Utils.encodeUrl (urlToEncode , "UTF-8")

结果是:http://www.growup.com/folder/int%C3%A9rieur-%C3%A0_vendre?o=4

我阅读了以前的答案,写我自己的方法,因为我不能有一些正确的工作使用以前的答案的解决方案,它看起来对我很好,但如果你能找到不与此工作的URL,请让我知道。

public static URL convertToURLEscapingIllegalCharacters(String toEscape) throws MalformedURLException, URISyntaxException {
URL url = new URL(toEscape);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
//if a % is included in the toEscape string, it will be re-encoded to %25 and we don't want re-encoding, just encoding
return new URL(uri.toString().replace("%25", "%"));
}
你也可以使用GUAVA和路径escaper: UrlEscapers.urlFragmentEscaper().escape(relativePath) < / p >

我也有同样的问题。通过unsing解决了这个问题:

android.net.Uri.encode(urlString, ":/");

它对字符串进行编码,但跳过“:”和“/”。

如果任何人不想向他们的项目添加依赖项,这些函数可能会有帮助。

我们将URL的path部分传递到这里。您可能不想将完整的URL作为参数传递进来(查询字符串需要不同的转义,等等)。

/**
* Percent-encodes a string so it's suitable for use in a URL Path (not a query string / form encode, which uses + for spaces, etc)
*/
public static String percentEncode(String encodeMe) {
if (encodeMe == null) {
return "";
}
String encoded = encodeMe.replace("%", "%25");
encoded = encoded.replace(" ", "%20");
encoded = encoded.replace("!", "%21");
encoded = encoded.replace("#", "%23");
encoded = encoded.replace("$", "%24");
encoded = encoded.replace("&", "%26");
encoded = encoded.replace("'", "%27");
encoded = encoded.replace("(", "%28");
encoded = encoded.replace(")", "%29");
encoded = encoded.replace("*", "%2A");
encoded = encoded.replace("+", "%2B");
encoded = encoded.replace(",", "%2C");
encoded = encoded.replace("/", "%2F");
encoded = encoded.replace(":", "%3A");
encoded = encoded.replace(";", "%3B");
encoded = encoded.replace("=", "%3D");
encoded = encoded.replace("?", "%3F");
encoded = encoded.replace("@", "%40");
encoded = encoded.replace("[", "%5B");
encoded = encoded.replace("]", "%5D");
return encoded;
}


/**
* Percent-decodes a string, such as used in a URL Path (not a query string / form encode, which uses + for spaces, etc)
*/
public static String percentDecode(String encodeMe) {
if (encodeMe == null) {
return "";
}
String decoded = encodeMe.replace("%21", "!");
decoded = decoded.replace("%20", " ");
decoded = decoded.replace("%23", "#");
decoded = decoded.replace("%24", "$");
decoded = decoded.replace("%26", "&");
decoded = decoded.replace("%27", "'");
decoded = decoded.replace("%28", "(");
decoded = decoded.replace("%29", ")");
decoded = decoded.replace("%2A", "*");
decoded = decoded.replace("%2B", "+");
decoded = decoded.replace("%2C", ",");
decoded = decoded.replace("%2F", "/");
decoded = decoded.replace("%3A", ":");
decoded = decoded.replace("%3B", ";");
decoded = decoded.replace("%3D", "=");
decoded = decoded.replace("%3F", "?");
decoded = decoded.replace("%40", "@");
decoded = decoded.replace("%5B", "[");
decoded = decoded.replace("%5D", "]");
decoded = decoded.replace("%25", "%");
return decoded;
}

和测试:

@Test
public void testPercentEncode_Decode() {
assertEquals("", percentDecode(percentEncode(null)));
assertEquals("", percentDecode(percentEncode("")));


assertEquals("!", percentDecode(percentEncode("!")));
assertEquals("#", percentDecode(percentEncode("#")));
assertEquals("$", percentDecode(percentEncode("$")));
assertEquals("@", percentDecode(percentEncode("@")));
assertEquals("&", percentDecode(percentEncode("&")));
assertEquals("'", percentDecode(percentEncode("'")));
assertEquals("(", percentDecode(percentEncode("(")));
assertEquals(")", percentDecode(percentEncode(")")));
assertEquals("*", percentDecode(percentEncode("*")));
assertEquals("+", percentDecode(percentEncode("+")));
assertEquals(",", percentDecode(percentEncode(",")));
assertEquals("/", percentDecode(percentEncode("/")));
assertEquals(":", percentDecode(percentEncode(":")));
assertEquals(";", percentDecode(percentEncode(";")));


assertEquals("=", percentDecode(percentEncode("=")));
assertEquals("?", percentDecode(percentEncode("?")));
assertEquals("@", percentDecode(percentEncode("@")));
assertEquals("[", percentDecode(percentEncode("[")));
assertEquals("]", percentDecode(percentEncode("]")));
assertEquals(" ", percentDecode(percentEncode(" ")));


// Get a little complex
assertEquals("[]]", percentDecode(percentEncode("[]]")));
assertEquals("a=d%*", percentDecode(percentEncode("a=d%*")));
assertEquals(")  (", percentDecode(percentEncode(")  (")));
assertEquals("%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25",
percentEncode("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %"));
assertEquals("! * ' ( % ) ; : @ & = + $ , / ? # [ ] %", percentDecode(
"%21%20%2A%20%27%20%28%20%25%20%29%20%3B%20%3A%20%40%20%26%20%3D%20%2B%20%24%20%2C%20%2F%20%3F%20%23%20%5B%20%5D%20%25"));


assertEquals("%23456", percentDecode(percentEncode("%23456")));


}

使用以下标准Java解决方案(通过Web平台测试提供的大约100个测试用例):

0. 测试URL是否已经编码

1.将URL拆分为结构部分。使用java.net.URL

2.编码每个结构部分正确!

3.使用IDN.toASCII(putDomainNameHere)Punycode编码主机名!

4.使用java.net.URI.toASCIIString()进行百分比编码,NFC编码的unicode -(更好的是NFKC!)

在这里找到更多:https://stackoverflow.com/a/49796882/1485527

我把上面的内容做了一些改变。我首先喜欢正逻辑,并且我认为HashSet可能比其他选项(比如通过String进行搜索)提供更好的性能。虽然,我不确定自动装箱的代价是否值得,但如果编译器针对ASCII字符进行了优化,那么装箱的代价就会很低。

/***
* Replaces any character not specifically unreserved to an equivalent
* percent sequence.
* @param s
* @return
*/
public static String encodeURIcomponent(String s)
{
StringBuilder o = new StringBuilder();
for (char ch : s.toCharArray()) {
if (isSafe(ch)) {
o.append(ch);
}
else {
o.append('%');
o.append(toHex(ch / 16));
o.append(toHex(ch % 16));
}
}
return o.toString();
}


private static char toHex(int ch)
{
return (char)(ch < 10 ? '0' + ch : 'A' + ch - 10);
}


// https://tools.ietf.org/html/rfc3986#section-2.3
public static final HashSet<Character> UnreservedChars = new HashSet<Character>(Arrays.asList(
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z',
'0','1','2','3','4','5','6','7','8','9',
'-','_','.','~'));
public static boolean isSafe(char ch)
{
return UnreservedChars.contains(ch);
}

我用这个

org.apache.commons.text.StringEscapeUtils.escapeHtml4("my text % & < >");

添加这个依赖项

 <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.8</version>
</dependency>

如果你正在使用spring,你可以试试 org.springframework.web.util.UriUtils#encodePath < / p >