diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock index 017e772..4ec1d81 100755 --- a/.speakeasy/gen.lock +++ b/.speakeasy/gen.lock @@ -3,10 +3,10 @@ id: 1d22a5a4-8bac-42e3-b164-121fcacf66c9 management: docChecksum: b7d2d41694154c8bb06badd95ee5eabf docVersion: v1 - speakeasyVersion: 1.437.0 - generationVersion: 2.456.0 - releaseVersion: 1.2.0 - configChecksum: 09ed2850809d9e84b4ae26784404d320 + speakeasyVersion: 1.441.0 + generationVersion: 2.460.1 + releaseVersion: 1.3.0 + configChecksum: 97544bded64ebb467d8148b7b71880ba repoURL: https://github.com/clerk/clerk-sdk-java.git published: true features: @@ -14,7 +14,7 @@ features: additionalDependencies: 0.1.0 additionalProperties: 0.0.1 constsAndDefaults: 0.1.1 - core: 3.31.0 + core: 3.32.0 deprecations: 2.81.1 examples: 2.81.3 flattening: 2.81.1 @@ -1234,6 +1234,7 @@ generatedFiles: - src/main/java/com/clerk/backend_api/utils/OneOfDeserializer.java - src/main/java/com/clerk/backend_api/utils/Options.java - src/main/java/com/clerk/backend_api/utils/PathParamsMetadata.java + - src/main/java/com/clerk/backend_api/utils/QueryParameter.java - src/main/java/com/clerk/backend_api/utils/QueryParameters.java - src/main/java/com/clerk/backend_api/utils/QueryParamsMetadata.java - src/main/java/com/clerk/backend_api/utils/RequestBody.java @@ -1248,6 +1249,7 @@ generatedFiles: - src/main/java/com/clerk/backend_api/utils/SpeakeasyMetadata.java - src/main/java/com/clerk/backend_api/utils/TypedObject.java - src/main/java/com/clerk/backend_api/utils/Types.java + - src/main/java/com/clerk/backend_api/utils/Utf8UrlEncoder.java - src/main/java/com/clerk/backend_api/utils/Utils.java examples: GetPublicInterstitial: {} diff --git a/.speakeasy/gen.yaml b/.speakeasy/gen.yaml index 95bccd9..ca30d11 100755 --- a/.speakeasy/gen.yaml +++ b/.speakeasy/gen.yaml @@ -13,7 +13,7 @@ generation: oAuth2ClientCredentialsEnabled: true oAuth2PasswordEnabled: false java: - version: 1.2.0 + version: 1.3.0 additionalDependencies: [] additionalPlugins: [] artifactID: backend-api diff --git a/.speakeasy/workflow.lock b/.speakeasy/workflow.lock index d0dfcbc..74cd5e6 100644 --- a/.speakeasy/workflow.lock +++ b/.speakeasy/workflow.lock @@ -1,20 +1,20 @@ -speakeasyVersion: 1.437.0 +speakeasyVersion: 1.441.0 sources: clerk-java-sdk: sourceNamespace: clerk-java-sdk - sourceRevisionDigest: sha256:1a486565939833e7da8fd89f4dca29e4ca17f28654e44085a3363f0f1e440eeb + sourceRevisionDigest: sha256:5bfc2dc2bdbf8afca23882fdc2fc99541c6aa384f8ea99311727bdc12bbe0c3f sourceBlobDigest: sha256:3a79bd76a35cec51d82efcdad7c5fffff14db6ed394011e299ac78c2f6159e12 tags: - latest - - main + - speakeasy-sdk-regen-1731969931 targets: clerk-java: source: clerk-java-sdk sourceNamespace: clerk-java-sdk - sourceRevisionDigest: sha256:1a486565939833e7da8fd89f4dca29e4ca17f28654e44085a3363f0f1e440eeb + sourceRevisionDigest: sha256:5bfc2dc2bdbf8afca23882fdc2fc99541c6aa384f8ea99311727bdc12bbe0c3f sourceBlobDigest: sha256:3a79bd76a35cec51d82efcdad7c5fffff14db6ed394011e299ac78c2f6159e12 codeSamplesNamespace: clerk-java-sdk-code-samples - codeSamplesRevisionDigest: sha256:d767573120892d3ab715b3145a54847f35113612c3817bacc394d4b02e93896d + codeSamplesRevisionDigest: sha256:d1becf6d0bfef998549a0d7e2547800a0c78a3b2fa6d8e625537c69afec6d8b1 my-first-target: source: clerk-java-sdk sourceNamespace: clerk-java-sdk diff --git a/README.md b/README.md index c6b970c..c0f11c3 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ The samples below show how a published SDK artifact is used: Gradle: ```groovy -implementation 'com.clerk:backend-api:1.2.0' +implementation 'com.clerk:backend-api:1.3.0' ``` Maven: @@ -55,7 +55,7 @@ Maven: com.clerk backend-api - 1.2.0 + 1.3.0 ``` diff --git a/RELEASES.md b/RELEASES.md index 1d3b651..872faf7 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -158,4 +158,14 @@ Based on: ### Generated - [java v1.2.0] . ### Releases -- [Maven Central v1.2.0] https://central.sonatype.com/artifact/com.clerk/backend-api/1.2.0 - . \ No newline at end of file +- [Maven Central v1.2.0] https://central.sonatype.com/artifact/com.clerk/backend-api/1.2.0 - . + +## 2024-11-18 22:45:28 +### Changes +Based on: +- OpenAPI Doc +- Speakeasy CLI 1.441.0 (2.460.1) https://github.com/speakeasy-api/speakeasy +### Generated +- [java v1.3.0] . +### Releases +- [Maven Central v1.3.0] https://central.sonatype.com/artifact/com.clerk/backend-api/1.3.0 - . \ No newline at end of file diff --git a/build.gradle b/build.gradle index aa39a35..4ec49cd 100644 --- a/build.gradle +++ b/build.gradle @@ -63,7 +63,7 @@ tasks.withType(Javadoc) { } group = "com.clerk" -version = "1.2.0" +version = "1.3.0" sourcesJar { archiveBaseName = "backend-api" @@ -101,7 +101,7 @@ publishing { maven(MavenPublication) { groupId = 'com.clerk' artifactId = 'backend-api' - version = '1.2.0' + version = '1.3.0' from components.java diff --git a/src/main/java/com/clerk/backend_api/SDKConfiguration.java b/src/main/java/com/clerk/backend_api/SDKConfiguration.java index 2c62f0b..65a08b7 100644 --- a/src/main/java/com/clerk/backend_api/SDKConfiguration.java +++ b/src/main/java/com/clerk/backend_api/SDKConfiguration.java @@ -23,8 +23,8 @@ public Optional securitySource() { public int serverIdx = 0; private static final String LANGUAGE = "java"; public static final String OPENAPI_DOC_VERSION = "v1"; - public static final String SDK_VERSION = "1.2.0"; - public static final String GEN_VERSION = "2.456.0"; + public static final String SDK_VERSION = "1.3.0"; + public static final String GEN_VERSION = "2.460.1"; private static final String BASE_PACKAGE = "com.clerk.backend_api"; public static final String USER_AGENT = String.format("speakeasy-sdk/%s %s %s %s %s", diff --git a/src/main/java/com/clerk/backend_api/utils/HTTPRequest.java b/src/main/java/com/clerk/backend_api/utils/HTTPRequest.java index 5f23ced..61ea307 100644 --- a/src/main/java/com/clerk/backend_api/utils/HTTPRequest.java +++ b/src/main/java/com/clerk/backend_api/utils/HTTPRequest.java @@ -4,6 +4,7 @@ package com.clerk.backend_api.utils; +import java.net.URI; import java.net.URISyntaxException; import java.net.http.HttpRequest; import java.net.http.HttpRequest.BodyPublisher; @@ -15,15 +16,15 @@ import java.util.Map; import java.util.Optional; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.message.BasicNameValuePair; - public class HTTPRequest { + private static final String FRAGMENT_SEGMENT_START = "#"; + private static final String QUERY_NAME_VALUE_DELIMITER = "="; + private static final String QUERY_PARAMETER_DELIMITER = "&"; + private static final String QUERY_SEGMENT_START = "?"; private final String baseURL; private final String method; - private final List queryParams = new ArrayList<>(); + private final List queryParams = new ArrayList<>(); private final Map> headers = new HashMap<>(); private Optional body = Optional.empty(); // mutable @@ -54,21 +55,21 @@ public HTTPRequest addHeaders(Map> map) { return this; } - public HTTPRequest addQueryParam(String name, String value) { - addQueryParam(new BasicNameValuePair(name, value)); + public HTTPRequest addQueryParam(QueryParameter param) { + this.queryParams.add(param); return this; } - - public HTTPRequest addQueryParam(NameValuePair param) { - this.queryParams.add(param); + + public HTTPRequest addQueryParam(String key, String value, boolean allowReserved) { + this.queryParams.add(QueryParameter.of(key, value, allowReserved)); return this; } - public HTTPRequest addQueryParams(Collection params) { + public HTTPRequest addQueryParams(Collection params) { params.forEach(p -> addQueryParam(p)); return this; } - + public HttpRequest build() { HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); @@ -81,14 +82,49 @@ public HttpRequest build() { } requestBuilder.method(method, bodyPublisher); try { - URIBuilder b = new URIBuilder(this.baseURL); - queryParams.forEach(pair -> b.addParameter(pair.getName(), pair.getValue())); - requestBuilder.uri(b.build()); + requestBuilder.uri(new URI(buildUrl(baseURL, queryParams))); } catch (URISyntaxException e) { throw new RuntimeException(e); } headers.forEach((k, list) -> list.forEach(v -> requestBuilder.header(k, v))); return requestBuilder.build(); } - + + // VisibleForTesting + public static String buildUrl(String baseURL, Collection queryParams) { + if (queryParams.isEmpty()) { + return baseURL; + } else { + final String base; + final String fragment; + int i = baseURL.indexOf(FRAGMENT_SEGMENT_START); + if (i == -1) { + base = baseURL; + fragment = ""; + } else { + base = baseURL.substring(0, i); + fragment = baseURL.substring(i); + } + StringBuilder b = new StringBuilder(base); + if (!base.contains(QUERY_SEGMENT_START)) { + b.append(QUERY_SEGMENT_START); + } else { + b.append(QUERY_PARAMETER_DELIMITER); + } + boolean first = true; + for (QueryParameter p : queryParams) { + if (!first) { + b.append(QUERY_PARAMETER_DELIMITER); + } + first = false; + // don't allow reserved characters to be unencoded in key (??) + b.append(Utf8UrlEncoder.DEFAULT.encode(p.name())); + b.append(QUERY_NAME_VALUE_DELIMITER); + b.append(Utf8UrlEncoder.allowReserved(p.allowReserved()).encode(p.value())); + } + b.append(fragment); + return b.toString(); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/clerk/backend_api/utils/PathParamsMetadata.java b/src/main/java/com/clerk/backend_api/utils/PathParamsMetadata.java index 99cbca0..46818d7 100644 --- a/src/main/java/com/clerk/backend_api/utils/PathParamsMetadata.java +++ b/src/main/java/com/clerk/backend_api/utils/PathParamsMetadata.java @@ -8,15 +8,17 @@ class PathParamsMetadata { + // these fields set via reflection + String style = "simple"; boolean explode; String name; String serialization; + boolean allowReserved; private PathParamsMetadata() { } - // pathParam:style=simple,explode=false,name=apiID static PathParamsMetadata parse(Field field) throws IllegalArgumentException, IllegalAccessException { return Metadata.parse("pathParam", new PathParamsMetadata(), field); } diff --git a/src/main/java/com/clerk/backend_api/utils/QueryParameter.java b/src/main/java/com/clerk/backend_api/utils/QueryParameter.java new file mode 100644 index 0000000..37bf254 --- /dev/null +++ b/src/main/java/com/clerk/backend_api/utils/QueryParameter.java @@ -0,0 +1,62 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +package com.clerk.backend_api.utils; + +import java.util.Objects; + +// internal class, not for public use +// TODO move to internal package +public final class QueryParameter { + + private final String name; + private final String value; + private final boolean allowReserved; + + private QueryParameter(String name, String value, boolean allowReserved) { + this.name = name; + this.value = value; + this.allowReserved = allowReserved; + } + + public static QueryParameter of(String name, String value, boolean allowReserved) { + return new QueryParameter(name, value, allowReserved); + } + + public String name() { + return name; + } + + public String value() { + return value; + } + + public boolean allowReserved() { + return allowReserved; + } + + @Override + public String toString() { + return "QueryParameter [name=" + name + ", value=" + value + ", allowReserved=" + allowReserved + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(allowReserved, name, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + QueryParameter other = (QueryParameter) obj; + return allowReserved == other.allowReserved && Objects.equals(name, other.name) + && Objects.equals(value, other.value); + } + +} diff --git a/src/main/java/com/clerk/backend_api/utils/QueryParameters.java b/src/main/java/com/clerk/backend_api/utils/QueryParameters.java index 69e4157..dfa51f5 100644 --- a/src/main/java/com/clerk/backend_api/utils/QueryParameters.java +++ b/src/main/java/com/clerk/backend_api/utils/QueryParameters.java @@ -11,16 +11,13 @@ import java.util.Map; import java.util.stream.Collectors; -import org.apache.http.NameValuePair; -import org.apache.http.message.BasicNameValuePair; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class QueryParameters { - public static List parseQueryParams(Class type, T queryParams, + public static List parseQueryParams(Class type, T queryParams, Map>> globals) throws Exception { - List allParams = new ArrayList<>(); + List allParams = new ArrayList<>(); Field[] fields = type.getDeclaredFields(); @@ -45,20 +42,20 @@ public static List parseQueryParams(Class t } if (queryParamsMetadata.serialization != null && !queryParamsMetadata.serialization.isBlank()) { - List params = parseSerializedParams(queryParamsMetadata, value); + List params = parseSerializedParams(queryParamsMetadata, value); allParams.addAll(params); } else { switch (queryParamsMetadata.style) { case "form": - List formParams = parseDelimitedParams(queryParamsMetadata, value, ","); + List formParams = parseDelimitedParams(queryParamsMetadata, value, ","); allParams.addAll(formParams); break; case "deepObject": - List deepObjectParams = parseDeepObjectParams(queryParamsMetadata, value); + List deepObjectParams = parseDeepObjectParams(queryParamsMetadata, value); allParams.addAll(deepObjectParams); break; case "pipeDelimited": - List pipeDelimitedParams = parseDelimitedParams(queryParamsMetadata, value, "|"); + List pipeDelimitedParams = parseDelimitedParams(queryParamsMetadata, value, "|"); allParams.addAll(pipeDelimitedParams); break; default: @@ -70,14 +67,14 @@ public static List parseQueryParams(Class t return allParams; } - private static List parseSerializedParams(QueryParamsMetadata queryParamsMetadata, Object value) + private static List parseSerializedParams(QueryParamsMetadata queryParamsMetadata, Object value) throws JsonProcessingException { - List params = new ArrayList<>(); + List params = new ArrayList<>(); switch (queryParamsMetadata.serialization) { case "json": ObjectMapper mapper = JSON.getMapper(); String json = mapper.writeValueAsString(value); - params.add(new BasicNameValuePair(queryParamsMetadata.name, json)); + params.add(QueryParameter.of(queryParamsMetadata.name, json, queryParamsMetadata.allowReserved)); break; default: break; @@ -85,9 +82,9 @@ private static List parseSerializedParams(QueryParamsMetadata que return params; } - private static List parseDelimitedParams(QueryParamsMetadata queryParamsMetadata, Object value, String delimiter) + private static List parseDelimitedParams(QueryParamsMetadata queryParamsMetadata, Object value, String delimiter) throws IllegalArgumentException, IllegalAccessException { - List params = new ArrayList<>(); + List params = new ArrayList<>(); switch (Types.getType(value.getClass())) { case ARRAY: { @@ -107,7 +104,7 @@ private static List parseDelimitedParams(QueryParamsMetadata quer values.add(String.join(delimiter, items)); } - params.addAll(values.stream().map(v -> new BasicNameValuePair(queryParamsMetadata.name, v)) + params.addAll(values.stream().map(v -> QueryParameter.of(queryParamsMetadata.name, v, queryParamsMetadata.allowReserved)) .collect(Collectors.toList())); break; } @@ -121,20 +118,20 @@ private static List parseDelimitedParams(QueryParamsMetadata quer String val = Utils.valToString(entry.getValue()); if (queryParamsMetadata.explode) { - params.add(new BasicNameValuePair(key, val)); + params.add(QueryParameter.of(key, val, queryParamsMetadata.allowReserved)); } else { items.add(String.format("%s%s%s", key, delimiter, val)); } } if (items.size() > 0) { - params.add(new BasicNameValuePair(queryParamsMetadata.name, String.join(delimiter, items))); + params.add(QueryParameter.of(queryParamsMetadata.name, String.join(delimiter, items), queryParamsMetadata.allowReserved)); } break; } case OBJECT: { if (!Utils.allowIntrospection(value.getClass())) { - params.add(new BasicNameValuePair(queryParamsMetadata.name, Utils.valToString(value))); + params.add(QueryParameter.of(queryParamsMetadata.name, Utils.valToString(value), queryParamsMetadata.allowReserved)); break; } Field[] fields = value.getClass().getDeclaredFields(); @@ -155,29 +152,29 @@ private static List parseDelimitedParams(QueryParamsMetadata quer } if (queryParamsMetadata.explode) { - params.add(new BasicNameValuePair(metadata.name, Utils.valToString(val))); + params.add(QueryParameter.of(metadata.name, Utils.valToString(val), metadata.allowReserved)); } else { items.add(String.format("%s%s%s", metadata.name, delimiter, Utils.valToString(val))); } } if (items.size() > 0) { - params.add(new BasicNameValuePair(queryParamsMetadata.name, String.join(delimiter, items))); + params.add(QueryParameter.of(queryParamsMetadata.name, String.join(delimiter, items), queryParamsMetadata.allowReserved)); } break; } default: - params.add(new BasicNameValuePair(queryParamsMetadata.name, Utils.valToString(value))); + params.add(QueryParameter.of(queryParamsMetadata.name, Utils.valToString(value), queryParamsMetadata.allowReserved)); break; } return params; } - private static List parseDeepObjectParams(QueryParamsMetadata queryParamsMetadata, Object value) + private static List parseDeepObjectParams(QueryParamsMetadata queryParamsMetadata, Object value) throws Exception { - List params = new ArrayList<>(); + List params = new ArrayList<>(); switch (Types.getType(value.getClass())) { case MAP: { @@ -189,12 +186,12 @@ private static List parseDeepObjectParams(QueryParamsMetadata que if (val instanceof List || val.getClass().isArray()) { for (Object v : Utils.toList(val)) { - params.add(new BasicNameValuePair(String.format("%s[%s]", queryParamsMetadata.name, key), - Utils.valToString(v))); + params.add(QueryParameter.of(String.format("%s[%s]", queryParamsMetadata.name, key), + Utils.valToString(v), queryParamsMetadata.allowReserved)); } } else { - params.add(new BasicNameValuePair(String.format("%s[%s]", queryParamsMetadata.name, key), - Utils.valToString(val))); + params.add(QueryParameter.of(String.format("%s[%s]", queryParamsMetadata.name, key), + Utils.valToString(val), queryParamsMetadata.allowReserved)); } } @@ -221,14 +218,14 @@ private static List parseDeepObjectParams(QueryParamsMetadata que if (val instanceof List || val.getClass().isArray()) { for (Object v : Utils.toList(val)) { - params.add(new BasicNameValuePair( + params.add(QueryParameter.of( String.format("%s[%s]", queryParamsMetadata.name, metadata.name), - Utils.valToString(v))); + Utils.valToString(v), metadata.allowReserved)); } } else { params.add( - new BasicNameValuePair(String.format("%s[%s]", queryParamsMetadata.name, metadata.name), - Utils.valToString(val))); + QueryParameter.of(String.format("%s[%s]", queryParamsMetadata.name, metadata.name), + Utils.valToString(val), metadata.allowReserved)); } } diff --git a/src/main/java/com/clerk/backend_api/utils/QueryParamsMetadata.java b/src/main/java/com/clerk/backend_api/utils/QueryParamsMetadata.java index c1bd13a..b31b33e 100644 --- a/src/main/java/com/clerk/backend_api/utils/QueryParamsMetadata.java +++ b/src/main/java/com/clerk/backend_api/utils/QueryParamsMetadata.java @@ -8,12 +8,14 @@ class QueryParamsMetadata { + // these parameters set via reflection + String style = "form"; boolean explode = true; String name; String serialization; + boolean allowReserved; - // queryParam:style=simple,explode=false,name=apiID static QueryParamsMetadata parse(Field field) throws IllegalArgumentException, IllegalAccessException { return Metadata.parse("queryParam", new QueryParamsMetadata(), field); } diff --git a/src/main/java/com/clerk/backend_api/utils/Security.java b/src/main/java/com/clerk/backend_api/utils/Security.java index 8ffc4a6..3c8b0cb 100644 --- a/src/main/java/com/clerk/backend_api/utils/Security.java +++ b/src/main/java/com/clerk/backend_api/utils/Security.java @@ -108,7 +108,7 @@ private static void parseSecuritySchemeValue(HTTPRequest request, SecurityMetada break; case "query": request.addQueryParam( - securityMetadata.name, Utils.valToString(value)); + securityMetadata.name, Utils.valToString(value), false); break; case "cookie": request.addHeader("Cookie", diff --git a/src/main/java/com/clerk/backend_api/utils/Utf8UrlEncoder.java b/src/main/java/com/clerk/backend_api/utils/Utf8UrlEncoder.java new file mode 100644 index 0000000..0f57565 --- /dev/null +++ b/src/main/java/com/clerk/backend_api/utils/Utf8UrlEncoder.java @@ -0,0 +1,117 @@ +/* + * Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. + */ + +package com.clerk.backend_api.utils; + +import java.io.CharArrayWriter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.BitSet; +import java.util.Objects; + +// Internal use only +// TODO move to an internal package +public final class Utf8UrlEncoder { + + private static final BitSet DO_NOT_ENCODE_CHARS = createDoNotEncodeChars(); + private static final int CASE_DIFF = ('a' - 'A'); + + private final BitSet safeChars; + + public static final Utf8UrlEncoder ALLOW_RESERVED = new Utf8UrlEncoder(":/?#[]@!$&'()*+,;="); + public static final Utf8UrlEncoder DEFAULT = new Utf8UrlEncoder(""); + + public static Utf8UrlEncoder allowReserved(boolean allowReserved) { + return allowReserved ? ALLOW_RESERVED : DEFAULT; + } + + private Utf8UrlEncoder(String safeCharacters) { + Objects.requireNonNull(safeCharacters, "safeCharacters"); + int max = -1; + for (int i = 0; i < safeCharacters.length(); i++) { + char ch = safeCharacters.charAt(i); + max = Math.max(ch, max); + } + BitSet safeChars = new BitSet(max + 1); + for (int i = 0; i < safeCharacters.length(); i++) { + char ch = safeCharacters.charAt(i); + safeChars.set(ch); + } + this.safeChars = safeChars; + } + + public String encode(String s) { + return encode(s, StandardCharsets.UTF_8); + } + + private String encode(String s, Charset charset) { + boolean changed = false; + StringBuilder out = new StringBuilder(s.length()); + CharArrayWriter writer = new CharArrayWriter(); + + for (int i = 0; i < s.length();) { + int c = (int) s.charAt(i); + if (DO_NOT_ENCODE_CHARS.get(c) || safeChars.get(c)) { + out.append((char) c); + i++; + } else { + // convert to external encoding before hex conversion + do { + writer.write(c); + if (c >= 0xD800 && c <= 0xDBFF) { + if ((i + 1) < s.length()) { + int d = (int) s.charAt(i + 1); + if (d >= 0xDC00 && d <= 0xDFFF) { + writer.write(d); + i++; + } + } + } + i++; + } while (i < s.length() && !DO_NOT_ENCODE_CHARS.get((c = (int) s.charAt(i)))); + + writer.flush(); + String str = new String(writer.toCharArray()); + byte[] ba = str.getBytes(charset); + for (int j = 0; j < ba.length; j++) { + out.append('%'); + char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16); + // converting to use uppercase letter as part of + // the hex value if ch is a letter. + if (Character.isLetter(ch)) { + ch -= CASE_DIFF; + } + out.append(ch); + ch = Character.forDigit(ba[j] & 0xF, 16); + if (Character.isLetter(ch)) { + ch -= CASE_DIFF; + } + out.append(ch); + } + writer.reset(); + changed = true; + } + } + + return (changed ? out.toString() : s); + } + + private static BitSet createDoNotEncodeChars() { + BitSet b = new BitSet(256); + for (int i = 'a'; i <= 'z'; i++) { + b.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + b.set(i); + } + for (int i = '0'; i <= '9'; i++) { + b.set(i); + } + b.set('-'); + b.set('_'); + b.set('.'); + b.set('*'); + return b; + } +} diff --git a/src/main/java/com/clerk/backend_api/utils/Utils.java b/src/main/java/com/clerk/backend_api/utils/Utils.java index 731cde5..612cabc 100644 --- a/src/main/java/com/clerk/backend_api/utils/Utils.java +++ b/src/main/java/com/clerk/backend_api/utils/Utils.java @@ -15,7 +15,6 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.net.URI; -import java.net.URISyntaxException; import java.net.URLEncoder; import java.net.http.HttpClient.Version; import java.net.http.HttpHeaders; @@ -144,7 +143,9 @@ public static String generateURL(Class type, String baseURL, String path, pathParams.put(pathParamsMetadata.name, String.join(",", - array.stream().map(v -> valToString(v)) + array.stream() + .map(v -> valToString(v)) + .map(v -> pathEncode(v, pathParamsMetadata.allowReserved)) .collect(Collectors.toList()))); break; case MAP: @@ -156,17 +157,17 @@ public static String generateURL(Class type, String baseURL, String path, pathParams.put(pathParamsMetadata.name, String.join(",", map.entrySet().stream().map(e -> { if (pathParamsMetadata.explode) { - return String.format("%s=%s", valToString(e.getKey()), - valToString(e.getValue())); + return String.format("%s=%s", pathEncode(valToString(e.getKey()), false), + pathEncode(valToString(e.getValue()), false)); } else { - return String.format("%s,%s", valToString(e.getKey()), - valToString(e.getValue())); + return String.format("%s,%s", pathEncode(valToString(e.getKey()), false), + pathEncode(valToString(e.getValue()), false)); } }).collect(Collectors.toList()))); break; case OBJECT: if (!allowIntrospection(value.getClass())) { - pathParams.put(pathParamsMetadata.name, valToString(value)); + pathParams.put(pathParamsMetadata.name, pathEncode(valToString(value), pathParamsMetadata.allowReserved)); break; } List values = new ArrayList<>(); @@ -187,17 +188,17 @@ public static String generateURL(Class type, String baseURL, String path, if (pathParamsMetadata.explode) { values.add(String.format("%s=%s", valuePathParamsMetadata.name, - valToString(val))); + pathEncode(valToString(val), valuePathParamsMetadata.allowReserved))); } else { values.add(String.format("%s,%s", valuePathParamsMetadata.name, - valToString(val))); + pathEncode(valToString(val), valuePathParamsMetadata.allowReserved))); } } pathParams.put(pathParamsMetadata.name, String.join(",", values)); break; default: - pathParams.put(pathParamsMetadata.name, valToString(value)); + pathParams.put(pathParamsMetadata.name, pathEncode(valToString(value), pathParamsMetadata.allowReserved)); break; } } @@ -206,6 +207,10 @@ public static String generateURL(Class type, String baseURL, String path, return baseURL + templateUrl(path, pathParams); } + + private static String pathEncode(String s, boolean allowReserved) { + return Utf8UrlEncoder.allowReserved(allowReserved).encode(s); + } public static boolean contentTypeMatches(String contentType, String pattern) { if (contentType == null || contentType.isBlank()) { @@ -256,7 +261,7 @@ public static SerializedBody serializeRequestBody(Object request, String request return RequestBody.serialize(request, requestField, serializationMethod, nullable); } - public static List getQueryParams(Class type, Optional params, + public static List getQueryParams(Class type, Optional params, Map>> globals) throws Exception { if (params.isEmpty()) { return Collections.emptyList(); @@ -265,7 +270,7 @@ public static List getQueryParams(Class typ } } - public static List getQueryParams(Class type, JsonNullable params, + public static List getQueryParams(Class type, JsonNullable params, Map>> globals) throws Exception { if (!params.isPresent() || params.get() == null) { return Collections.emptyList(); @@ -274,7 +279,7 @@ public static List getQueryParams(Class typ } } - public static List getQueryParams(Class type, T params, + public static List getQueryParams(Class type, T params, Map>> globals) throws Exception { return QueryParameters.parseQueryParams(type, params, globals); } @@ -282,6 +287,8 @@ public static List getQueryParams(Class typ public static HTTPRequest configureSecurity(HTTPRequest request, Object security) throws Exception { return Security.configureSecurity(request, security); } + + private static final String DOLLAR_MARKER = "D9qPtyhOYzkHGu3c"; public static String templateUrl(String url, Map params) { StringBuilder sb = new StringBuilder(); @@ -294,12 +301,16 @@ public static String templateUrl(String url, Map params) { String key = match.substring(1, match.length() - 1); String value = params.get(key); if (value != null) { - m.appendReplacement(sb, URLEncoder.encode(value, StandardCharsets.UTF_8)); + // note that we replace $ characters in values with a marker + // and then replace the markers at the end with the $ characters + // because the presence of dollar signs can stuff up the next + // regex find + m.appendReplacement(sb, value.replace("$", DOLLAR_MARKER)); } } m.appendTail(sb); - return sb.toString(); + return sb.toString().replace(DOLLAR_MARKER, "$"); } public static Map> getHeadersFromMetadata(Object headers, Map>> globals) throws Exception { @@ -474,7 +485,7 @@ private static Map parseSerializedParams(PathParamsMetadata path case "json": ObjectMapper mapper = JSON.getMapper(); String json = mapper.writeValueAsString(value); - params.put(pathParamsMetadata.name, json); + params.put(pathParamsMetadata.name, pathEncode(json, pathParamsMetadata.allowReserved)); break; default: break; @@ -1167,18 +1178,18 @@ public static boolean isPresentAndNotNull(JsonNullable x) { return x.isPresent() && x.get() != null; } - private static final String OPEN_BRACKET_MARKER = UUID.randomUUID().toString().replace("-", ""); - private static final String CLOSE_BRACKET_MARKER = UUID.randomUUID().toString().replace("-", ""); - - public static String urlEncode(String s) { - // Ensure that complies with RFC 2732 (URLEncoder does not and we don't want to - // encode [, ] chars) - return URLEncoder.encode( // - s.replace("[", OPEN_BRACKET_MARKER) // - .replace("]", CLOSE_BRACKET_MARKER), // - StandardCharsets.UTF_8) // - .replace(OPEN_BRACKET_MARKER, "[") // - .replace(CLOSE_BRACKET_MARKER, "]"); + public static void setSseSentinel(Object o, String value) { + if (o == null || value.isBlank()) { + return; + } else { + try { + Field field = o.getClass().getDeclaredField("_eventSentinel"); + field.setAccessible(true); + field.set(o, Optional.of(value)); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + // ignore + } + } } }