diff --git a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java index 5e738b341..532b80ce4 100644 --- a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java +++ b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java @@ -15,6 +15,7 @@ */ package okhttp3.logging; +import java.io.EOFException; import java.io.IOException; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; @@ -192,10 +193,14 @@ public final class HttpLoggingInterceptor implements Interceptor { } logger.log(""); - logger.log(buffer.readString(charset)); - - logger.log("--> END " + request.method() - + " (" + requestBody.contentLength() + "-byte body)"); + if (isPlaintext(buffer)) { + logger.log(buffer.readString(charset)); + logger.log("--> END " + request.method() + + " (" + requestBody.contentLength() + "-byte body)"); + } else { + logger.log("--> END " + request.method() + " (binary " + + requestBody.contentLength() + "-byte body omitted)"); + } } } @@ -239,6 +244,12 @@ public final class HttpLoggingInterceptor implements Interceptor { } } + if (!isPlaintext(buffer)) { + logger.log(""); + logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)"); + return response; + } + if (contentLength != 0) { logger.log(""); logger.log(buffer.clone().readString(charset)); @@ -251,6 +262,29 @@ public final class HttpLoggingInterceptor implements Interceptor { return response; } + /** + * Returns true if the body in question probably contains human readable text. Uses a small sample + * of code points to detect unicode control characters commonly used in binary file signatures. + */ + static boolean isPlaintext(Buffer buffer) throws EOFException { + try { + Buffer prefix = new Buffer(); + long byteCount = buffer.size() < 64 ? buffer.size() : 64; + buffer.copyTo(prefix, 0, byteCount); + for (int i = 0; i < 16; i++) { + if (prefix.exhausted()) { + break; + } + if (Character.isISOControl(prefix.readUtf8CodePoint())) { + return false; + } + } + return true; + } catch (EOFException e) { + return false; // Truncated UTF-8 sequence. + } + } + private boolean bodyEncoded(Headers headers) { String contentEncoding = headers.get("Content-Encoding"); return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); diff --git a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java index 88630b500..3159ad2f7 100644 --- a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java +++ b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java @@ -37,6 +37,7 @@ import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -638,6 +639,60 @@ public final class HttpLoggingInterceptorTest { .assertNoMoreLogs(); } + @Test public void isPlaintext() throws IOException { + assertTrue(HttpLoggingInterceptor.isPlaintext(new Buffer())); + assertTrue(HttpLoggingInterceptor.isPlaintext(new Buffer().writeUtf8("abc"))); + assertTrue(HttpLoggingInterceptor.isPlaintext(new Buffer().writeByte(0x80))); + assertFalse(HttpLoggingInterceptor.isPlaintext(new Buffer().writeByte(0x00))); + assertFalse(HttpLoggingInterceptor.isPlaintext(new Buffer().writeByte(0xc0))); + } + + @Test public void responseBodyIsBinary() throws IOException { + setLevel(Level.BODY); + Buffer buffer = new Buffer(); + buffer.writeUtf8CodePoint(0x89); + buffer.writeUtf8CodePoint(0x50); + buffer.writeUtf8CodePoint(0x4e); + buffer.writeUtf8CodePoint(0x47); + buffer.writeUtf8CodePoint(0x0d); + buffer.writeUtf8CodePoint(0x0a); + buffer.writeUtf8CodePoint(0x1a); + buffer.writeUtf8CodePoint(0x0a); + server.enqueue(new MockResponse() + .setBody(buffer) + .setHeader("Content-Type", "image/png; charset=utf-8")); + Response response = client.newCall(request().build()).execute(); + response.body().close(); + + applicationLogs + .assertLogEqual("--> GET " + url + " http/1.1") + .assertLogEqual("--> END GET") + .assertLogMatch("<-- 200 OK " + url + " \\(\\d+ms\\)") + .assertLogEqual("Content-Length: 9") + .assertLogEqual("Content-Type: image/png; charset=utf-8") + .assertLogMatch("OkHttp-Sent-Millis: \\d+") + .assertLogMatch("OkHttp-Received-Millis: \\d+") + .assertLogEqual("") + .assertLogEqual("<-- END HTTP (binary 9-byte body omitted)") + .assertNoMoreLogs(); + + networkLogs + .assertLogEqual("--> GET " + url + " http/1.1") + .assertLogEqual("Host: " + host) + .assertLogEqual("Connection: Keep-Alive") + .assertLogEqual("Accept-Encoding: gzip") + .assertLogMatch("User-Agent: okhttp/.+") + .assertLogEqual("--> END GET") + .assertLogMatch("<-- 200 OK " + url + " \\(\\d+ms\\)") + .assertLogEqual("Content-Length: 9") + .assertLogEqual("Content-Type: image/png; charset=utf-8") + .assertLogMatch("OkHttp-Sent-Millis: \\d+") + .assertLogMatch("OkHttp-Received-Millis: \\d+") + .assertLogEqual("") + .assertLogEqual("<-- END HTTP (binary 9-byte body omitted)") + .assertNoMoreLogs(); + } + private Request.Builder request() { return new Request.Builder().url(url); }