A command-line program can serve a page to the user who started it, on the
user’s own machine. Such a server acts as the user. localweb keeps out
the three others that can reach it (decision 34):
| Who | What keeps them out |
|---|---|
| another user of the machine, who can reach a loopback port | localweb.Listen on loopback or a private Unix socket; localweb.PeerListener on Linux |
| a page of another site that makes its name resolve to 127.0.0.1 | localweb.Hosts: the Host header must name this server |
| any page in the user’s browser | localweb.SameOrigin, by Sec-Fetch-Site and Origin |
localweb.Headers sets the Content-Security-Policy and the
frame, referrer and resource policies, and
localweb.Serve serves with every timeout set.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
b, err := localweb.Listen(ctx, "127.0.0.1:0", localweb.ListenOptions{})
if err != nil {
log.Fatal(err)
}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprintln(w, "Hello from the user's machine.")
})
h := localweb.Headers("")(localweb.Hosts(b.Answers, localweb.HostsOptions{})(
localweb.SameOrigin(localweb.OriginPolicy{Navigate: true})(mux)))
done := make(chan error, 1)
go func() { done <- localweb.Serve(ctx, b.Listeners, h, localweb.ServeOptions{}) }()
client := &http.Client{Transport: &http.Transport{DisableKeepAlives: true}}
for _, host := range []string{b.Host(), "rebind.example"} {
req, err := http.NewRequest(http.MethodGet, "http://"+b.Host()+"/", nil)
if err != nil {
log.Fatal(err)
}
req.Host = host
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
body, _ := io.ReadAll(res.Body)
_ = res.Body.Close()
fmt.Printf("%s: %s %s", res.Status, res.Header.Get("X-Frame-Options"), body)
}
cancel()
fmt.Println(<-done)200 OK: DENY Hello from the user's machine.
421 Misdirected Request: DENY This server answers to its loopback address only.
<nil>localweb/example_test.go. CI runs it and checks its output.The order#
The checks are middleware of the type func(http.Handler) http.Handler,
so http.ServeMux and a router such as chi both take them. Put them in this
order, outermost first:
HeadersHostsSameOriginsession.Manager.Middleware- your handler
A check that refuses a request answers with a localweb.Refusal.
A localweb.Refuser lets the program write it in its own form,
and log the reason.
Every port of 127.0.0.1 is the same site. A SameSite cookie alone does
not keep out the pages that another program serves there.
Fixed interfaces#
The refusals, the headers, the cookie and the frames of localweb and its
packages (decision 34). net/http and html/template are imported
in localweb and its packages alone (decision 25).