// 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) }