1package http
2
3import (
4 "context"
5 "net/http"
6 "net/netip"
7 "strings"
8
9 "tug/internal/platform/origin"
10)
11
12type Client = origin.Client
13
14func WithClient(ctx context.Context, c Client) context.Context {
15 return origin.With(ctx, c)
16}
17
18func ClientOf(ctx context.Context) Client {
19 return origin.Of(ctx)
20}
21
22func forwardedClient(next http.Handler) http.Handler {
23 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24 next.ServeHTTP(w, r.WithContext(WithClient(r.Context(), Client{Addr: lastForwarded(r.Header), UserAgent: r.UserAgent()})))
25 })
26}
27
28func lastForwarded(h http.Header) netip.Addr {
29 lines := h.Values("X-Forwarded-For")
30 if len(lines) == 0 {
31 return netip.Addr{}
32 }
33 last := lines[len(lines)-1]
34 return parseForwarded(strings.Trim(last[strings.LastIndexByte(last, ',')+1:], " \t"))
35}
36
37func parseForwarded(s string) netip.Addr {
38 if a, err := netip.ParseAddr(s); err == nil {
39 return a.WithZone("").Unmap()
40 }
41 if ap, err := netip.ParseAddrPort(s); err == nil {
42 return ap.Addr().WithZone("").Unmap()
43 }
44 if inner, ok := strings.CutPrefix(s, "["); ok {
45 if inner, ok = strings.CutSuffix(inner, "]"); ok {
46 if a, err := netip.ParseAddr(inner); err == nil && a.Is6() {
47 return a.WithZone("").Unmap()
48 }
49 }
50 }
51 return netip.Addr{}
52}