You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2023/12/13 08:24:04 UTC

(httpcomponents-core) 37/37: Support for percent coding as defined in RFC 5987

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit e5ba6e1fab154d63d0387911253cefbba76eec25
Author: Arturo Bernal <ab...@apache.org>
AuthorDate: Fri Dec 8 18:54:32 2023 +0100

    Support for percent coding as defined in RFC 5987
---
 .../java/org/apache/hc/core5/net/PercentCodec.java | 70 +++++++++++++++++++++-
 .../org/apache/hc/core5/net/TestPercentCodec.java  | 30 ++++++++++
 2 files changed, 99 insertions(+), 1 deletion(-)

diff --git a/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java b/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java
index 2782282a4..40ebc9cb8 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java
@@ -34,7 +34,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.BitSet;
 
 /**
- * Percent-encoding mechanism defined in RFC 3986
+ * Percent-encoding.
  *
  * @since 5.1
  */
@@ -84,6 +84,35 @@ public class PercentCodec {
         URIC.or(UNRESERVED);
     }
 
+    static final BitSet RFC5987_UNRESERVED = new BitSet(256);
+
+    static {
+        // Alphanumeric characters
+        for (int i = 'a'; i <= 'z'; i++) {
+            RFC5987_UNRESERVED.set(i);
+        }
+        for (int i = 'A'; i <= 'Z'; i++) {
+            RFC5987_UNRESERVED.set(i);
+        }
+        for (int i = '0'; i <= '9'; i++) {
+            RFC5987_UNRESERVED.set(i);
+        }
+
+        // Additional characters as per RFC 5987 attr-char
+        RFC5987_UNRESERVED.set('!');
+        RFC5987_UNRESERVED.set('#');
+        RFC5987_UNRESERVED.set('$');
+        RFC5987_UNRESERVED.set('&');
+        RFC5987_UNRESERVED.set('+');
+        RFC5987_UNRESERVED.set('-');
+        RFC5987_UNRESERVED.set('.');
+        RFC5987_UNRESERVED.set('^');
+        RFC5987_UNRESERVED.set('_');
+        RFC5987_UNRESERVED.set('`');
+        RFC5987_UNRESERVED.set('|');
+        RFC5987_UNRESERVED.set('~');
+    }
+
     private static final int RADIX = 16;
 
     static void encode(final StringBuilder buf, final CharSequence content, final Charset charset,
@@ -160,4 +189,43 @@ public class PercentCodec {
         return decode(content, charset, false);
     }
 
+    public static final PercentCodec RFC3986 = new PercentCodec(UNRESERVED);
+    public static final PercentCodec RFC5987 = new PercentCodec(RFC5987_UNRESERVED);
+
+    private final BitSet unreserved;
+
+    private PercentCodec(final BitSet unreserved) {
+        this.unreserved = unreserved;
+    }
+
+    public PercentCodec() {
+        this.unreserved = UNRESERVED;
+    }
+
+    /**
+     * @since 5.3
+     */
+    public void encode(final StringBuilder buf, final CharSequence content) {
+        encode(buf, content, StandardCharsets.UTF_8, unreserved, false);
+    }
+
+    /**
+     * @since 5.3
+     */
+    public String encode(final CharSequence content) {
+        if (content == null) {
+            return null;
+        }
+        final StringBuilder buf = new StringBuilder();
+        encode(buf, content, StandardCharsets.UTF_8, unreserved, false);
+        return buf.toString();
+    }
+
+    /**
+     * @since 5.3
+     */
+    public String decode(final CharSequence content) {
+        return decode(content, StandardCharsets.UTF_8, false);
+    }
+
 }
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java b/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java
index 99fe9a050..48ea00efb 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/net/TestPercentCodec.java
@@ -28,11 +28,15 @@
 package org.apache.hc.core5.net;
 
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
 import java.nio.charset.StandardCharsets;
+import java.util.stream.Stream;
 
 import org.hamcrest.CoreMatchers;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /**
  * Unit tests for {@link PercentCodec}.
@@ -68,4 +72,30 @@ public class TestPercentCodec {
                 CoreMatchers.equalTo("blah! %wa"));
     }
 
+    @ParameterizedTest
+    @MethodSource("params")
+    public void testRfc5987EncodingDecoding(final String input, final String expected) {
+        assertEquals(expected, PercentCodec.RFC5987.encode(input));
+        assertEquals(input, PercentCodec.RFC5987.decode(expected));
+    }
+
+    static Stream<Object[]> params() {
+        return Stream.of(
+                new Object[]{"foo-ä-€.html", "foo-%C3%A4-%E2%82%AC.html"},
+                new Object[]{"世界ーファイル 2.jpg", "%E4%B8%96%E7%95%8C%E3%83%BC%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%202.jpg"},
+                new Object[]{"foo.jpg", "foo.jpg"},
+                new Object[]{"simple", "simple"},  // Unreserved characters
+                new Object[]{"reserved/chars?", "reserved%2Fchars%3F"},  // Reserved characters
+                new Object[]{"", ""},  // Empty string
+                new Object[]{"space test", "space%20test"},  // String with space
+                new Object[]{"ümlaut", "%C3%BCmlaut"}  // Non-ASCII characters
+        );
+    }
+
+    @Test
+    public void verifyRfc5987EncodingandDecoding() {
+        final String s = "!\"$£%^&*()_-+={[}]:@~;'#,./<>?\\|✓éèæðŃœ";
+        assertThat(PercentCodec.RFC5987.decode(PercentCodec.RFC5987.encode(s)), CoreMatchers.equalTo(s));
+    }
+
 }