Skip to content

Commit

Permalink
Use own copy of PercentCodec for URI path encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
Xtansia committed Jul 25, 2024
1 parent c098d8b commit 59ef0db
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 137 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import org.opensearch.client.json.JsonpDeserializer;
import org.opensearch.client.opensearch._types.ErrorResponse;
import org.opensearch.client.transport.JsonEndpoint;
import org.opensearch.client.util.PathEncoder;
import org.opensearch.client.util.PercentCodec;

public class SimpleEndpoint<RequestT, ResponseT> implements JsonEndpoint<RequestT, ResponseT, ErrorResponse> {

Expand Down Expand Up @@ -133,6 +133,6 @@ public static RuntimeException noPathTemplateFound(String what) {
}

public static void pathEncode(String src, StringBuilder dest) {
dest.append(PathEncoder.encode(src));
PercentCodec.RFC3986.encode(dest, src);
}
}

This file was deleted.

187 changes: 187 additions & 0 deletions java-client/src/main/java/org/opensearch/client/util/PercentCodec.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.client.util;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.BitSet;

/**
* Percent-encoding.
* <p>
* Adapted from Apache HttpComponents HttpCore v5's <a href="https://github.com/apache/httpcomponents-core/blob/e009a923eefe79cf3593efbb0c18a3525ae63669/httpcore5/src/main/java/org/apache/hc/core5/net/PercentCodec.java">PercentCodec.java</a>
* </p>
*/
public class PercentCodec {
private static final BitSet GEN_DELIMS = new BitSet(256);
private static final BitSet SUB_DELIMS = new BitSet(256);
private static final BitSet UNRESERVED = new BitSet(256);
private static final BitSet URIC = new BitSet(256);

static {
GEN_DELIMS.set(':');
GEN_DELIMS.set('/');
GEN_DELIMS.set('?');
GEN_DELIMS.set('#');
GEN_DELIMS.set('[');
GEN_DELIMS.set(']');
GEN_DELIMS.set('@');

SUB_DELIMS.set('!');
SUB_DELIMS.set('$');
SUB_DELIMS.set('&');
SUB_DELIMS.set('\'');
SUB_DELIMS.set('(');
SUB_DELIMS.set(')');
SUB_DELIMS.set('*');
SUB_DELIMS.set('+');
SUB_DELIMS.set(',');
SUB_DELIMS.set(';');
SUB_DELIMS.set('=');

for (int i = 'a'; i <= 'z'; i++) {
UNRESERVED.set(i);
}
for (int i = 'A'; i <= 'Z'; i++) {
UNRESERVED.set(i);
}
// numeric characters
for (int i = '0'; i <= '9'; i++) {
UNRESERVED.set(i);
}
UNRESERVED.set('-');
UNRESERVED.set('.');
UNRESERVED.set('_');
UNRESERVED.set('~');
URIC.or(SUB_DELIMS);
URIC.or(UNRESERVED);
}

private 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;

private static void encode(
final StringBuilder buf,
final CharSequence content,
final Charset charset,
final BitSet safechars,
final boolean blankAsPlus
) {
if (content == null) {
return;
}
final CharBuffer cb = CharBuffer.wrap(content);
final ByteBuffer bb = (charset != null ? charset : StandardCharsets.UTF_8).encode(cb);
while (bb.hasRemaining()) {
final int b = bb.get() & 0xff;
if (safechars.get(b)) {
buf.append((char) b);
} else if (blankAsPlus && b == ' ') {
buf.append("+");
} else {
buf.append("%");
final char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, RADIX));
final char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, RADIX));
buf.append(hex1);
buf.append(hex2);
}
}
}

private static String decode(final CharSequence content, final Charset charset, final boolean plusAsBlank) {
if (content == null) {
return null;
}
final ByteBuffer bb = ByteBuffer.allocate(content.length());
final CharBuffer cb = CharBuffer.wrap(content);
while (cb.hasRemaining()) {
final char c = cb.get();
if (c == '%' && cb.remaining() >= 2) {
final char uc = cb.get();
final char lc = cb.get();
final int u = Character.digit(uc, RADIX);
final int l = Character.digit(lc, RADIX);
if (u != -1 && l != -1) {
bb.put((byte) ((u << 4) + l));
} else {
bb.put((byte) '%');
bb.put((byte) uc);
bb.put((byte) lc);
}
} else if (plusAsBlank && c == '+') {
bb.put((byte) ' ');
} else {
bb.put((byte) c);
}
}
bb.flip();
return (charset != null ? charset : StandardCharsets.UTF_8).decode(bb).toString();
}

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;
}

public void encode(final StringBuilder buf, final CharSequence content) {
encode(buf, content, StandardCharsets.UTF_8, unreserved, false);
}

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();
}

public String decode(final CharSequence content) {
return decode(content, StandardCharsets.UTF_8, false);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,10 @@ public void testArrayPathParameter() {
assertEquals("/a/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));

req = RefreshRequest.of(b -> b.index("a", "b"));
if (isHttpClient5Present()) {
assertEquals("/a%2Cb/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));

} else {
assertEquals("/a,b/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));
}
assertEquals("/a%2Cb/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));

req = RefreshRequest.of(b -> b.index("a", "b", "c"));
if (isHttpClient5Present()) {
assertEquals("/a%2Cb%2Cc/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));
} else {
assertEquals("/a,b,c/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));
}
assertEquals("/a%2Cb%2Cc/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));
}

@Test
Expand All @@ -80,11 +71,7 @@ public void testPathEncoding() {
assertEquals("/a%2Fb/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));

req = RefreshRequest.of(b -> b.index("a/b", "c/d"));
if (isHttpClient5Present()) {
assertEquals("/a%2Fb%2Cc%2Fd/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));
} else {
assertEquals("/a%2Fb,c%2Fd/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));
}
assertEquals("/a%2Fb%2Cc%2Fd/_refresh", RefreshRequest._ENDPOINT.requestUrl(req));

}

Expand All @@ -103,13 +90,4 @@ public void testArrayQueryParameter() {
req = RefreshRequest.of(b -> b.expandWildcards(ExpandWildcard.All, ExpandWildcard.Closed));
assertEquals("all,closed", RefreshRequest._ENDPOINT.queryParameters(req).get("expand_wildcards"));
}

private static boolean isHttpClient5Present() {
try {
Class.forName("org.apache.hc.core5.net.URLEncodedUtils");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

This file was deleted.

Loading

0 comments on commit 59ef0db

Please sign in to comment.