diff --git a/browsermob-core/pom.xml b/browsermob-core/pom.xml
index 2071434ee..1bac1fb7d 100644
--- a/browsermob-core/pom.xml
+++ b/browsermob-core/pom.xml
@@ -245,5 +245,17 @@
hamcrest-library
test
+
+ org.meteogroup.jbrotli
+ jbrotli-servlet
+ 0.5.0
+
+
+
+ bintray-nitram509-jbrotli
+ bintray
+ http://dl.bintray.com/nitram509/jbrotli
+
+
\ No newline at end of file
diff --git a/browsermob-core/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java b/browsermob-core/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java
index d69ad8f76..1bce61ad7 100644
--- a/browsermob-core/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java
+++ b/browsermob-core/src/main/java/net/lightbody/bmp/filters/ServerResponseCaptureFilter.java
@@ -25,6 +25,7 @@
*/
public class ServerResponseCaptureFilter extends HttpFiltersAdapter {
private static final Logger log = LoggerFactory.getLogger(ServerResponseCaptureFilter.class);
+ private static final String BROTLI_COMPRESSION = "br";
/**
* Populated by serverToProxyResponse() when processing the HttpResponse object
@@ -133,6 +134,13 @@ protected void decompressContents() {
} catch (RuntimeException e) {
log.warn("Failed to decompress response with encoding type " + contentEncoding + " when decoding request from " + originalRequest.getUri(), e);
}
+ } else if(contentEncoding.equals(BROTLI_COMPRESSION)) {
+ try {
+ fullResponseContents = BrowserMobHttpUtil.decompressBrotliContents(getRawResponseContents());
+ decompressionSuccessful = true;
+ } catch (RuntimeException e) {
+ log.warn("Failed to decompress response with encoding type " + contentEncoding + " when decoding request from " + originalRequest.getUri(), e);
+ }
} else {
log.warn("Cannot decode unsupported content encoding type {}", contentEncoding);
}
diff --git a/browsermob-core/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java b/browsermob-core/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java
index 98172810f..9af7734a2 100644
--- a/browsermob-core/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java
+++ b/browsermob-core/src/main/java/net/lightbody/bmp/util/BrowserMobHttpUtil.java
@@ -9,12 +9,15 @@
import io.netty.handler.codec.http.HttpResponse;
import net.lightbody.bmp.exception.DecompressionException;
import net.lightbody.bmp.exception.UnsupportedCharsetException;
+import org.meteogroup.jbrotli.io.BrotliInputStream;
+import org.meteogroup.jbrotli.libloader.BrotliLibraryLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
@@ -111,6 +114,47 @@ public static byte[] decompressContents(byte[] fullMessage) throws Decompression
return fullMessage;
}
+ /**
+ * Decompresses the brotli byte stream.
+ *
+ * @param fullMessage brotli byte stream to decompress
+ * @return decompressed bytes
+ * @throws DecompressionException thrown if the fullMessage cannot be read or decompressed for any reason
+ */
+ public static byte[] decompressBrotliContents(byte[] fullMessage) throws DecompressionException {
+ BrotliLibraryLoader.loadBrotli();
+
+ InputStream brotliInputReader = null;
+ ByteArrayOutputStream uncompressed = null;
+
+ try {
+ brotliInputReader = new BrotliInputStream(new ByteArrayInputStream(fullMessage));
+
+ uncompressed = new ByteArrayOutputStream();
+
+ byte[] decompressBuffer = new byte[DECOMPRESS_BUFFER_SIZE];
+ int bytesRead;
+ while ((bytesRead = brotliInputReader.read(decompressBuffer, 0, decompressBuffer.length)) != -1) {
+ uncompressed.write(decompressBuffer, 0, bytesRead);
+ }
+
+ uncompressed.flush();
+ fullMessage = uncompressed.toByteArray();
+ } catch (IOException e) {
+ throw new DecompressionException("Unable to decompress response", e);
+ } finally {
+ try {
+ if (brotliInputReader != null) {
+ brotliInputReader.close();
+ }
+ } catch (IOException e) {
+ log.warn("Unable to close brotli stream", e);
+ }
+ }
+
+ return fullMessage;
+ }
+
/**
* Returns true if the content type string indicates textual content. Currently these are any Content-Types that start with one of the
* following: