From 5221dc8248a8fb501f49cf4bc50cf81e807d29d0 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Mon, 23 Feb 2026 14:02:23 -0300 Subject: [PATCH] improve HTTP body pcap formatting --- internet/pcap/capture.go | 38 ++++++++++++++++++++++++++++++++++- internet/pcap/capture_test.go | 17 ++++++++-------- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/internet/pcap/capture.go b/internet/pcap/capture.go index 0a066f6..6bce41b 100644 --- a/internet/pcap/capture.go +++ b/internet/pcap/capture.go @@ -2,6 +2,7 @@ package pcap //go:generate stringer -type=FieldClass -linecomment -output stringers.go . import ( + "bytes" "encoding/binary" "errors" "fmt" @@ -554,6 +555,39 @@ func (pc *PacketBreakdown) CaptureDHCPv4(dst []Frame, pkt []byte, bitOffset int) return dst, nil } +// httpBodyClass returns FieldClassText if the HTTP body appears to be human-readable text +// based on the Content-Type header, falling back to byte inspection when Content-Type is absent. +func httpBodyClass(contentType, body []byte) FieldClass { + if len(contentType) > 0 { + // Strip parameters (e.g. "; charset=utf-8") for media type matching. + mediaType := contentType + if i := bytes.IndexByte(contentType, ';'); i >= 0 { + mediaType = contentType[:i] + } + mediaType = bytes.TrimSpace(mediaType) + switch { + case bytes.HasPrefix(mediaType, []byte("text/")): + return FieldClassText + case bytes.Equal(mediaType, []byte("application/json")), + bytes.Equal(mediaType, []byte("application/javascript")), + bytes.Equal(mediaType, []byte("application/xml")): + return FieldClassText + case bytes.HasSuffix(mediaType, []byte("+json")), + bytes.HasSuffix(mediaType, []byte("+xml")): + return FieldClassText + } + return FieldClassPayload + } + // No Content-Type: inspect bytes for printable ASCII. + for _, c := range body { + if c >= 32 && c <= 126 || c == '\t' || c == '\n' || c == '\r' { + continue + } + return FieldClassPayload + } + return FieldClassText +} + func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) { const httpProtocol = "HTTP" if bitOffset%8 != 0 { @@ -573,6 +607,7 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ( } hdrLen := pc.hdr.BufferParsed() body, _ := pc.hdr.Body() + bodyClass := httpBodyClass(pc.hdr.Get("Content-Type"), body) dst = append(dst, Frame{ Protocol: httpProtocol, PacketBitOffset: bitOffset, @@ -584,7 +619,8 @@ func (pc *PacketBreakdown) CaptureHTTP(dst []Frame, pkt []byte, bitOffset int) ( BitLength: hdrLen * octet, }, { - Class: FieldClassPayload, + Name: "HTTP Body", + Class: bodyClass, FrameBitOffset: hdrLen * octet, BitLength: len(body) * octet, }, diff --git a/internet/pcap/capture_test.go b/internet/pcap/capture_test.go index ab45e5f..f1ae4ac 100644 --- a/internet/pcap/capture_test.go +++ b/internet/pcap/capture_test.go @@ -85,13 +85,14 @@ func TestCap(t *testing.T) { } return math.MaxUint64 } - getClassData := func(frame Frame, class FieldClass) []byte { - idx, err := frame.FieldByClass(class) - if err != nil { - return nil + getNameData := func(frame Frame, name string) []byte { + for i := range frame.Fields { + if frame.Fields[i].Name == name { + v, _ := frame.AppendField(nil, i, pkt) + return v + } } - v, _ := frame.AppendField(nil, idx, pkt) - return v + return nil } efrm, _ := ethernet.NewFrame(pkt) pefrm := frames[0] @@ -143,11 +144,11 @@ func TestCap(t *testing.T) { if gotHeaderLen != uint64(wantHeaderLen) { t.Errorf("want %d TCP header length, got %d", wantHeaderLen, gotHeaderLen) } - gotHttpHeader := string(getClassData(phfrm, FieldClassText)) + gotHttpHeader := string(getNameData(phfrm, "HTTP Header")) if !strings.HasPrefix(gotHttpHeader, httpProtocol) { t.Errorf("want HTTP header starting with %q, got %q", httpProtocol, gotHttpHeader) } - gotBody := getClassData(phfrm, FieldClassPayload) + gotBody := getNameData(phfrm, "HTTP Body") if string(gotBody) != httpBody { t.Errorf("want %q HTTP body, got %q", httpBody, gotBody) }