【问题标题】:Adding duplicate parameters with Retrofit v1.9.0使用 Retrofit v1.9.0 添加重复参数
【发布时间】:2022-02-19 00:31:19
【问题描述】:

here 也有类似的问题,但我的情况有点不同。

我正在尝试发出类似于以下内容的请求:

http://www.example.com/abc?foo=def&foo=ghi&foo=jkl&bar=xyz

我有两个让事情变得困难的问题。首先,重复的参数(多次设置“foo”的值)阻止使用QueryMap(我没有选项以不同的方式传递查询字符串中的值,比如作为数组)。其次,我使用的查询参数是动态的,所以我不能真正使用Query 并为其提供给定参数名称的值列表,因为在我制作之前我不会知道参数名称请求。

我尝试升级的代码使用的是旧版本的 Retrofit,但不知何故,它有一个 QueryList 的概念,它采用 NameValuePairs 的 List 来传递查询参数作为名称(并将它们的值作为值)并允许重复参数。我在 Retrofit 的源代码历史或网络上的任何地方都没有看到对 retrofit.http.QueryList 的引用,所以我不确定这是否是当时的自定义添加。无论如何,我正在努力寻找在最新版本中复制该功能的最佳方法,因此我们将不胜感激!

【问题讨论】:

  • 为什么要传递重复的参数?如果它是一个数组是可以理解的,但你的例子只是一个重复。不同的服务器可能对此有不同的响应,攻击者可以利用它来利用服务器。
  • 被称为“参数污染”:owasp.org/index.php/…
  • 同意,我不会这样设置它,每次看到它我都会畏缩。不幸的是,这就是我正在使用的服务器/API 的设置方式,我无法控制更改它,所以我必须使用我所拥有的......

标签: android retrofit


【解决方案1】:

为了跟进,我能够通过使用 QueryMap 和一些 hack 来解决这个问题。基本上,我将 NameValuePairs 的 List 转换为 HashMap,在其中检查我是否已经拥有密钥,如果有,则将同一密钥的新值附加到旧值。所以本质上 (key, value) 会变成 (key, value&key=value2)。这样,在构造查询字符串时,我将根据需要拥有 key=value&key=value2。为了让它工作,我需要自己处理值编码,这样我在值中包含的其他 & 和等号就不会被编码。

所以HashMap 是从List 构造的,如下所示:

public static HashMap<String, String> getPathMap(List<NameValuePair> params) {
    HashMap<String, String> paramMap = new HashMap<>();
    String paramValue;

    for (NameValuePair paramPair : params) {
        if (!TextUtils.isEmpty(paramPair.getName())) {
            try {
                if (paramMap.containsKey(paramPair.getName())) {
                    // Add the duplicate key and new value onto the previous value
                    // so (key, value) will now look like (key, value&key=value2)
                    // which is a hack to work with Retrofit's QueryMap
                    paramValue = paramMap.get(paramPair.getName());
                    paramValue += "&" + paramPair.getName() + "=" + URLEncoder.encode(String.valueOf(paramPair.getValue()), "UTF-8");
                } else {
                    // This is the first value, so directly map it
                    paramValue = URLEncoder.encode(String.valueOf(paramPair.getValue()), "UTF-8");
                }
                paramMap.put(paramPair.getName(), paramValue);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }

    return paramMap;
}

那么我的请求是这样的:

@GET("/api/")
List<Repo> listRepos(@QueryMap(encodeValues=false) Map<String, String> params);

我的服务调用如下所示:

// Get the list of params for the service call
ArrayList<NameValuePair> paramList = getParams();

// Convert the list into a map and make the call with it
Map<String, String> params = getPathMap(paramList);
List<Repo> repos = service.listRepos(params);

我最初尝试使用Path 的解决方案,我尝试手动构造查询字符串,但查询字符串中不允许替换块,所以我使用了这个 QueryMap 解决方案。希望这可以帮助遇到同样问题的其他人!

【讨论】:

  • 谢谢,您的解决方案让我省了很多麻烦!
【解决方案2】:

界面:

@GET()
Call<List<CustomDto>> getCustomData(@Url String url);

请求数据:

 String params = "custom?";
 for (Integer id : ids) {
      params += "id=" + id.toString() + "&";
 }
 params = params.substring(0, params.length() - 1);
 api.getCustomData(params).enqueue(callback);

【讨论】:

  • 这是对我有用的方法。我似乎无法得到公认的工作答案。
【解决方案3】:

Retrofit 1.9/2+ 中提供了一种添加重复参数的更简单方法

public interface AbcService {  
    @GET("/abc")
    Call<List<Abc>> getAbcData(@Query("foo") List<String> foos, @Query("bar") String bar);
}

foos 可以这样定义:

List<String> foos = Arrays.asList("def", "ghi", "jkl");

有关详细信息,您还可以查看link

【讨论】:

    【解决方案4】:

    只需将列表传递给您的@Query:

    @GET("/api")
    Response shareItem(@Query("email")List<String> email);
    

    【讨论】:

    • 我不能使用我在这里提到的列表:“第二,我使用的查询参数是动态的,所以我不能真正使用 Query 并为其提供值列表对于给定的参数名称,因为在我发出请求之前我不会知道参数名称。”但是,我通过我在此页面上发布的替代解决方案解决了它。
    猜你喜欢
    • 2017-01-03
    • 2014-03-20
    • 1970-01-01
    • 2014-06-30
    • 1970-01-01
    • 2015-11-25
    • 1970-01-01
    • 1970-01-01
    • 2014-04-12
    相关资源
    最近更新 更多