Proper handling of chunked input streams in LoggingInterceptor (#4753)
Signed-off-by: Denis Kurochkin <d.k.brazz@gmail.com>
diff --git a/core-common/pom.xml b/core-common/pom.xml
index 3288c5f..e36288f 100644
--- a/core-common/pom.xml
+++ b/core-common/pom.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
- Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
+ Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0, which is available at
@@ -211,6 +211,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<scope>test</scope>
diff --git a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java
index c90d8b6..7ad26ec 100644
--- a/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java
+++ b/core-common/src/main/java/org/glassfish/jersey/logging/LoggingInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -193,7 +193,16 @@
}
stream.mark(maxEntitySize + 1);
final byte[] entity = new byte[maxEntitySize + 1];
- final int entitySize = stream.read(entity);
+
+ int entitySize = 0;
+ while (entitySize < entity.length) {
+ int readBytes = stream.read(entity, entitySize, entity.length - entitySize);
+ if (readBytes < 0) {
+ break;
+ }
+ entitySize += readBytes;
+ }
+
b.append(new String(entity, 0, Math.min(entitySize, maxEntitySize), charset));
if (entitySize > maxEntitySize) {
b.append("...more...");
diff --git a/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java b/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java
index e4e5e91..5516273 100644
--- a/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java
+++ b/core-common/src/test/java/org/glassfish/jersey/logging/LoggingInterceptorTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -16,14 +16,27 @@
package org.glassfish.jersey.logging;
+import org.mockito.stubbing.Answer;
+
import javax.ws.rs.core.MediaType;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Random;
import org.junit.Test;
import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.HEADERS_ONLY;
import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_ANY;
import static org.glassfish.jersey.logging.LoggingFeature.Verbosity.PAYLOAD_TEXT;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
import static javax.ws.rs.core.MediaType.TEXT_HTML_TYPE;
@@ -106,4 +119,67 @@
assertFalse(LoggingInterceptor.printEntity(HEADERS_ONLY, APPLICATION_OCTET_STREAM_TYPE));
}
+ //
+ // logInboundEntity
+ //
+
+ @Test
+ public void testLogInboundEntityMockedStream() throws Exception {
+ int maxEntitySize = 20;
+ LoggingInterceptor loggingInterceptor = new LoggingInterceptor(null, null, null, maxEntitySize) {};
+
+ StringBuilder buffer = new StringBuilder();
+ InputStream stream = mock(InputStream.class);
+ when(stream.markSupported()).thenReturn(true);
+
+ when(stream.read(any(), eq(0), eq(maxEntitySize + 1)))
+ .thenAnswer(chunk(4, 'a'));
+ when(stream.read(any(), eq(4), eq(maxEntitySize + 1 - 4)))
+ .thenAnswer(chunk(3, 'b'));
+ when(stream.read(any(), eq(7), eq(maxEntitySize + 1 - 7)))
+ .thenAnswer(chunk(5, 'c'));
+ when(stream.read(any(), eq(12), eq(maxEntitySize + 1 - 12)))
+ .thenReturn(-1);
+
+ loggingInterceptor.logInboundEntity(buffer, stream, StandardCharsets.UTF_8);
+
+ assertEquals("aaaabbbccccc\n", buffer.toString());
+ verify(stream).mark(maxEntitySize + 1);
+ verify(stream).reset();
+ }
+
+ private Answer<?> chunk(int size, char filler) {
+ return invocation -> {
+ byte[] buf = invocation.getArgumentAt(0, byte[].class);
+ int offset = invocation.getArgumentAt(1, Integer.class);
+ Arrays.fill(buf, offset, offset + size, (byte) filler);
+ return size;
+ };
+ }
+
+ @Test
+ public void testLogInboundEntityRealStream() throws Exception {
+ int maxEntitySize = 2000;
+ String inputString = getRandomString(maxEntitySize * 2);
+
+ LoggingInterceptor loggingInterceptor = new LoggingInterceptor(null, null, null, maxEntitySize) {};
+ StringBuilder buffer = new StringBuilder();
+ InputStream stream = new ByteArrayInputStream(inputString.getBytes());
+
+ loggingInterceptor.logInboundEntity(buffer, stream, StandardCharsets.UTF_8);
+
+ assertEquals(inputString.substring(0, maxEntitySize) + "...more...\n", buffer.toString());
+ }
+
+ private static String getRandomString(int length) {
+ final String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 _";
+ StringBuilder result = new StringBuilder();
+
+ while (length > 0) {
+ Random rand = new Random();
+ result.append(characters.charAt(rand.nextInt(characters.length())));
+ length--;
+ }
+ return result.toString();
+ }
}
diff --git a/core-common/src/test/resources/surefire.policy b/core-common/src/test/resources/surefire.policy
index 27602ae..530db3c 100644
--- a/core-common/src/test/resources/surefire.policy
+++ b/core-common/src/test/resources/surefire.policy
@@ -29,12 +29,15 @@
grant codebase "file:${project.build.directory}/test-classes/-" {
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.lang.RuntimePermission "modifyThread";
- permission java.util.PropertyPermission "*", "write";
+ permission java.util.PropertyPermission "*", "read,write";
permission java.io.FilePermission "${java.io.tmpdir}/-", "read,write,delete";
permission java.lang.RuntimePermission "getClassLoader";
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc.*";
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
+ permission java.lang.RuntimePermission "accessDeclaredMembers";
+ permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
+ permission java.lang.RuntimePermission "reflectionFactoryAccess";
};
grant codebase "file:${project.build.directory}/classes/-" {