From 32b71fef6f05d60cf6e412e19736c8eeaa4e3fd6 Mon Sep 17 00:00:00 2001 From: emanuele-f Date: Sat, 26 Mar 2022 23:20:33 +0100 Subject: [PATCH] Add support for deflate and brotli content encodings --- app/build.gradle | 1 + .../remote_capture/HTTPReassembly.java | 86 +++++++++++++++---- app/src/main/res/values/strings.xml | 2 + 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1e6ddbd7..0bca29a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -79,6 +79,7 @@ dependencies { // Google implementation 'com.google.android.material:material:1.5.0' implementation 'com.google.code.gson:gson:2.8.9' + implementation 'org.brotli:dec:0.1.2' // Third-party implementation 'cat.ereza:customactivityoncrash:2.3.0' diff --git a/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java b/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java index ac9416e7..e4acc961 100644 --- a/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java +++ b/app/src/main/java/com/emanuelef/remote_capture/HTTPReassembly.java @@ -27,16 +27,20 @@ import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; +import java.util.zip.Inflater; +import org.brotli.dec.BrotliInputStream; public class HTTPReassembly { private static final String TAG = "HTTPReassembly"; private static final int MAX_HEADERS_SIZE = 1024; private boolean mReadingHeaders; - private boolean mGzipEncoding; private boolean mChunkedEncoding; + private ContentEncoding mContentEncoding; private int mContentLength; private int mHeadersSize; private final ArrayList mHeaders = new ArrayList<>(); @@ -51,9 +55,16 @@ public class HTTPReassembly { reset(); } + private enum ContentEncoding { + UNKNOWN, + GZIP, + DEFLATE, + BROTLI, + } + private void reset() { mReadingHeaders = true; - mGzipEncoding = false; + mContentEncoding = ContentEncoding.UNKNOWN; mChunkedEncoding = false; mContentLength = -1; mHeadersSize = 0; @@ -95,7 +106,19 @@ public class HTTPReassembly { String contentEncoding = line.substring(18); Log.d(TAG, "Content-Encoding: " + contentEncoding); - mGzipEncoding = contentEncoding.equals("gzip"); + switch (contentEncoding) { + case "gzip": + mContentEncoding = ContentEncoding.GZIP; + break; + case "deflate": + // test with http://carsten.codimi.de/gzip.yaws/daniels.html?deflate=on + mContentEncoding = ContentEncoding.DEFLATE; + break; + case "br": + // test with google.com + mContentEncoding = ContentEncoding.BROTLI; + break; + } } else if(line.startsWith("content-type: ")) { String contentType = line.substring(14); Log.d(TAG, "Content-Type: " + contentType); @@ -197,22 +220,9 @@ public class HTTPReassembly { PayloadChunk headers = reassembleChunks(mHeaders); PayloadChunk body = mBody.size() > 0 ? reassembleChunks(mBody) : null; - if((body != null) && mGzipEncoding) { - // Decode the body - try(GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(body.payload))) { - try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) { - byte[] buf = new byte[1024]; - int read; - - while ((read = gzipInputStream.read(buf)) != -1) - bos.write(buf, 0, read); - - body.payload = bos.toByteArray(); - } - } catch (IOException ignored) { - Log.d(TAG, "GZIP decoding failed"); - } - } + // Decode body + if((body != null) && (mContentEncoding != ContentEncoding.UNKNOWN)) + decodeBody(body); PayloadChunk to_add; @@ -241,6 +251,44 @@ public class HTTPReassembly { } } + private void decodeBody(PayloadChunk body) { + InputStream inputStream = null; + + //Log.d(TAG, "Decoding as " + mContentEncoding.name().toLowerCase()); + + try(ByteArrayInputStream bis = new ByteArrayInputStream(body.payload)) { + switch (mContentEncoding) { + case GZIP: + inputStream = new GZIPInputStream(bis); + break; + case DEFLATE: + inputStream = new InflaterInputStream(bis, new Inflater(true)); + break; + case BROTLI: + inputStream = new BrotliInputStream(bis); + break; + } + + if(inputStream != null) { + try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + byte[] buf = new byte[1024]; + int read; + + while ((read = inputStream.read(buf)) != -1) + bos.write(buf, 0, read); + + // success + body.payload = bos.toByteArray(); + } + } + } catch (IOException ignored) { + Log.d(TAG, mContentEncoding.name().toLowerCase() + " decoding failed"); + //ignored.printStackTrace(); + } finally { + Utils.safeClose(inputStream); + } + } + private PayloadChunk reassembleChunks(ArrayList chunks) { if(chunks.size() == 1) return chunks.get(0); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e47dd18..6069ca63 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,8 @@ - nDPI: LGPL-3.0\n\n - CustomActivityOnCrash: Apache-2.0\n\n - Gson: Apache-2.0\n\n + - Brotli decoder: MIT\n\n + - mitmproxy: MIT\n\n - MaxMind DB Reader: Apache-2.0\n\n - FlagKit: MIT\n\n - IP Geolocation by DB-IP\n\n