v2.10.2 705579a

clientip.go RawHistoryFolder

1package middleware
2
3import (
4	"net"
5	"net/http"
6	"strings"
7)
8
9// ParseTrustedProxies converts a list of IP addresses and CIDR ranges to net.IPNet.
10// It accepts both single IPs (e.g., "10.0.0.1") and CIDR notation (e.g., "10.0.0.0/8").
11// Single IPs are converted to /32 (IPv4) or /128 (IPv6) CIDR blocks.
12func ParseTrustedProxies(proxies []string) []*net.IPNet {
13	var nets []*net.IPNet
14	for _, proxy := range proxies {
15		// Try parsing as CIDR
16		_, ipNet, err := net.ParseCIDR(proxy)
17		if err == nil {
18			nets = append(nets, ipNet)
19			continue
20		}
21		// Try parsing as single IP
22		ip := net.ParseIP(proxy)
23		if ip != nil {
24			// Convert single IP to /32 or /128 CIDR
25			bits := 32
26			if ip.To4() == nil {
27				bits = 128
28			}
29			nets = append(nets, &net.IPNet{IP: ip, Mask: net.CIDRMask(bits, bits)})
30		}
31	}
32	return nets
33}
34
35// IsTrustedProxy checks if the given IP address is from a trusted proxy.
36// Returns false if trustedNets is empty or the IP cannot be parsed.
37func IsTrustedProxy(ip string, trustedNets []*net.IPNet) bool {
38	if len(trustedNets) == 0 {
39		return false
40	}
41	parsedIP := net.ParseIP(ip)
42	if parsedIP == nil {
43		return false
44	}
45	for _, ipNet := range trustedNets {
46		if ipNet.Contains(parsedIP) {
47			return true
48		}
49	}
50	return false
51}
52
53// GetClientIP extracts the client IP address from the request.
54// It only honors X-Forwarded-For and X-Real-IP headers when the request
55// originates from a trusted proxy. This prevents attackers from spoofing
56// their IP address when Gordon is exposed directly without a reverse proxy.
57func GetClientIP(r *http.Request, trustedNets []*net.IPNet) string {
58	// Extract the direct connection IP from RemoteAddr
59	remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
60	if err != nil {
61		remoteIP = r.RemoteAddr
62	}
63
64	// Only honor proxy headers if the request comes from a trusted proxy
65	if IsTrustedProxy(remoteIP, trustedNets) {
66		// Check X-Forwarded-For for proxied requests
67		if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
68			// Take first IP in chain (original client)
69			if first, _, found := strings.Cut(xff, ","); found {
70				return strings.TrimSpace(first)
71			}
72			return strings.TrimSpace(xff)
73		}
74
75		// Check X-Real-IP
76		if xri := r.Header.Get("X-Real-IP"); xri != "" {
77			return xri
78		}
79	}
80
81	// Fall back to RemoteAddr (direct connection IP)
82	return remoteIP
83}

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