This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 180eebf592c7 CAMEL-26169: camel-sap-netweaver - keep the url query
string and support special characters in the credentials (#28945)
180eebf592c7 is described below
commit 180eebf592c7197b8d7c1507d902c8a245498b6d
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 21:15:11 2026 +0100
CAMEL-26169: camel-sap-netweaver - keep the url query string and support
special characters in the credentials (#28945)
* CAMEL-26169: camel-sap-netweaver - keep the url query string and supp…
…ort special characters in the credentials
The producer built the http endpoint uri as url + "?authUsername=...", so a
url
that already had query parameters got a second "?" and the credentials
ended up
inside the last url parameter (the server answered 401). The username and
password were also added without RAW(), so a password with & was cut there
and
a + became a space.
Now the auth parameters are appended with & when the url already has a
query,
and the username and password are wrapped in RAW().
* CAMEL-26169: camel-sap-netweaver - use RAW{...} for credentials that …
…contain a )
RAW(...) ends at a ) followed by &, so a password such as a)&b=c was cut
short and the rest leaked into the http query. A credential that contains
a ) is now passed on with the RAW{...} form.
* CAMEL-26169: camel-sap-netweaver - document the RAW syntax corner case
A credential with both a ) followed by & and a } fits neither RAW form,
note it in a code comment (review feedback).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
components/camel-sap-netweaver/pom.xml | 7 ++
.../component/sap/netweaver/NetWeaverProducer.java | 17 ++-
.../sap/netweaver/NetWeaverProducerTest.java | 118 +++++++++++++++++++++
3 files changed, 140 insertions(+), 2 deletions(-)
diff --git a/components/camel-sap-netweaver/pom.xml
b/components/camel-sap-netweaver/pom.xml
index 3c5778f8670a..9bfea473c386 100644
--- a/components/camel-sap-netweaver/pom.xml
+++ b/components/camel-sap-netweaver/pom.xml
@@ -74,6 +74,11 @@
<artifactId>camel-velocity</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -84,6 +89,8 @@
<includes>
<!-- Here we only run test of -->
<include>**/*UriConfigurationTest.java</include>
+ <!-- uses a local http server -->
+ <include>**/NetWeaverProducerTest.java</include>
</includes>
</configuration>
</plugin>
diff --git
a/components/camel-sap-netweaver/src/main/java/org/apache/camel/component/sap/netweaver/NetWeaverProducer.java
b/components/camel-sap-netweaver/src/main/java/org/apache/camel/component/sap/netweaver/NetWeaverProducer.java
index 9ff7fa8cd3d0..e4669c805e5b 100644
---
a/components/camel-sap-netweaver/src/main/java/org/apache/camel/component/sap/netweaver/NetWeaverProducer.java
+++
b/components/camel-sap-netweaver/src/main/java/org/apache/camel/component/sap/netweaver/NetWeaverProducer.java
@@ -78,8 +78,11 @@ public class NetWeaverProducer extends DefaultProducer {
@Override
protected void doStart() throws Exception {
- String url = getEndpoint().getUrl() + "?authUsername=" +
getEndpoint().getUsername() + "&authPassword="
- + getEndpoint().getPassword() + "&authMethod=Basic";
+ String url = getEndpoint().getUrl();
+ // the url may already have query parameters, and the credentials use
RAW so characters such as & and + are kept
+ url += (url.indexOf('?') != -1 ? "&" : "?")
+ + "authUsername=" + raw(getEndpoint().getUsername()) +
"&authPassword=" + raw(getEndpoint().getPassword())
+ + "&authMethod=Basic";
if (LOG.isInfoEnabled()) {
LOG.info("Creating NetWeaverProducer using url: {}",
URISupport.sanitizeUri(url));
}
@@ -88,6 +91,16 @@ public class NetWeaverProducer extends DefaultProducer {
ServiceHelper.startService(http);
}
+ private static String raw(String value) {
+ // RAW(...) ends at a ) followed by &, so a value with a ) uses the
RAW{...} form instead
+ // a value with both a ) followed by & and a } fits neither RAW form
(a limit of the RAW syntax),
+ // and is cut short at the )&
+ if (value != null && value.indexOf(')') != -1 && value.indexOf('}') ==
-1) {
+ return "RAW{" + value + "}";
+ }
+ return "RAW(" + value + ")";
+ }
+
@Override
protected void doStop() throws Exception {
ServiceHelper.stopService(http);
diff --git
a/components/camel-sap-netweaver/src/test/java/org/apache/camel/component/sap/netweaver/NetWeaverProducerTest.java
b/components/camel-sap-netweaver/src/test/java/org/apache/camel/component/sap/netweaver/NetWeaverProducerTest.java
new file mode 100644
index 000000000000..18b5872007d3
--- /dev/null
+++
b/components/camel-sap-netweaver/src/test/java/org/apache/camel/component/sap/netweaver/NetWeaverProducerTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.sap.netweaver;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpHeaders;
+import org.apache.hc.core5.http.HttpStatus;
+import org.apache.hc.core5.http.impl.bootstrap.HttpServer;
+import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class NetWeaverProducerTest extends CamelTestSupport {
+
+ private HttpServer server;
+ private volatile String credentials;
+ private volatile URI requestUri;
+
+ @Override
+ public void setupResources() throws Exception {
+ server = ServerBootstrap.bootstrap()
+ .setCanonicalHostName("localhost")
+ .register("*", (request, response, context) -> {
+ Header auth =
request.getFirstHeader(HttpHeaders.AUTHORIZATION);
+ if (auth == null) {
+ // ask for basic authentication, so the client sends
the credentials
+ response.setCode(HttpStatus.SC_UNAUTHORIZED);
+ response.setHeader(HttpHeaders.WWW_AUTHENTICATE,
"Basic realm=\"netweaver\"");
+ return;
+ }
+ credentials = new String(
+
Base64.getDecoder().decode(auth.getValue().substring("Basic ".length())),
StandardCharsets.UTF_8);
+ try {
+ requestUri = request.getUri();
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ response.setCode(HttpStatus.SC_OK);
+ response.setEntity(new
StringEntity("{\"d\":{\"Name\":\"Camel\"}}", ContentType.APPLICATION_JSON));
+ })
+ .create();
+ server.start();
+ }
+
+ @Override
+ public void cleanupResources() {
+ if (server != null) {
+ server.stop();
+ }
+ }
+
+ @Test
+ public void testUrlWithQueryParameters() {
+ String base = "http://localhost:" + server.getLocalPort() + "/odata";
+ NetWeaverEndpoint endpoint = context.getEndpoint(
+ "sap-netweaver:" + base + "?username=camel&password=secret",
NetWeaverEndpoint.class);
+ endpoint.setUrl(base + "?sap-client=100");
+
+ Object body = template.requestBodyAndHeader(endpoint, null,
NetWeaverConstants.COMMAND, "Flights");
+
+ assertThat(body).isEqualTo(Map.of("Name", "Camel"));
+ assertThat(requestUri.getPath()).isEqualTo("/odata/Flights");
+ assertThat(requestUri.getQuery()).isEqualTo("sap-client=100");
+ assertThat(credentials).isEqualTo("camel:secret");
+ }
+
+ @Test
+ public void testPasswordWithSpecialCharacters() {
+ String base = "http://localhost:" + server.getLocalPort() + "/odata";
+
+ Object body = template.requestBodyAndHeader(
+ "sap-netweaver:" + base +
"?username=camel&password=RAW(a&b=c+d)", null, NetWeaverConstants.COMMAND,
+ "Flights");
+
+ assertThat(body).isEqualTo(Map.of("Name", "Camel"));
+ assertThat(requestUri.getPath()).isEqualTo("/odata/Flights");
+ assertThat(requestUri.getQuery()).isNull();
+ assertThat(credentials).isEqualTo("camel:a&b=c+d");
+ }
+
+ @Test
+ public void testPasswordWithClosingParenthesis() {
+ String base = "http://localhost:" + server.getLocalPort() + "/odata";
+ NetWeaverEndpoint endpoint = context.getEndpoint(
+ "sap-netweaver:" + base + "?username=camel&password=secret",
NetWeaverEndpoint.class);
+ // RAW(...) would end at the ) followed by &, so such a password is
passed on with RAW{...}
+ endpoint.setPassword("a)&b=c)");
+
+ Object body = template.requestBodyAndHeader(endpoint, null,
NetWeaverConstants.COMMAND, "Flights");
+
+ assertThat(body).isEqualTo(Map.of("Name", "Camel"));
+ assertThat(requestUri.getQuery()).isNull();
+ assertThat(credentials).isEqualTo("camel:a)&b=c)");
+ }
+}