From 1906539847477e04d78306a5c5f35a771cb58690 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Sat, 23 Dec 2023 13:28:51 +0100 Subject: [PATCH] Use core percent codec instead of internal one --- .../entity/mime/HttpRFC7578Multipart.java | 4 +- .../client5/http/impl/auth/DigestScheme.java | 3 +- .../client5/http/impl/auth/RFC5987Codec.java | 161 ------------------ .../client5/http/utils/CodingException.java | 77 --------- .../entity/mime/HttpRFC7578MultipartTest.java | 11 +- .../http/impl/auth/RFC5987CodecTest.java | 67 -------- 6 files changed, 6 insertions(+), 317 deletions(-) delete mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/RFC5987Codec.java delete mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/utils/CodingException.java delete mode 100644 httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/RFC5987CodecTest.java diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java index 85d946893..ff42b7746 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578Multipart.java @@ -33,8 +33,8 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; -import org.apache.hc.client5.http.impl.auth.RFC5987Codec; import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.net.PercentCodec; class HttpRFC7578Multipart extends AbstractMultipartFormat { @@ -97,7 +97,7 @@ class HttpRFC7578Multipart extends AbstractMultipartFormat { if (name.equalsIgnoreCase(MimeConsts.FIELD_PARAM_FILENAME) || name.equalsIgnoreCase(MimeConsts.FIELD_PARAM_FILENAME_START)) { final String encodedValue = name.equalsIgnoreCase(MimeConsts.FIELD_PARAM_FILENAME_START) ? - "UTF-8''" + RFC5987Codec.encode(value) : RFC5987Codec.encode(value); + "UTF-8''" + PercentCodec.RFC5987.encode(value) : PercentCodec.RFC5987.encode(value); final byte[] encodedBytes = encodedValue.getBytes(StandardCharsets.US_ASCII); out.write(encodedBytes); } else { diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java index 144ace870..a455c1030 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java @@ -65,6 +65,7 @@ import org.apache.hc.core5.http.NameValuePair; import org.apache.hc.core5.http.message.BasicHeaderValueFormatter; import org.apache.hc.core5.http.message.BasicNameValuePair; import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.net.PercentCodec; import org.apache.hc.core5.util.Args; import org.apache.hc.core5.util.CharArrayBuffer; import org.slf4j.Logger; @@ -350,7 +351,7 @@ public class DigestScheme implements AuthScheme, Serializable { String encodedUsername = null; // Check if 'username' has invalid characters and use 'username*' if (username != null && containsInvalidABNFChars(username)) { - encodedUsername = "UTF-8''" + RFC5987Codec.encode(username, StandardCharsets.UTF_8); + encodedUsername = "UTF-8''" + PercentCodec.RFC5987.encode(username); } final String usernameForDigest; diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/RFC5987Codec.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/RFC5987Codec.java deleted file mode 100644 index f373482c7..000000000 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/RFC5987Codec.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.client5.http.impl.auth; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.BitSet; - -import org.apache.hc.client5.http.utils.CodingException; -import org.conscrypt.Internal; - -/** - * Utility class for encoding and decoding strings according to RFC 5987. - * This class provides methods to percent-encode and decode strings, particularly - * useful for handling HTTP header parameters that include non-ASCII characters. - * - * @Internal This class is intended for internal use within the library and - * should not be used as part of the public API. - */ -@Internal -public class RFC5987Codec { - - private static final BitSet UNRESERVED = new BitSet(256); - private static final int RADIX = 16; - - static { - // Alphanumeric characters - for (int i = 'a'; i <= 'z'; i++) { - UNRESERVED.set(i); - } - for (int i = 'A'; i <= 'Z'; i++) { - UNRESERVED.set(i); - } - for (int i = '0'; i <= '9'; i++) { - UNRESERVED.set(i); - } - - // Additional characters as per RFC 5987 attr-char - UNRESERVED.set('!'); - UNRESERVED.set('#'); - UNRESERVED.set('$'); - UNRESERVED.set('&'); - UNRESERVED.set('+'); - UNRESERVED.set('-'); - UNRESERVED.set('.'); - UNRESERVED.set('^'); - UNRESERVED.set('_'); - UNRESERVED.set('`'); - UNRESERVED.set('|'); - UNRESERVED.set('~'); - } - - - - /** - * Encodes a string using the default UTF-8 charset. - * - * @param s The string to encode. - * @return The percent-encoded string. - */ - public static String encode(final String s) { - return encode(s, StandardCharsets.UTF_8); - } - - /** - * Encodes a string using the specified charset. - * - * @param s The string to encode. - * @param charset The charset to use for encoding. - * @return The percent-encoded string. - */ - public static String encode(final String s, final Charset charset) { - final ByteBuffer bb = charset.encode(CharBuffer.wrap(s)); - final StringBuilder sb = new StringBuilder(); - - while (bb.hasRemaining()) { - final int b = bb.get() & 0xff; - if (UNRESERVED.get(b)) { - sb.append((char) b); - } else { - sb.append('%'); - sb.append(Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX))); - sb.append(Character.toUpperCase(Character.forDigit(b & 0xF, RADIX))); - } - } - - return sb.toString(); - } - - /** - * Decodes a percent-encoded string using the default UTF-8 charset. - * - * @param s The percent-encoded string to decode. - * @return The decoded string. - * @throws IllegalArgumentException If the percent-encoded string is invalid. - */ - public static String decode(final String s) throws CodingException { - return decode(s, StandardCharsets.UTF_8); - } - - /** - * Decodes a percent-encoded string using the specified charset. - * - * @param s The percent-encoded string to decode. - * @param charset The charset to use for decoding. - * @return The decoded CodingException. - * @throws IllegalArgumentException If the percent-encoded string is invalid. - */ - public static String decode(final String s, final Charset charset) throws CodingException { - final ByteBuffer bb = ByteBuffer.allocate(s.length()); - final CharBuffer cb = CharBuffer.wrap(s); - - while (cb.hasRemaining()) { - final char c = cb.get(); - if (c == '%') { - if (cb.remaining() < 2) { - throw new CodingException("Incomplete percent encoding in " + s); - } - final int u = Character.digit(cb.get(), RADIX); - final int l = Character.digit(cb.get(), RADIX); - if (u != -1 && l != -1) { - bb.put((byte) ((u << 4) + l)); - } else { - throw new CodingException("Invalid percent encoding in " + s); - } - } else { - bb.put((byte) c); - } - } - bb.flip(); - return charset.decode(bb).toString(); - } - -} \ No newline at end of file diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/utils/CodingException.java b/httpclient5/src/main/java/org/apache/hc/client5/http/utils/CodingException.java deleted file mode 100644 index f22f3f409..000000000 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/utils/CodingException.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ - -package org.apache.hc.client5.http.utils; - -import java.io.IOException; - -/** - * Signals that an error has occurred during encoding/decoding process. - *

- * This exception is thrown to indicate that a problem occurred while - * encoding (such as URL encoding) or decoding data. It is a specific - * type of {@link IOException} that is used within the Apache HttpComponents - * to handle errors related to data transformation processes. - *

- * - * @since 5.4 - */ -public class CodingException extends IOException { - - - private static final long serialVersionUID = 1668301205622354315L; - - /** - * Constructs a new CodingException with the specified detail message. - * - * @param message the detail message (which is saved for later retrieval - * by the {@link #getMessage()} method). - */ - public CodingException(final String message) { - super(message); - } - - /** - * Constructs a new CodingException with the specified detail message and cause. - *

- * Note that the detail message associated with {@code cause} is not automatically - * incorporated into this exception's detail message. - *

- * - * @param message the detail message (which is saved for later retrieval - * by the {@link #getMessage()} method). - * @param cause the cause (which is saved for later retrieval by the - * {@link #getCause()} method). (A {@code null} value is - * permitted, and indicates that the cause is nonexistent or - * unknown.) - */ - public CodingException(final String message, final Throwable cause) { - super(message, cause); - initCause(cause); - } - -} diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java index c1ff07eff..b3a553839 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/HttpRFC7578MultipartTest.java @@ -29,18 +29,11 @@ package org.apache.hc.client5.http.entity.mime; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.apache.hc.client5.http.impl.auth.RFC5987Codec; -import org.apache.hc.client5.http.utils.CodingException; -import org.junit.jupiter.api.Assertions; +import org.apache.hc.core5.net.PercentCodec; import org.junit.jupiter.api.Test; public class HttpRFC7578MultipartTest { - @Test - public void testPercentDecodingWithTooShortMessage() { - Assertions.assertThrows(CodingException.class, () -> RFC5987Codec.decode("%")); - } - @Test public void testPercentDecodingWithValidMessages() throws Exception { final String[][] tests = new String[][] { @@ -54,7 +47,7 @@ public class HttpRFC7578MultipartTest { }; for (final String[] test : tests) { - assertEquals(test[1], RFC5987Codec.decode(test[0])); + assertEquals(test[1], PercentCodec.RFC5987.decode(test[0])); } } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/RFC5987CodecTest.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/RFC5987CodecTest.java deleted file mode 100644 index 45e3d59a3..000000000 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/RFC5987CodecTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ -package org.apache.hc.client5.http.impl.auth; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.stream.Stream; - -import org.apache.hc.client5.http.utils.CodingException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -class RFC5987CodecTest { - - @ParameterizedTest - @MethodSource("params") - public void testRfc5987EncodingDecoding(final String input, final String expected) throws CodingException { - assertEquals(expected, RFC5987Codec.encode(input)); - assertEquals(input, RFC5987Codec.decode(expected)); - } - - static Stream 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() throws CodingException { - final String s = "!\"$£%^&*()_-+={[}]:@~;'#,./<>?\\|✓éèæðŃœ"; - assertThat(RFC5987Codec.decode(RFC5987Codec.encode(s)), equalTo(s)); - } -}