From af19dd19774821ae27aa5c41182a088ec437ab36 Mon Sep 17 00:00:00 2001 From: Anders Jaensson Date: Mon, 10 Oct 2022 10:01:05 +0200 Subject: [PATCH 1/3] chore: disable owasp dependency check plugin. This is handled by mend. --- logbook-parent/pom.xml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/logbook-parent/pom.xml b/logbook-parent/pom.xml index f4b4dad12..c7d843f20 100644 --- a/logbook-parent/pom.xml +++ b/logbook-parent/pom.xml @@ -498,25 +498,6 @@ false - - org.owasp - dependency-check-maven - 6.5.1 - - - - check - - - - - true - false - - cve-suppressions.xml - - - org.apache.maven.plugins maven-shade-plugin From 545eaf6d15f96b5e4d96517bd50000a04ca597ed Mon Sep 17 00:00:00 2001 From: Anders Jaensson Date: Mon, 10 Oct 2022 10:01:31 +0200 Subject: [PATCH 2/3] chore: bump a few dependencies --- logbook-parent/pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/logbook-parent/pom.xml b/logbook-parent/pom.xml index c7d843f20..6cfaf2921 100644 --- a/logbook-parent/pom.xml +++ b/logbook-parent/pom.xml @@ -35,7 +35,7 @@ 2.7.0 1.6.10 1.6.7 - 4.1.74.Final + 4.1.82.Final 1.0.14 1.7.36 @@ -43,13 +43,13 @@ 1.5.22.RELEASE 4.2.20.RELEASE - 5.3.16 + 5.3.23 2.6.4 - 5.6.0 + 5.6.7 - 5.3.16 + 5.3.23 2.6.4 - 5.6.2 + 5.6.7 5.8.2 4.3.1 From ca0aed43034b3edfdf34c2b48395526289587224 Mon Sep 17 00:00:00 2001 From: Anders Jaensson Date: Mon, 10 Oct 2022 10:04:18 +0200 Subject: [PATCH 3/3] chore: make sure only maxSize number of bytes from the response are read instead of reading all and truncating --- .../logbook/httpclient5/HttpEntities.java | 4 +- .../logbook/httpclient5/LocalRequest.java | 2 +- .../LogbookHttpAsyncResponseConsumer.java | 2 +- .../LogbookHttpResponseInterceptor.java | 10 ++- .../logbook/httpclient5/RemoteResponse.java | 19 ++-- .../httpclient5/LimitedResponseSizeTest.java | 86 +++++++++++++++++++ .../logbook/spring/RemoteResponseTest.java | 5 +- 7 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 logbook-httpclient5/src/test/java/org/zalando/logbook/httpclient5/LimitedResponseSizeTest.java diff --git a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/HttpEntities.java b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/HttpEntities.java index 27c23a160..854a12708 100644 --- a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/HttpEntities.java +++ b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/HttpEntities.java @@ -19,8 +19,8 @@ interface Copy extends HttpEntity { byte[] getBody(); } - Copy copy(final HttpEntity entity) throws IOException { - final byte[] body = toByteArray(entity); + Copy copy(final HttpEntity entity, int sizeLimit) throws IOException { + final byte[] body = toByteArray(entity, sizeLimit); ContentType contentType = ContentType.parse(entity.getContentType()); boolean chunked = entity.isChunked(); String contentEncoding = entity.getContentEncoding(); diff --git a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LocalRequest.java b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LocalRequest.java index b35e5e032..53573633d 100644 --- a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LocalRequest.java +++ b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LocalRequest.java @@ -76,7 +76,7 @@ public State buffer(final HttpRequest request, final EntityDetails entity) throw if (httpEntity == null) { return new Passing(); } else { - final HttpEntities.Copy copy = HttpEntities.copy(httpEntity); + final HttpEntities.Copy copy = HttpEntities.copy(httpEntity, Integer.MAX_VALUE); original.setEntity(copy); return new Buffering(copy.getBody()); } diff --git a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpAsyncResponseConsumer.java b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpAsyncResponseConsumer.java index 9f8d3cea5..08982f528 100644 --- a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpAsyncResponseConsumer.java +++ b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpAsyncResponseConsumer.java @@ -46,7 +46,7 @@ public void consumeResponse(HttpResponse response, EntityDetails entityDetails, @Override public void consume(ByteBuffer src) throws IOException { - stage.process(new RemoteResponse(this.response, this.entityDetails, src)).write(); + stage.process(new RemoteResponse(this.response, this.entityDetails, src, Integer.MAX_VALUE)).write(); delegate().consume(src); } diff --git a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpResponseInterceptor.java b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpResponseInterceptor.java index 0ea3b037b..628e0246a 100644 --- a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpResponseInterceptor.java +++ b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpResponseInterceptor.java @@ -21,11 +21,19 @@ */ @API(status = EXPERIMENTAL) public final class LogbookHttpResponseInterceptor implements HttpResponseInterceptor { + final int maxResponseSize; + public LogbookHttpResponseInterceptor() { + maxResponseSize = Integer.MAX_VALUE; + } + + public LogbookHttpResponseInterceptor(int maxResponseSize) { + this.maxResponseSize = maxResponseSize; + } @Override public void process(HttpResponse original, EntityDetails entity, HttpContext context) throws IOException { final ResponseProcessingStage stage = find(context); - stage.process(new RemoteResponse(original)).write(); + stage.process(new RemoteResponse(original, maxResponseSize)).write(); } private ResponseProcessingStage find(final HttpContext context) { diff --git a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/RemoteResponse.java b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/RemoteResponse.java index 748fd8557..e126ba4fc 100644 --- a/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/RemoteResponse.java +++ b/logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/RemoteResponse.java @@ -25,11 +25,20 @@ final class RemoteResponse implements org.zalando.logbook.HttpResponse { private final HttpResponse response; private final EntityDetails entityDetails; private final ByteBuffer body; + private final int sizeLimit; RemoteResponse(HttpResponse response) { this.response = response; this.body = null; this.entityDetails = null; + this.sizeLimit = Integer.MAX_VALUE; + } + + RemoteResponse(HttpResponse response, int sizeLimit) { + this.response = response; + this.body = null; + this.entityDetails = null; + this.sizeLimit = sizeLimit; } private interface State { @@ -42,7 +51,7 @@ default State without() { return this; } - default State buffer(final HttpResponse response) throws IOException { + default State buffer(final HttpResponse response, int sizeLimit) throws IOException { return this; } @@ -73,14 +82,14 @@ public State without() { } @Override - public State buffer(final HttpResponse response) throws IOException { + public State buffer(final HttpResponse response, int sizeLimit) throws IOException { if (response instanceof ClassicHttpResponse) { ClassicHttpResponse classicResponse = (ClassicHttpResponse) response; - HttpEntity entity = (classicResponse).getEntity(); + HttpEntity entity = classicResponse.getEntity(); if (entity == null) { return new Passing(); } else { - final HttpEntities.Copy copy = HttpEntities.copy(entity); + final HttpEntities.Copy copy = HttpEntities.copy(entity, sizeLimit); classicResponse.setEntity(copy); return new Buffering(copy.getBody()); } @@ -207,7 +216,7 @@ public RemoteResponse withoutBody() { @Override public byte[] getBody() { - return state.updateAndGet(throwingUnaryOperator(state -> (body != null) ? state.buffer(Objects.requireNonNull(entityDetails), body) : state.buffer(response))).getBody(); + return state.updateAndGet(throwingUnaryOperator(state -> (body != null) ? state.buffer(Objects.requireNonNull(entityDetails), body) : state.buffer(response, sizeLimit))).getBody(); } } diff --git a/logbook-httpclient5/src/test/java/org/zalando/logbook/httpclient5/LimitedResponseSizeTest.java b/logbook-httpclient5/src/test/java/org/zalando/logbook/httpclient5/LimitedResponseSizeTest.java new file mode 100644 index 000000000..0e27f6bca --- /dev/null +++ b/logbook-httpclient5/src/test/java/org/zalando/logbook/httpclient5/LimitedResponseSizeTest.java @@ -0,0 +1,86 @@ +package org.zalando.logbook.httpclient5; + +import com.github.restdriver.clientdriver.ClientDriver; +import com.github.restdriver.clientdriver.ClientDriverFactory; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.zalando.logbook.Correlation; +import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; +import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.Logbook; +import org.zalando.logbook.TestStrategy; + +import java.io.IOException; + +import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.GET; +import static com.github.restdriver.clientdriver.RestClientDriver.giveResponse; +import static com.github.restdriver.clientdriver.RestClientDriver.onRequestTo; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class LimitedResponseSizeTest { + + private static final int MAX_RESPONSE_SIZE = 10; + final ClientDriver driver = new ClientDriverFactory().createClientDriver(); + + final HttpLogWriter writer = mock(HttpLogWriter.class); + + protected final Logbook logbook = Logbook.builder() + .strategy(new TestStrategy()) + .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) + .build(); + + private final CloseableHttpClient client = HttpClientBuilder.create() + .addRequestInterceptorFirst(new LogbookHttpRequestInterceptor(logbook)) + .addResponseInterceptorFirst(new LogbookHttpResponseInterceptor(MAX_RESPONSE_SIZE)) + .build(); + + @BeforeEach + void defaultBehaviour() { + when(writer.isActive()).thenReturn(true); + } + + @AfterEach + void stop() throws IOException { + client.close(); + } + + @Test + void shouldLogRequestWithoutBody() throws IOException, ParseException { + driver.addExpectation(onRequestTo("/").withMethod(GET), + giveResponse("Hello, world!", "text/plain")); + + final HttpGet request = new HttpGet(driver.getBaseUrl()); + + final CloseableHttpResponse response = client.execute(request); + + assertThat(response.getCode()).isEqualTo(200); + assertThat(response.getEntity()).isNotNull(); + final String cappedResponse = "Hello, world!".substring(0, 10); + assertThat(EntityUtils.toString(response.getEntity())).isEqualTo(cappedResponse); + + final String message = captureResponse(); + + assertThat(message) + .startsWith("Incoming Response:") + .contains("HTTP/1.1 200 OK", "Content-Type: text/plain", cappedResponse); + } + + private String captureResponse() throws IOException { + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Correlation.class), captor.capture()); + return captor.getValue(); + } +} diff --git a/logbook-spring/src/test/java/org/zalando/logbook/spring/RemoteResponseTest.java b/logbook-spring/src/test/java/org/zalando/logbook/spring/RemoteResponseTest.java index 89ef9cbfb..3395d8a35 100644 --- a/logbook-spring/src/test/java/org/zalando/logbook/spring/RemoteResponseTest.java +++ b/logbook-spring/src/test/java/org/zalando/logbook/spring/RemoteResponseTest.java @@ -2,6 +2,7 @@ import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; import org.springframework.mock.http.client.MockClientHttpResponse; import java.io.IOException; @@ -15,7 +16,7 @@ class RemoteResponseTest { @Test void statusCanThrow() throws IOException { - MockClientHttpResponse response = mock(MockClientHttpResponse.class); + ClientHttpResponse response = mock(ClientHttpResponse.class); when(response.getRawStatusCode()).thenThrow(new IOException("io exception")); assertThatThrownBy(() -> unit(response).getStatus()).hasMessageContaining("io exception"); } @@ -34,7 +35,7 @@ private MockClientHttpResponse helloWorld() { return new MockClientHttpResponse("hello world".getBytes(), HttpStatus.OK); } - private org.zalando.logbook.HttpResponse unit(MockClientHttpResponse response) { + private org.zalando.logbook.HttpResponse unit(ClientHttpResponse response) { return new RemoteResponse(response); } }