mirror of
https://github.com/emanuele-f/PCAPdroid.git
synced 2026-06-16 21:10:57 +08:00
User can now specify text/hexdump visualization
This commit is contained in:
parent
6ffcdd41da
commit
38e24f4a1f
@ -1125,4 +1125,8 @@ public class Utils {
|
||||
icon.setColorFilter(ContextCompat.getColor(context, color));
|
||||
icon.setImageDrawable(ContextCompat.getDrawable(context, resid));
|
||||
}
|
||||
|
||||
public static boolean isPrintable(byte c) {
|
||||
return ((c >= 32) && (c <= 126)) || (c == '\r') || (c == '\n') || (c == '\t');
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
|
||||
package com.emanuelef.remote_capture.adapters;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@ -63,6 +64,7 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
|
||||
private final ArrayList<AdapterChunk> mChunks = new ArrayList<>();
|
||||
private final HTTPReassembly mHttpReq;
|
||||
private final HTTPReassembly mHttpRes;
|
||||
private boolean mShowAsPrintable;
|
||||
|
||||
public PayloadAdapter(Context context, ConnectionDescriptor conn, ChunkType mode) {
|
||||
mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
@ -111,15 +113,13 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
|
||||
private void makeText() {
|
||||
int dump_len = mIsExpanded ? mChunk.payload.length : Math.min(mChunk.payload.length, COLLAPSE_CHUNK_SIZE);
|
||||
|
||||
if(mMode == ChunkType.RAW)
|
||||
if(!mShowAsPrintable)
|
||||
mTheText = Utils.hexdump(mChunk.payload, 0, dump_len);
|
||||
else
|
||||
mTheText = new String(mChunk.payload, 0, dump_len, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
void expand() {
|
||||
assert(!mIsExpanded);
|
||||
|
||||
mIsExpanded = true;
|
||||
makeText();
|
||||
|
||||
@ -129,8 +129,6 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
|
||||
|
||||
// collapses the item and returns the old number of pages
|
||||
void collapse() {
|
||||
assert(mIsExpanded);
|
||||
|
||||
mIsExpanded = false;
|
||||
mTheText = null;
|
||||
|
||||
@ -307,6 +305,19 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
|
||||
return mChunks.get(i).getPage(pageIdx);
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
public void setDisplayAsPrintableText(boolean asText) {
|
||||
if(mShowAsPrintable != asText) {
|
||||
mShowAsPrintable = asText;
|
||||
|
||||
// Chunk pagination depends on the displayed data length, collapsing everything is simpler
|
||||
// than handling individual changes
|
||||
for(AdapterChunk chunk: mChunks)
|
||||
chunk.collapse(); // resets the chunk text
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int getAdapterPosition(AdapterChunk chunk) {
|
||||
int i;
|
||||
int count = 0;
|
||||
|
||||
@ -22,6 +22,9 @@ package com.emanuelef.remote_capture.fragments;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
@ -31,6 +34,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.emanuelef.remote_capture.R;
|
||||
import com.emanuelef.remote_capture.Utils;
|
||||
import com.emanuelef.remote_capture.activities.ConnectionDetailsActivity;
|
||||
import com.emanuelef.remote_capture.adapters.PayloadAdapter;
|
||||
import com.emanuelef.remote_capture.model.ConnectionDescriptor;
|
||||
@ -43,6 +47,9 @@ public class ConnectionPayload extends Fragment implements ConnectionDetailsActi
|
||||
private PayloadAdapter mAdapter;
|
||||
private TextView mTruncatedWarning;
|
||||
private int mCurChunks;
|
||||
private Menu mMenu;
|
||||
private boolean mJustCreated;
|
||||
private boolean mShowAsPrintable;
|
||||
|
||||
public static ConnectionPayload newInstance(PayloadChunk.ChunkType mode) {
|
||||
ConnectionPayload fragment = new ConnectionPayload();
|
||||
@ -70,6 +77,7 @@ public class ConnectionPayload extends Fragment implements ConnectionDetailsActi
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
setHasOptionsMenu(true);
|
||||
return inflater.inflate(R.layout.connection_payload, container, false);
|
||||
}
|
||||
|
||||
@ -94,10 +102,83 @@ public class ConnectionPayload extends Fragment implements ConnectionDetailsActi
|
||||
mAdapter = new PayloadAdapter(requireContext(), mConn, mode);
|
||||
mCurChunks = mConn.getNumPayloadChunks();
|
||||
recyclerView.setAdapter(mAdapter);
|
||||
mJustCreated = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.connection_payload, menu);
|
||||
mMenu = menu;
|
||||
if((mCurChunks > 0) && mJustCreated) {
|
||||
mShowAsPrintable = guessDisplayAsPrintable();
|
||||
mAdapter.setDisplayAsPrintableText(mShowAsPrintable);
|
||||
mJustCreated = false;
|
||||
}
|
||||
refreshDisplayMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
if(id == R.id.printable_text) {
|
||||
mShowAsPrintable = true;
|
||||
mAdapter.setDisplayAsPrintableText(true);
|
||||
refreshDisplayMode();
|
||||
return true;
|
||||
} else if(id == R.id.hexdump) {
|
||||
mShowAsPrintable = false;
|
||||
mAdapter.setDisplayAsPrintableText(false);
|
||||
refreshDisplayMode();
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private boolean guessDisplayAsPrintable() {
|
||||
// try to determine the best mode based on the current payload
|
||||
if(mConn.getNumPayloadChunks() == 0)
|
||||
return mConn.l7proto.equals("HTTPS");
|
||||
|
||||
PayloadChunk firstChunk = mConn.getPayloadChunk(0);
|
||||
if(firstChunk.type == PayloadChunk.ChunkType.HTTP)
|
||||
return true;
|
||||
|
||||
// guess based on the actual data
|
||||
int maxLen = Math.min(firstChunk.payload.length, 16);
|
||||
for(int i=0; i<maxLen; i++) {
|
||||
if(!Utils.isPrintable(firstChunk.payload[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void refreshDisplayMode() {
|
||||
if(mMenu == null)
|
||||
return;
|
||||
|
||||
MenuItem printableText = mMenu.findItem(R.id.printable_text);
|
||||
MenuItem hexdump = mMenu.findItem(R.id.hexdump);
|
||||
|
||||
// important: the checked item must first be unchecked
|
||||
if(mShowAsPrintable) {
|
||||
hexdump.setChecked(false);
|
||||
printableText.setChecked(true);
|
||||
} else {
|
||||
printableText.setChecked(false);
|
||||
hexdump.setChecked(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionUpdated() {
|
||||
if(mCurChunks == 0) {
|
||||
mShowAsPrintable = guessDisplayAsPrintable();
|
||||
mAdapter.setDisplayAsPrintableText(mShowAsPrintable);
|
||||
}
|
||||
|
||||
if(mConn.getNumPayloadChunks() > mCurChunks) {
|
||||
mAdapter.handleChunksAdded(mConn.getNumPayloadChunks());
|
||||
mCurChunks = mConn.getNumPayloadChunks();
|
||||
|
||||
5
app/src/main/res/drawable/ic_short_text.xml
Normal file
5
app/src/main/res/drawable/ic_short_text.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M4,9h16v2H4V9zM4,13h10v2H4V13z"/>
|
||||
</vector>
|
||||
20
app/src/main/res/menu/connection_payload.xml
Normal file
20
app/src/main/res/menu/connection_payload.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:title="@string/display_as"
|
||||
android:icon="@drawable/ic_short_text"
|
||||
app:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<group android:id="@+id/display_as_grp" android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/printable_text"
|
||||
android:title="@string/printable_text" />
|
||||
<item
|
||||
android:id="@+id/hexdump"
|
||||
android:title="@string/hexdump" />
|
||||
</group>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
@ -314,4 +314,7 @@
|
||||
<string name="network_traffic">Traffic</string>
|
||||
<string name="warn_no_app_data">No application data has been exchanged</string>
|
||||
<string name="waiting_application_data">Waiting data</string>
|
||||
<string name="display_as">Display as…</string>
|
||||
<string name="printable_text">Printable text</string>
|
||||
<string name="hexdump">Hexdump</string>
|
||||
</resources>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user