PcapFileEx.HTTP.Content (pcap_file_ex v0.5.5)

View Source

Generic HTTP body content decoder based on Content-Type.

Recursively decodes multipart bodies, JSON, and text. Unknown types remain as binary. Supports custom decoders for binary content.

Design Principles

  1. Content-Type driven - Decode strategy based on Content-Type header
  2. Recursive - Multipart parts are decoded based on their own Content-Type
  3. Safe fallback - Unknown types remain as binary (no crashes)
  4. Custom decoders - Binary content can be decoded by user-provided decoders

Custom Decoder Pipeline

Custom decoders are invoked only when built-in decoding yields {:binary, payload}:

  1. Built-in JSON decoder (application/json)
  2. Built-in text decoder (text/*)
  3. Built-in multipart parser (multipart/*)
  4. Custom decoders (if provided in opts)
  5. Binary fallback

Examples

iex> PcapFileEx.HTTP.Content.decode("application/json", ~s({"key":"value"}))
{:json, %{"key" => "value"}}

iex> PcapFileEx.HTTP.Content.decode("text/plain", "hello")
{:text, "hello"}

iex> PcapFileEx.HTTP.Content.decode("application/octet-stream", <<1, 2, 3>>)
{:binary, <<1, 2, 3>>}

Summary

Types

A multipart part with decoded body.

Functions

Decode HTTP body based on Content-Type header.

Extract boundary parameter from multipart Content-Type.

Parse MIME multipart body into raw parts.

Types

decoded()

@type decoded() ::
  {:json, map() | list()}
  | {:text, String.t()}
  | {:multipart, [part()]}
  | {:binary, binary()}
  | {:custom, term()}
  | {:decode_error, term()}

part()

@type part() :: %{
  :content_type => String.t(),
  :content_id => String.t() | nil,
  :headers => %{required(String.t()) => String.t()},
  :body => decoded(),
  optional(:body_binary) => binary()
}

A multipart part with decoded body.

When keep_binary: true is passed and a custom decoder was invoked (success or error), body_binary contains the original binary.

raw_part()

@type raw_part() :: %{headers: %{required(String.t()) => String.t()}, body: binary()}

Functions

decode(content_type, body, opts \\ [])

@spec decode(String.t() | nil, binary(), keyword()) :: decoded()

Decode HTTP body based on Content-Type header.

Returns a tagged tuple indicating the decoded content type:

  • {:json, data} - Parsed JSON map or list
  • {:text, string} - Valid UTF-8 text
  • {:multipart, parts} - List of decoded parts
  • {:binary, data} - Raw binary (unknown type or decode failure)
  • {:custom, data} - Custom decoder result
  • {:decode_error, reason} - Custom decoder error

Options

  • :decoders - List of custom decoder specs (see PcapFileEx.Flows.Decoder)
  • :context - Base context for decoder matching (protocol, direction, headers, etc.)
  • :keep_binary - When true, preserve original binary in multipart parts' body_binary field when custom decoders are invoked (default: false)

Examples

iex> Content.decode("application/json", ~s({"a":1}))
{:json, %{"a" => 1}}

iex> Content.decode("text/plain", "hello")
{:text, "hello"}

iex> Content.decode(nil, <<1, 2, 3>>)
{:binary, <<1, 2, 3>>}

extract_boundary(content_type)

@spec extract_boundary(String.t()) :: {:ok, String.t()} | {:error, :no_boundary}

Extract boundary parameter from multipart Content-Type.

Examples

iex> Content.extract_boundary("multipart/related; boundary=abc123")
{:ok, "abc123"}

iex> Content.extract_boundary(~s(multipart/related; boundary="abc 123"))
{:ok, "abc 123"}

iex> Content.extract_boundary("application/json")
{:error, :no_boundary}

parse_parts(body, boundary)

@spec parse_parts(binary(), String.t()) :: {:ok, [raw_part()]} | {:error, term()}

Parse MIME multipart body into raw parts.

Uses binary pattern matching to preserve exact bytes in part bodies. Does not decode part bodies - use decode/2 for that.

Examples

iex> body = "--abc\r\nContent-Type: text/plain\r\n\r\nhello\r\n--abc--"
iex> Content.parse_parts(body, "abc")
{:ok, [%{headers: %{"content-type" => "text/plain"}, body: "hello"}]}