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: