Ganesh Gautam created CODEC-345:
-----------------------------------

             Summary: URLCodec.encodeUrl(BitSet, byte[]) ignores the 
caller-supplied BitSet for % and +
                 Key: CODEC-345
                 URL: https://issues.apache.org/jira/browse/CODEC-345
             Project: Commons Codec
          Issue Type: Bug
    Affects Versions: 1.22.1
            Reporter: Ganesh Gautam


{{URLCodec.encodeUrl(BitSet urlsafe, byte[] bytes)}} is public API and is 
widely used as a general-purpose percent-encoder, with the caller passing the 
character set appropriate to the URI component being encoded. The javadoc 
describes the parameter as {_}"bitset of characters deemed URL safe"{_}.

CODEC-339 changed the method to percent-escape {{%}} and {{+}} unconditionally, 
even when the caller has explicitly marked them safe:
{code:java}
-            if (urlsafe.get(b)) {
+            if (urlsafe.get(b) && b != ESCAPE_CHAR && b != PLUS_CHAR) {
{code}
 
For callers that use the {{www-form-urlencoded}} default set this is a no-op. 
For callers that supply their own set, which is the only reason the overload 
exists, it now silently changes the output:
 * a legal literal {{+}} in a URI path or query becomes {{{}%2B{}}}, changing 
the meaning of the URI;
 * an already-percent-encoded input is {*}double-encoded{*}, because {{%}} 
becomes {{{}%25{}}}. Encoders of this shape are routinely applied to whole URLs 
that may already contain valid escapes, so {{Caf%C3%A9}} becomes 
{{{}Caf%25C3%25A9{}}}.

The same reasoning applies to {{{}PercentCodec{}}}, where the {{plusForSpace}} 
constructor now adds {{+}} to {{{}alwaysEncodeChars{}}}, so a literal {{+}} in 
the input is no longer round-trippable.

We appreciate that the intent was to make {{encodeUrl}} or {{decodeUrl}} 
round-trip safely. Our point is narrower: that guarantee is only meaningful for 
the codec's _own_ {{WWW_FORM_URL}} alphabet, and enforcing it inside the BitSet 
overload removes the caller's ability to encode a URI component correctly.
h3. Reproduction

Self-contained and no dependency beyond commons-codec itself. Full source:
{code:java}
import java.util.BitSet;
import org.apache.commons.codec.net.URLCodec;

public class Repro {
    public static void main(String[] args) throws Exception {
        // RFC 2396 abs_path, i.e. what HtmlUnit's UrlUtils and HttpClient 
3.x's URI build.
        BitSet allowed = new BitSet(256);
        for (int c = 'a'; c <= 'z'; c++) allowed.set(c);
        for (int c = 'A'; c <= 'Z'; c++) allowed.set(c);
        for (int c = '0'; c <= '9'; c++) allowed.set(c);
        for (char c : "-_.!~*'()".toCharArray()) allowed.set(c);   // mark
        allowed.set('%');                                          // escaped
        for (char c : ":@&=+$,;/".toCharArray()) allowed.set(c);   // pchar

        for (String s : new String[] {"/pages/1/Test+Page", 
"/display/TST/Caf%C3%A9"}) {
            System.out.println(s + "  ->  "
                + new String(URLCodec.encodeUrl(allowed, s.getBytes("UTF-8")), 
"US-ASCII"));
        }
    }
}
{code}
----
{code:java}
1.22.0 (expected — the BitSet is honoured):

```
/pages/1/Test+Page      ->  /pages/1/Test+Page
/display/TST/Caf%C3%A9  ->  /display/TST/Caf%C3%A9
```

1.22.1 (actual):

```
/pages/1/Test+Page      ->  /pages/1/Test%2BPage
/display/TST/Caf%C3%A9  ->  /display/TST/Caf%25C3%25A9
{code}
----
h3. Downstream impact

Two libraries were found that call this overload with their own BitSet:
 * *HtmlUnit* — {{org.htmlunit.util.UrlUtils}} / 
{{com.gargoylesoftware.htmlunit.util.UrlUtils}} (present in 2.15 through 
3.11.0). {{UrlUtils.encode}} passes {{PATH_ALLOWED_CHARS}} / 
{{QUERY_ALLOWED_CHARS}} / {{{}ANCHOR_ALLOWED_CHARS{}}}, all of which include 
{{%}} and {{+}} by construction, and {{WebClient}} runs every request URL — 
including every redirect target through {{{}UrlUtils.encodeUrl{}}}.
 * *Commons HttpClient 3.x* — {{org.apache.commons.httpclient.URI}} and
{{{}org.apache.commons.httpclient.util.URIUtil{}}}, same pattern.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to