Files
pcap-web-tools/internal/web/server.go
T
2026-10-05 20:35:47 +02:00

96 lines
3.0 KiB
Go

// Package web serves the pcap tools web UI.
package web
import (
"bytes"
"embed"
"html/template"
"io/fs"
"log"
"net/http"
)
//go:embed templates static
var assets embed.FS
// Tool is one sub-application, listed on the index page and in the navigation.
type Tool struct {
Name string
Path string
Description string
}
var tools = []Tool{
{Name: "Hex → pcap", Path: "/hex2pcap", Description: "Build a pcap file from hex-encoded layer 2, layer 3 and payload bytes, optionally fixing IP lengths and checksums."},
{Name: "Split pcap", Path: "/split", Description: "Split a pcap file into several files by packet count or file size, downloaded as a zip."},
{Name: "Reorder pcap", Path: "/reorder", Description: "Sort the packets of a pcap file by timestamp, like reordercap."},
}
// Config holds server settings.
type Config struct {
MaxUploadBytes int64 // largest accepted file upload
}
// Server is the HTTP handler for the whole application.
type Server struct {
cfg Config
mux *http.ServeMux
pages map[string]*template.Template
}
// New parses the templates and sets up the routes.
func New(cfg Config) (*Server, error) {
s := &Server{cfg: cfg, mux: http.NewServeMux(), pages: map[string]*template.Template{}}
// Each page is parsed together with the shared layout so pages can define
// their own "title" and "content" blocks.
for _, page := range []string{"index.html", "hex2pcap.html", "split.html", "reorder.html"} {
t, err := template.New(page).ParseFS(assets, "templates/layout.html", "templates/"+page)
if err != nil {
return nil, err
}
s.pages[page] = t
}
static, err := fs.Sub(assets, "static")
if err != nil {
return nil, err
}
s.mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServerFS(static)))
s.mux.HandleFunc("GET /{$}", s.handleIndex)
s.mux.HandleFunc("GET /hex2pcap", s.handleHex2PcapForm)
s.mux.HandleFunc("POST /hex2pcap", s.handleHex2Pcap)
s.mux.HandleFunc("GET /split", s.handleSplitForm)
s.mux.HandleFunc("POST /split", s.handleSplit)
s.mux.HandleFunc("GET /reorder", s.handleReorderForm)
s.mux.HandleFunc("POST /reorder", s.handleReorder)
return s, nil
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
// pageData is passed to every page template.
type pageData struct {
Tools []Tool
Active string // path of the current tool, for nav highlighting
Data any // page specific data
}
func (s *Server) render(w http.ResponseWriter, status int, page, active string, data any) {
var buf bytes.Buffer
if err := s.pages[page].ExecuteTemplate(&buf, "layout", pageData{Tools: tools, Active: active, Data: data}); err != nil {
log.Printf("render %s: %v", page, err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(status)
w.Write(buf.Bytes())
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
s.render(w, http.StatusOK, "index.html", "/", nil)
}