CXF-9145: Inconcise handling of logging features logMulitpart and logBinary (#2520)
diff --git a/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/AbstractLoggingInterceptor.java b/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/AbstractLoggingInterceptor.java
index 9713358..67e2763 100644
--- a/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/AbstractLoggingInterceptor.java
+++ b/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/AbstractLoggingInterceptor.java
@@ -153,16 +153,21 @@
String[] parts = originalLogString.split(Pattern.quote(boundary));
String payload = "";
for (String str : parts) {
- if (findContentType(str) != null
+ final String contentType = findContentType(str);
+ if (contentType != null
&& eventMapper.isBinaryContent(
- findContentType(str).substring("Content-Type:".length()).trim())) {
- payload = payload + "\r\n" + CONTENT_SUPPRESSED + "\r\n";
+ contentType.substring("Content-Type:".length()).trim())) {
+ final String headers = extractHeaders(str);
+ if (headers == null || headers.isEmpty()) {
+ payload = payload + "\r\n" + CONTENT_SUPPRESSED + "\r\n";
+ } else {
+ payload = payload + "\r\n" + headers + "\r\n" + CONTENT_SUPPRESSED + "\r\n";
+ }
} else {
payload = payload + str;
payload = payload + boundary;
}
}
-
originalLogString = payload;
}
} catch (Exception ex) {
@@ -172,6 +177,43 @@
}
+ private static String extractHeaders(String str) {
+ // We know that content header is present, let's start from that
+ final Matcher m = CONTENT_TYPE_PATTERN.matcher(str);
+ if (m.find()) {
+ int payloadStart = 0;
+
+ // Look for empty line to find out where the actual payload starts (it should
+ // follow headers)
+ int buffer = 0;
+ for (int i = m.start(0); i < str.length(); ++i) {
+ final char c = str.charAt(i);
+ // a linefeed is a terminator, always.
+ if (c == '\n') {
+ if (buffer == 0) {
+ payloadStart = i;
+ break;
+ } else {
+ buffer = 0;
+ }
+ } else if (c == '\r') {
+ //just ignore the CR. The next character SHOULD be an NL. If not, we're
+ //just going to discard this
+ continue;
+ } else {
+ // just count is as a buffer
+ ++buffer;
+ }
+ }
+
+ if (payloadStart > 0 && payloadStart < str.length()) {
+ return str.substring(0, payloadStart).trim();
+ }
+ }
+
+ return null;
+ }
+
private String findContentType(String payload) {
// Use regex to get the Content-Type and return null if it's not found
Matcher m = CONTENT_TYPE_PATTERN.matcher(payload);
diff --git a/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/DefaultLogEventMapper.java b/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/DefaultLogEventMapper.java
index 4e8ee74..ee3a386 100644
--- a/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/DefaultLogEventMapper.java
+++ b/rt/features/logging/src/main/java/org/apache/cxf/ext/logging/event/DefaultLogEventMapper.java
@@ -234,11 +234,22 @@
private boolean isBinaryContent(Message message) {
String contentType = safeGet(message, Message.CONTENT_TYPE);
- return contentType != null && binaryContentMediaTypes.contains(contentType);
+ return isBinaryContent(contentType);
}
public boolean isBinaryContent(String contentType) {
- return contentType != null && binaryContentMediaTypes.contains(contentType);
+ if (contentType == null) {
+ return false;
+ } else {
+ // Consider compound header values, like:
+ // Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
+ final int index = contentType.indexOf(';');
+ if (index > 0) {
+ return binaryContentMediaTypes.contains(contentType.substring(0, index).trim());
+ } else {
+ return binaryContentMediaTypes.contains(contentType);
+ }
+ }
}
private boolean isMultipartContent(Message message) {
diff --git a/rt/features/logging/src/test/java/org/apache/cxf/ext/logging/LoggingInInterceptorTest.java b/rt/features/logging/src/test/java/org/apache/cxf/ext/logging/LoggingInInterceptorTest.java
index fda5b7b..d7b7578 100644
--- a/rt/features/logging/src/test/java/org/apache/cxf/ext/logging/LoggingInInterceptorTest.java
+++ b/rt/features/logging/src/test/java/org/apache/cxf/ext/logging/LoggingInInterceptorTest.java
@@ -19,6 +19,9 @@
package org.apache.cxf.ext.logging;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -27,6 +30,7 @@
import java.util.Set;
import org.apache.cxf.ext.logging.event.LogEvent;
+import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
@@ -36,6 +40,7 @@
import static org.apache.cxf.ext.logging.event.DefaultLogEventMapper.MASKED_HEADER_VALUE;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals;
@@ -114,4 +119,119 @@
assertEquals(TEST_HEADER_VALUE, event.getHeaders().get(TEST_HEADER_NAME));
}
+
+ @Test
+ public void shouldLogMultipartPayload() throws IOException {
+ message.put(Message.ENDPOINT_ADDRESS, "http://localhost:9001/");
+ message.put(Message.REQUEST_URI, "/api");
+
+ StringBuilder buf = new StringBuilder(512);
+ buf.append("------=_Part_0_2180223.1203118300920\n");
+ buf.append("Content-Type: application/xop+xml; charset=UTF-8; type=\"text/xml\"\n");
+ buf.append("Content-Transfer-Encoding: 8bit\n");
+ buf.append("Content-ID: <soap.xml@xfire.codehaus.org>\n");
+ buf.append('\n');
+ buf.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
+ + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
+ + "<soap:Body><getNextMessage xmlns=\"http://foo.bar\" /></soap:Body>"
+ + "</soap:Envelope>\n");
+ buf.append("------=_Part_0_2180223.1203118300920--\n");
+
+ String ct = "multipart/related; type=\"application/xop+xml\"; "
+ + "boundary=\"----=_Part_0_2180223.1203118300920\"";
+
+ final byte[] bytes = buf.toString().getBytes(StandardCharsets.UTF_8);
+ final OutputStream os = new CachedOutputStream();
+ os.write(bytes, 0, bytes.length);
+ message.setContent(CachedOutputStream.class, os);
+ message.put(Message.CONTENT_TYPE, ct);
+
+ interceptor.addBinaryContentMediaTypes("application/xop+xml");
+ interceptor.setLogMultipart(true);
+ interceptor.setLogBinary(true);
+ interceptor.handleMessage(message);
+
+ assertThat(sender.getEvents(), hasSize(1));
+ final LogEvent event = sender.getEvents().get(0);
+
+ assertThat(event.getPayload(), equalToIgnoringCase(buf.toString()));
+ }
+
+ @Test
+ public void shouldLogMultipartHeadersOnly() throws IOException {
+ message.put(Message.ENDPOINT_ADDRESS, "http://localhost:9001/");
+ message.put(Message.REQUEST_URI, "/api");
+
+ final StringBuilder headers = new StringBuilder(512);
+ headers.append("------=_Part_0_2180223.1203118300920\n");
+ headers.append("Content-Type: application/xop+xml; charset=UTF-8; type=\"text/xml\"\n");
+ headers.append("Content-Transfer-Encoding: 8bit\n");
+ headers.append("Content-ID: <soap.xml@xfire.codehaus.org>\n");
+
+ final StringBuilder buf = new StringBuilder(512);
+ buf.append(headers);
+ buf.append('\n');
+ buf.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
+ + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
+ + "<soap:Body><getNextMessage xmlns=\"http://foo.bar\" /></soap:Body>"
+ + "</soap:Envelope>\n");
+ buf.append("------=_Part_0_2180223.1203118300920--\n");
+
+ String ct = "multipart/related; type=\"application/xop+xml\"; "
+ + "boundary=\"----=_Part_0_2180223.1203118300920\"";
+
+ final byte[] bytes = buf.toString().getBytes(StandardCharsets.UTF_8);
+ final OutputStream os = new CachedOutputStream();
+ os.write(bytes, 0, bytes.length);
+ message.setContent(CachedOutputStream.class, os);
+ message.put(Message.CONTENT_TYPE, ct);
+
+ interceptor.addBinaryContentMediaTypes("application/xop+xml");
+ interceptor.setLogMultipart(true);
+ interceptor.setLogBinary(false);
+ interceptor.handleMessage(message);
+
+ assertThat(sender.getEvents(), hasSize(1));
+ final LogEvent event = sender.getEvents().get(0);
+
+ assertThat(event.getPayload().replaceAll("\r\n", "\n"), equalToIgnoringCase(headers.toString()
+ + "--- Content suppressed ---\n--\n------=_Part_0_2180223.1203118300920"));
+ }
+
+ @Test
+ public void shouldLogMultipartPayloadNoHeaders() throws IOException {
+ message.put(Message.ENDPOINT_ADDRESS, "http://localhost:9001/");
+ message.put(Message.REQUEST_URI, "/api");
+
+ StringBuilder buf = new StringBuilder(512);
+ buf.append("------=_Part_0_2180223.1203118300920\n");
+ buf.append('\n');
+ buf.append("<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" "
+ + "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"
+ + "<soap:Body><getNextMessage xmlns=\"http://foo.bar\" /></soap:Body>"
+ + "</soap:Envelope>\n");
+ buf.append("------=_Part_0_2180223.1203118300920--\n");
+
+ String ct = "multipart/related; type=\"application/xop+xml\"; "
+ + "boundary=\"----=_Part_0_2180223.1203118300920\"";
+
+ final byte[] bytes = buf.toString().getBytes(StandardCharsets.UTF_8);
+ final OutputStream os = new CachedOutputStream();
+ os.write(bytes, 0, bytes.length);
+ message.setContent(CachedOutputStream.class, os);
+ message.put(Message.CONTENT_TYPE, ct);
+
+ interceptor.addBinaryContentMediaTypes("application/xop+xml");
+ interceptor.setLogMultipart(true);
+ interceptor.setLogBinary(true);
+ interceptor.handleMessage(message);
+
+ assertThat(sender.getEvents(), hasSize(1));
+ final LogEvent event = sender.getEvents().get(0);
+
+ assertThat(event.getPayload(), equalToIgnoringCase(buf.toString()));
+ }
}