在 Java 中解码 URI 查询字符串

Posted

技术标签:

【中文标题】在 Java 中解码 URI 查询字符串【英文标题】:Decoding URI query string in Java 【发布时间】:2011-02-07 14:21:40 【问题描述】:

我需要解码一个包含查询字符串的 URI;预期的输入/输出行为类似于以下内容:

abstract class URIParser
       
    /** example input: 
      * something?alias=pos&FirstName=Foo+A%26B%3DC&LastName=Bar */
    URIParser(String input)  ... 
    /** should return "something" for the example input */
    public String getPath(); 
    /** should return a map 
      * alias: "pos", FirstName: "Foo+A&B=C", LastName: "Bar" */
    public Map<String,String> getQuery();

我尝试过使用java.net.URI,但它似乎可以解码查询字符串,所以在上面的示例中,我留下了“alias=pos&FirstName=Foo+A&B=C&LastName=Bar”,因此“是否”存在歧义&" 是查询分隔符或查询组件中的字符。

编辑:我刚刚尝试了URI.getRawQuery(),但它没有进行编码,所以我可以用&amp; 拆分查询字符串,但是我该怎么办? javascript有decodeURIComponent,Java中好像找不到对应的方法。

有什么建议吗?我不想使用任何新的库。

【问题讨论】:

既然你不想引入新的库,请问你是在什么环境下收到这些URI的? 【参考方案1】:

使用

URLDecoder.decode(proxyRequestParam.replace("+", "%2B"), "UTF-8")
          .replace("%2B", "+")

模拟decodeURIComponent。 Java 的URLDecoder 将加号解码为空格,这不是您想要的,因此您需要替换语句。

警告:末尾的.replace("%2B", "+")会损坏您的数据如果原始数据(pre-x-www-form-urlencoded ) 包含该字符串,正如@xehpuk 指出的那样。

【讨论】:

这应该是公认的答案。 URI 按原样处理 + 符号,而空格被编码为 %20。 URLDecoder 与 URI 编码字符串不兼容,因为它会将 + 和 %20 解码为空格。 第二次替换有什么意义?解码后,字符串中将不再有任何“%2B”实例,因为它们都将被替换为“+”,因此替换将没有任何内容。 重点是您不希望在解码字符串中包含编码字符。由于 Java 不会像 JavaScript 那样解码 + 符号,因此我首先对 + 符号进行编码,这样它就不会被 Java 触及,然后将 %2B 解码为 + 符号。简而言之:如果我不这样做,解码后的 URL 将不包含原始的 + 符号(因为 Java 在解码阶段会丢失它们)。 @janb - 我认为第二次替换是不必要的,因为decode 方法已经将它找到的任何%2B 转换为+。第一次替换是必要的,以阻止它将+ 转换为空格。 @StevePowell 第二次替换不仅没有必要,而且是错误的。【参考方案2】:

见课程URLDecoder

【讨论】:

应该注意的是,您应该在使用它之前识别查询部分并将参数拆分为键/值对,但它会将百分比编码的值解码为给定的编码(参见 UTF- 8) 根据 html application/x-www-form-urlencoded 规范。 总是把答案放在你的答案中。链接会产生额外的工作,并且不能保证链接将始终有效。【参考方案3】:
var reqParam =  URLDecoder.decode(reqParam, "UTF-8")

【讨论】:

【参考方案4】:

关于 + 号的问题:

我根据@janb 的回答做了一个封装了 URLDecoder 函数的辅助类

import android.net.Uri;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class DateDecoder 

    private static final String KEY_DATE = "datekey";

    private static final SimpleDateFormat SIMPLE_DATE_FORMAT =
            new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZZZZZ", Locale.US);


    public static void main(String[] args) throws UnsupportedEncodingException 
        try 
            Uri uri = Uri.parse("http://asdf.com?something=12345&" +
                    KEY_DATE +"=2016-12-24T12:00:00+01:00");

            System.out.println("parsed date: " + DateDecoder.createDate(uri)); // parsed date: Sat Dec 24 12:00:00 GMT+01:00 2016
         catch (Exception e) 
            e.printStackTrace();
        
    

    @Nullable
    public static Date createDate(@Nullable Uri data) 
        if (data != null) 
            try 
                String withPlus = decodeButKeepPlus(KEY_DATE, data.getEncodedQuery());
                if (!TextUtils.isEmpty(withPlus)) 
                    return SIMPLE_DATE_FORMAT.parse(withPlus);
                
             catch (Exception e) 
                e.printStackTrace();
            
        
        return null;
    

    /**
     * copied from android.net.Uri.java
     */
    @Nullable
    public static String decodeButKeepPlus(String encodedKey, String completeEncodedQuery)
            throws UnsupportedEncodingException 

        final int length = completeEncodedQuery.length();
        int start = 0;
        do 
            int nextAmpersand = completeEncodedQuery.indexOf('&', start);
            int end = nextAmpersand != -1 ? nextAmpersand : length;

            int separator = completeEncodedQuery.indexOf('=', start);
            if (separator > end || separator == -1) 
                separator = end;
            

            if (separator - start == encodedKey.length()
                    && completeEncodedQuery.regionMatches(start, encodedKey, 0, encodedKey.length())) 
                if (separator == end) 
                    return "";
                 else 
                    String encodedValue = completeEncodedQuery.substring(separator + 1, end);
                    if (!TextUtils.isEmpty(encodedValue)) 
                        return URLDecoder.decode(encodedValue.replace("+", "%2B"), "UTF-8").replace("%2B", "+");
                    
                
            

            // Move start to end of name.
            if (nextAmpersand != -1) 
                start = nextAmpersand + 1;
             else 
                break;
            
         while (true);
        return null;
    


【讨论】:

【参考方案5】:
new java.net.URI(proxyRequestParam).getPath()

js encodeURIComponent编码的字符串应该只是一个路径,没有schema等。但是它仍然是 java.net.URI 的有效输入。所以 java.net.URI 会为我们做所有事情,然后它的路径就是我们想要的。

【讨论】:

虽然此代码可能会解决问题,including an explanation 关于如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。

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

JS中编码和解码的总结

URI解码在MultipartConfig文件中不起作用

在 Java 中编写 URL 或 URI 的惯用方式是啥?

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

JavaScript URI 编码与解码

JavaScript中的编码解码