From 223e5604653c5e19bea8b2712a59d0328b2f98ca Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 5 Jul 2024 14:07:34 +0200 Subject: [PATCH] fix(twilio): Fix content-type header issue when the case is inconsistent + fix element template (#2821) --- .../parts/ApacheRequestBodyBuilder.java | 7 +++- .../parts/ApacheRequestHeadersBuilder.java | 12 ++++--- .../apache/ApacheRequestFactoryTest.java | 34 +++++++++++++++++++ .../element-templates/twilio-connector.json | 4 +-- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestBodyBuilder.java b/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestBodyBuilder.java index 2c5ef9b427..f0f5278b85 100644 --- a/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestBodyBuilder.java +++ b/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestBodyBuilder.java @@ -85,7 +85,12 @@ private HttpEntity createEntityForContentType( private Optional tryGetContentType(HttpCommonRequest request) { return Optional.ofNullable(request.getHeaders()) - .map(headers -> headers.get(CONTENT_TYPE)) + .flatMap( + headers -> + headers.entrySet().stream() + .filter(e -> e.getKey().equalsIgnoreCase(CONTENT_TYPE)) + .findFirst()) + .map(Map.Entry::getValue) .map(ContentType::parse); } diff --git a/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestHeadersBuilder.java b/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestHeadersBuilder.java index 34b7e31428..fd15b71b2d 100644 --- a/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestHeadersBuilder.java +++ b/connectors/http/http-base/src/main/java/io/camunda/connector/http/base/client/apache/builder/parts/ApacheRequestHeadersBuilder.java @@ -35,7 +35,7 @@ public void build(ClassicRequestBuilder builder, HttpCommonRequest request) { var headers = sanitizedHeaders(request); var hasContentTypeHeader = - Optional.of(headers).map(hd -> hd.containsKey(CONTENT_TYPE)).orElse(false); + headers.entrySet().stream().anyMatch(e -> e.getKey().equalsIgnoreCase(CONTENT_TYPE)); if (request.getMethod().supportsBody && !hasContentTypeHeader) { // default content type builder.addHeader(CONTENT_TYPE, APPLICATION_JSON.getMimeType()); @@ -54,7 +54,7 @@ public void build(ClassicRequestBuilder builder, HttpCommonRequest request) { */ private Predicate> defaultMultipartContentType() { return e -> - e.getKey().equals(CONTENT_TYPE) + e.getKey().equalsIgnoreCase(CONTENT_TYPE) && e.getValue().contains(MULTIPART_FORM_DATA.getMimeType()) && !e.getValue().contains("boundary"); } @@ -63,9 +63,11 @@ private Predicate> defaultMultipartContentType() { private Map sanitizedHeaders(HttpCommonRequest request) { var headers = Optional.ofNullable(request.getHeaders()).map(HashMap::new).orElse(new HashMap<>()); - if (headers.get(CONTENT_TYPE) == null) { - headers.remove(CONTENT_TYPE); - } + var keysToRemove = + headers.entrySet().stream() + .filter(e -> e.getKey().equalsIgnoreCase(CONTENT_TYPE) && e.getValue() == null) + .findFirst(); + keysToRemove.ifPresent(e -> headers.remove(e.getKey())); return headers; } } diff --git a/connectors/http/http-base/src/test/java/io/camunda/connector/http/base/client/apache/ApacheRequestFactoryTest.java b/connectors/http/http-base/src/test/java/io/camunda/connector/http/base/client/apache/ApacheRequestFactoryTest.java index 31adaf8311..9e57c2d1aa 100644 --- a/connectors/http/http-base/src/test/java/io/camunda/connector/http/base/client/apache/ApacheRequestFactoryTest.java +++ b/connectors/http/http-base/src/test/java/io/camunda/connector/http/base/client/apache/ApacheRequestFactoryTest.java @@ -47,6 +47,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.MockedStatic; public class ApacheRequestFactoryTest { @@ -358,6 +359,39 @@ public void shouldSetJsonBody_whenBodySupportedAndContentTypeProvidedAndBodyIsMa assertThat(jsonNode.get("key").asText()).isEqualTo("value"); } + @ParameterizedTest + @ValueSource(strings = {"content-type", "ContEnt-TyPe", "CONTENT-TYPE", "Content-type"}) + public void + shouldSetFormUrlEncodedBody_whenBodySupportedAndWrongCaseContentTypeProvidedAndBodyIsMap( + String contentTypeHeader) throws Exception { + // given request with body + HttpCommonRequest request = new HttpCommonRequest(); + request.setMethod(HttpMethod.POST); + request.setBody(Map.of("key", "value", "key2", "value2")); + request.setHeaders( + Map.of( + contentTypeHeader, + ContentType.APPLICATION_FORM_URLENCODED + .withCharset(StandardCharsets.UTF_8) + .toString())); + + // when + ClassicHttpRequest httpRequest = ApacheRequestFactory.get().createHttpRequest(request); + + // then + assertThat(httpRequest.getEntity()).isNotNull(); + assertThat(httpRequest.getEntity().getContentLength()).isGreaterThan(0); + assertThat(httpRequest.getEntity().getContentType()) + .isEqualTo( + ContentType.APPLICATION_FORM_URLENCODED + .withCharset(StandardCharsets.UTF_8) + .toString()); + String content = new String(httpRequest.getEntity().getContent().readAllBytes()); + assertThat(content).contains("key=value"); + assertThat(content).contains("key2=value2"); + assertThat(content).contains("&"); + } + @Test public void shouldSetFormUrlEncodedBody_whenBodySupportedAndContentTypeProvidedAndBodyIsMap() throws Exception { diff --git a/connectors/twilio/element-templates/twilio-connector.json b/connectors/twilio/element-templates/twilio-connector.json index f9b386f5dc..584458834b 100644 --- a/connectors/twilio/element-templates/twilio-connector.json +++ b/connectors/twilio/element-templates/twilio-connector.json @@ -2,7 +2,7 @@ "$schema": "https://unpkg.com/@camunda/zeebe-element-templates-json-schema/resources/schema.json", "name": "Twilio Outbound Connector", "id": "io.camunda.connectors.Twilio.v1", - "version": 3, + "version": 4, "description": "Send SMS messages or retrieve message information using the Twilio API", "icon": { "contents": "data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' preserveAspectRatio='xMidYMid' viewBox='0 0 256 256' id='twilio'%3E%3Cg fill='%23CF272D'%3E%3Cpath d='M127.86 222.304c-52.005 0-94.164-42.159-94.164-94.163 0-52.005 42.159-94.163 94.164-94.163 52.004 0 94.162 42.158 94.162 94.163 0 52.004-42.158 94.163-94.162 94.163zm0-222.023C57.245.281 0 57.527 0 128.141 0 198.756 57.245 256 127.86 256c70.614 0 127.859-57.244 127.859-127.859 0-70.614-57.245-127.86-127.86-127.86z'%3E%3C/path%3E%3Cpath d='M133.116 96.297c0-14.682 11.903-26.585 26.586-26.585 14.683 0 26.585 11.903 26.585 26.585 0 14.684-11.902 26.586-26.585 26.586-14.683 0-26.586-11.902-26.586-26.586M133.116 159.983c0-14.682 11.903-26.586 26.586-26.586 14.683 0 26.585 11.904 26.585 26.586 0 14.683-11.902 26.586-26.585 26.586-14.683 0-26.586-11.903-26.586-26.586M69.431 159.983c0-14.682 11.904-26.586 26.586-26.586 14.683 0 26.586 11.904 26.586 26.586 0 14.683-11.903 26.586-26.586 26.586-14.682 0-26.586-11.903-26.586-26.586M69.431 96.298c0-14.683 11.904-26.585 26.586-26.585 14.683 0 26.586 11.902 26.586 26.585 0 14.684-11.903 26.586-26.586 26.586-14.682 0-26.586-11.902-26.586-26.586'%3E%3C/path%3E%3C/g%3E%3C/svg%3E" @@ -156,7 +156,7 @@ }, { "type": "Hidden", - "value": "={\"content-type\":\"application/x-www-form-urlencoded\"}", + "value": "={\"Content-Type\":\"application/x-www-form-urlencoded\"}", "binding": { "type": "zeebe:input", "name": "headers"