Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Retrofit cannot encode properly query string with square brackets?

I'm using Retrofit for my network layer in my Android app, but I have a problem with the URL encoding.

I have to call an REST API like that:

https://my_hostname.com/some_path?q=some_query&param[0]=value1&param[1]=value2&other_param=abcd

as you can see the query string is composed by some different kind of parameters, so I decided to use the @QueryMap annotation in the Retrofit Interface with a Map<String, String> where q, param[1], param[0], other_param are String keys of the map

What do I expect? I expect the square brackets in the URL are encoded with %5B for '[' and %5D for '[', but this does not happen.

Why does this happen? The square brackets should be encoded with percent encoding. Is this a bug or I'm doing something wrong? I also tried the @EncodedQueryMap annotation with no differences.

like image 638
Noodles Avatar asked Sep 30 '14 20:09

Noodles


People also ask

Are square brackets allowed in URLs?

A host identified by an Internet Protocol literal address, version 6 [RFC3513] or later, is distinguished by enclosing the IP literal within square brackets ("[" and "]"). This is the only place where square bracket characters are allowed in the URI syntax.

What is encoding in query string?

encode() method is used to produce a URL query string from the given object that contains the key-value pairs. The method iterates through the object's own properties to generate the query string. It can serialize a single or an array of strings, numbers, and boolean.

How do I add a query parameter to retrofit?

Adding query parameters to single requests is straight forward. You're using the @Query annotation for your parameter declaration within the interface methods. This tells Retrofit to translate the provided query parameter name and value to the request and append the fields to the url.

Do query parameters need to be encoded?

Why do we need to encode? URLs can only have certain characters from the standard 128 character ASCII set. Reserved characters that do not belong to this set must be encoded. This means that we need to encode these characters when passing them into a URL.


2 Answers

Query names are never URL encoded.

The documentation for @QueryMap states:

Values are URL encoded.

And for @EncodedQueryMap:

Values are not URL encoded.

However, I just submitted a pull request to change this behavior a bit. I am adding support for encoding keys by using @Query(value = "..", encodeName = true) or @QueryMap(encodeNames = true).

like image 161
Jake Wharton Avatar answered Sep 28 '22 03:09

Jake Wharton


I just had to deal with @QueryMap encoding everything but {}[]. Most of the time I don't mind because I rarely send json in the query, and want to push the encoding down as low as possible in my application's stack, but recently I had to add an endpoint just like Ivan describes. My solution was to add an interceptor that does this:

Request originalRequest = chain.request();
HttpUrl url = originalRequest.url();
String urlFilePath = url.encodedPath();
if(url.encodedQuery() != null) {
    // Because Retrofit doesn't think "{}[]" should be encoded
    // but our API does
    String correctlyEncodedQuery = correctlyEncodeQuery(url);
    urlFilePath += "?" + correctlyEncodedQuery;
}
URL correctUrl = new URL(url.scheme(), url.host(), url.port(),
        urlFilePath);
Request newRequest = originalRequest.newBuilder()
        .url(correctUrl)


private String correctlyEncodeQuery(HttpUrl url) throws UnsupportedEncodingException {
    String retVal = "";
    for(String queryKey : url.queryParameterNames()){
        if(retVal.length() > 0){
            retVal += "&";
        }
        String queryValue = url.queryParameter(queryKey);
        retVal += queryKey + "=" + URLEncoder.encode(queryValue, "utf-8");
    }
    return retVal;
}
like image 43
etherton Avatar answered Sep 28 '22 05:09

etherton