diff --git a/java-client/src/main/java/co/elastic/clients/transport/endpoints/EndpointBase.java b/java-client/src/main/java/co/elastic/clients/transport/endpoints/EndpointBase.java index 62d17b703..e845e0a35 100644 --- a/java-client/src/main/java/co/elastic/clients/transport/endpoints/EndpointBase.java +++ b/java-client/src/main/java/co/elastic/clients/transport/endpoints/EndpointBase.java @@ -22,9 +22,11 @@ import co.elastic.clients.elasticsearch._types.ErrorResponse; import co.elastic.clients.json.JsonpDeserializer; import co.elastic.clients.transport.Endpoint; -import org.apache.http.client.utils.URLEncodedUtils; import javax.annotation.Nullable; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.BitSet; import java.util.Collections; import java.util.Map; import java.util.function.Function; @@ -145,8 +147,54 @@ public static RuntimeException noPathTemplateFound(String what) { "Please check the API documentation, or raise an issue if this should be a valid request."); } - public static void pathEncode(String src, StringBuilder dest) { - // TODO: avoid dependency on HttpClient here (and use something more efficient) - dest.append(URLEncodedUtils.formatSegments(src).substring(1)); + private static final BitSet PATH_SAFE; + private static final char[] HEX_CHARS; + + static { + PATH_SAFE = new BitSet(256); + // From RFC 3986 + // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + PATH_SAFE.set('a', 'z'+1); + PATH_SAFE.set('A', 'Z'+1); + PATH_SAFE.set('0', '9'+1); + PATH_SAFE.set('-'); + PATH_SAFE.set('.'); + PATH_SAFE.set('_'); + PATH_SAFE.set('~'); + + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + PATH_SAFE.set('!'); + PATH_SAFE.set('$'); + PATH_SAFE.set('&'); + PATH_SAFE.set('\''); + PATH_SAFE.set('('); + PATH_SAFE.set(')'); + PATH_SAFE.set('*'); + PATH_SAFE.set('+'); + PATH_SAFE.set(','); + PATH_SAFE.set(';'); + PATH_SAFE.set('='); + + // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + PATH_SAFE.set(':'); + PATH_SAFE.set('@'); + + HEX_CHARS = "0123456789ABCDEF".toCharArray(); + } + + public static void pathEncode(final String src, StringBuilder dest) { + final ByteBuffer buf = StandardCharsets.UTF_8.encode(src); + // In UTF-8 multibyte encoding, all bytes have the high bit set. This means we can iterate + // on all bytes and percent-encode without having to care about code point context. + while (buf.hasRemaining()) { + int b = buf.get() & 0xff; + if (PATH_SAFE.get(b)) { + dest.append((char) b); + } else { + dest.append("%"); + dest.append(HEX_CHARS[b >> 4 & 0xF]); + dest.append(HEX_CHARS[b & 0xF]); + } + } } } diff --git a/java-client/src/main/java/co/elastic/clients/transport/endpoints/SimpleEndpoint.java b/java-client/src/main/java/co/elastic/clients/transport/endpoints/SimpleEndpoint.java index 9e8041dcb..b5df37c90 100644 --- a/java-client/src/main/java/co/elastic/clients/transport/endpoints/SimpleEndpoint.java +++ b/java-client/src/main/java/co/elastic/clients/transport/endpoints/SimpleEndpoint.java @@ -22,7 +22,6 @@ import co.elastic.clients.elasticsearch._types.ErrorResponse; import co.elastic.clients.json.JsonpDeserializer; import co.elastic.clients.transport.JsonEndpoint; -import org.apache.http.client.utils.URLEncodedUtils; import java.util.Map; import java.util.function.Function; @@ -88,14 +87,4 @@ public SimpleEndpoint withResponseDeseria newResponseParser ); } - - public static RuntimeException noPathTemplateFound(String what) { - return new RuntimeException("Could not find a request " + what + " with this set of properties. " + - "Please check the API documentation, or raise an issue if this should be a valid request."); - } - - public static void pathEncode(String src, StringBuilder dest) { - // TODO: avoid dependency on HttpClient here (and use something more efficient) - dest.append(URLEncodedUtils.formatSegments(src).substring(1)); - } } diff --git a/java-client/src/test/java/co/elastic/clients/transport/endpoints/EndpointBaseTest.java b/java-client/src/test/java/co/elastic/clients/transport/endpoints/EndpointBaseTest.java new file mode 100644 index 000000000..e47c11093 --- /dev/null +++ b/java-client/src/test/java/co/elastic/clients/transport/endpoints/EndpointBaseTest.java @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. 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. + */ + +package co.elastic.clients.transport.endpoints; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class EndpointBaseTest extends Assertions { + + @Test + public void testPathEncoding() { + assertEquals("abCD12;-_*", pathEncode("abCD12;-_*")); + assertEquals("XYZ%5B", pathEncode("XYZ[")); + assertEquals("xyz%7B", pathEncode("xyz{")); + assertEquals("foo%2Fbar", pathEncode("foo/bar")); + assertEquals("foo%20bar", pathEncode("foo bar")); + assertEquals("f%C3%AAl%C3%A9", pathEncode("fĂȘlĂ©")); + } + + private String pathEncode(String s) { + StringBuilder sb = new StringBuilder(); + EndpointBase.pathEncode(s, sb); + return sb.toString(); + } +}