internal/parser/http2/hpack.go

package http2

import (
	"fmt"

	"golang.org/x/net/http2/hpack"
)

// Decoder wraps hpack.Decoder with a scratch buffer and a decoded-header
// accumulator. Each (H2 stream, direction) pair owns one decoder because
// the dynamic table is shared across frames for that pair.
type Decoder struct {
	d    *hpack.Decoder
	buf  []hpack.HeaderField
}

// NewHPACK returns a new Decoder with the standard table size. The tap
// never sends SETTINGS, so we assume the 4 KiB default.
func NewHPACK() *Decoder {
	dec := &Decoder{}
	dec.d = hpack.NewDecoder(4096, func(f hpack.HeaderField) {
		dec.buf = append(dec.buf, f)
	})
	return dec
}

// DecodeBlock consumes one complete header block (possibly assembled from
// multiple CONTINUATION fragments) and returns the decoded field list.
func (d *Decoder) DecodeBlock(block []byte) ([][2]string, error) {
	d.buf = d.buf[:0]
	if _, err := d.d.Write(block); err != nil {
		return nil, fmt.Errorf("http2: hpack write: %w", err)
	}
	if err := d.d.Close(); err != nil {
		return nil, fmt.Errorf("http2: hpack close: %w", err)
	}
	out := make([][2]string, len(d.buf))
	for i, f := range d.buf {
		out[i] = [2]string{f.Name, f.Value}
	}
	return out, nil
}

// SplitPseudo separates :method/:path/:status/:scheme/:authority from
// the regular headers in a decoded block. Pseudo headers precede normal
// ones per RFC 7540 section 8.1.2.1.
func SplitPseudo(h [][2]string) (pseudo, regular [][2]string) {
	for _, kv := range h {
		if len(kv[0]) > 0 && kv[0][0] == ':' {
			pseudo = append(pseudo, kv)
		} else {
			regular = append(regular, kv)
		}
	}
	return pseudo, regular
}

// PseudoGet fetches the value of a single pseudo header by name
// (including the leading colon) or returns "" if missing.
func PseudoGet(h [][2]string, name string) string {
	for _, kv := range h {
		if kv[0] == name {
			return kv[1]
		}
	}
	return ""
}

// AssembleStartLine reconstructs an HTTP/1.1-style start line from
// pseudo headers so that the TUI renderer can reuse the h1 layout code.
func AssembleStartLine(h [][2]string) string {
	if st := PseudoGet(h, ":status"); st != "" {
		return "HTTP/2 " + st
	}
	m := PseudoGet(h, ":method")
	p := PseudoGet(h, ":path")
	a := PseudoGet(h, ":authority")
	if m == "" || p == "" {
		return ""
	}
	if a != "" {
		return m + " " + a + p + " HTTP/2"
	}
	return m + " " + p + " HTTP/2"
}