v3.3.2 9697f9d

trust.go RawHistoryFolder

1package httphelper
2
3import (
4	"net"
5	"net/http"
6	"strconv"
7	"strings"
8)
9
10// LocalhostNets contains IPv4 and IPv6 loopback ranges.
11var LocalhostNets = ParseTrustedProxies([]string{"127.0.0.0/8", "::1"})
12
13// ParseTrustedProxies converts a list of IP addresses and CIDR ranges to [net.IPNet].
14// It accepts both single IPs (e.g., "10.0.0.1") and CIDR notation (e.g., "10.0.0.0/8").
15// Single IPs are converted to /32 (IPv4) or /128 (IPv6) CIDR blocks.
16func ParseTrustedProxies(proxies []string) []*net.IPNet {
17	var nets []*net.IPNet
18	for _, proxy := range proxies {
19		proxy = strings.TrimSpace(proxy)
20		if proxy == "" {
21			continue
22		}
23
24		// Try parsing as CIDR
25		_, ipNet, err := net.ParseCIDR(proxy)
26		if err == nil {
27			nets = append(nets, ipNet)
28			continue
29		}
30		// Try parsing as single IP
31		ip := net.ParseIP(proxy)
32		if ip != nil {
33			// Convert single IP to /32 or /128 CIDR
34			bits := 32
35			if ip.To4() == nil {
36				bits = 128
37			}
38			nets = append(nets, &net.IPNet{IP: ip, Mask: net.CIDRMask(bits, bits)})
39		}
40	}
41	return nets
42}
43
44// IsTrustedProxy reports whether the given IP address falls within any of the
45// trusted networks. Returns false if trustedNets is empty or the IP cannot be parsed.
46func IsTrustedProxy(ip string, trustedNets []*net.IPNet) bool {
47	if len(trustedNets) == 0 {
48		return false
49	}
50	parsedIP := net.ParseIP(ip)
51	if parsedIP == nil {
52		return false
53	}
54	for _, ipNet := range trustedNets {
55		if ipNet.Contains(parsedIP) {
56			return true
57		}
58	}
59	return false
60}
61
62// IsTrustedOrLocal reports whether an IP belongs to localhost or the given
63// trusted proxy networks.
64func IsTrustedOrLocal(ip string, proxyNets []*net.IPNet) bool {
65	return IsTrustedProxy(ip, LocalhostNets) || IsTrustedProxy(ip, proxyNets)
66}
67
68// ExtractRemoteIP returns the IP portion of an [http.Request.RemoteAddr],
69// stripping the port. When RemoteAddr lacks a port (e.g. Unix sockets or
70// certain proxy setups), it is returned as-is.
71func ExtractRemoteIP(remoteAddr string) string {
72	host, _, err := net.SplitHostPort(remoteAddr)
73	if err != nil {
74		return remoteAddr
75	}
76	return host
77}
78
79// IsTrustedSource reports whether the request's remote address is in trustedNets.
80// When RemoteAddr lacks a port, IPv6 brackets are stripped before checking.
81func IsTrustedSource(r *http.Request, trustedNets []*net.IPNet) bool {
82	if len(trustedNets) == 0 {
83		return false
84	}
85	host, _, err := net.SplitHostPort(r.RemoteAddr)
86	if err != nil {
87		// RemoteAddr may be a bare IP without a port (e.g. from Unix sockets
88		// or certain proxy setups). Fall back to using it directly.
89		host = strings.TrimSuffix(strings.TrimPrefix(r.RemoteAddr, "["), "]")
90	}
91	return IsTrustedProxy(host, trustedNets)
92}
93
94// HTTPSAuthority converts an incoming Host header plus httpPort/tlsPort into
95// the correct HTTPS authority (host or host:port).
96//
97// Rules:
98//   - No port in Host: omit TLS port (public reverse-proxy assumed on :443).
99//   - Host port == httpPort: map to tlsPort.
100//   - Any other explicit port: preserve it.
101func HTTPSAuthority(host string, httpPort, tlsPort int) string {
102	hostname, portStr, err := net.SplitHostPort(host)
103	if err != nil {
104		// No port in Host header — omit TLS port from URL.
105		return host
106	}
107
108	port, err := strconv.Atoi(portStr)
109	if err == nil && port == httpPort {
110		return net.JoinHostPort(hostname, strconv.Itoa(tlsPort))
111	}
112
113	// Unknown explicit port — preserve it.
114	return net.JoinHostPort(hostname, portStr)
115}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close