1 package app
2 3 import (
4 "net/http"
5 "strings"
6 7 "next.orly.dev/pkg/lol/log"
8 )
9 10 // GetRemoteFromReq retrieves the originating IP address of the client from
11 // an HTTP request, considering standard and non-standard proxy headers.
12 //
13 // # Parameters
14 //
15 // - r: The HTTP request object containing details of the client and
16 // routing information.
17 //
18 // # Return Values
19 //
20 // - rr: A string value representing the IP address of the originating
21 // remote client.
22 //
23 // # Expected behaviour
24 //
25 // The function first checks for the standardized "Forwarded" header (RFC 7239)
26 // to identify the original client IP. If that isn't available, it falls back to
27 // the "X-Forwarded-For" header. If both headers are absent, it defaults to
28 // using the request's RemoteAddr.
29 //
30 // For the "Forwarded" header, it extracts the client IP from the "for"
31 // parameter. For the "X-Forwarded-For" header, if it contains one IP, it
32 // returns that. If it contains two IPs, it returns the second.
33 func GetRemoteFromReq(r *http.Request) (rr string) {
34 // First check for the standardized Forwarded header (RFC 7239)
35 forwarded := r.Header.Get("Forwarded")
36 if forwarded != "" {
37 // Parse the Forwarded header which can contain multiple parameters
38 //
39 // Format:
40 //
41 // Forwarded: by=<identifier>;for=<identifier>;host=<host>;proto=<http|https>
42 parts := strings.Split(forwarded, ";")
43 for _, part := range parts {
44 part = strings.TrimSpace(part)
45 if strings.HasPrefix(part, "for=") {
46 // Extract the client IP from the "for" parameter
47 forValue := strings.TrimPrefix(part, "for=")
48 // Remove quotes if present
49 forValue = strings.Trim(forValue, "\"")
50 // Handle IPv6 addresses which are enclosed in square brackets
51 forValue = strings.Trim(forValue, "[]")
52 return forValue
53 }
54 }
55 }
56 // If the Forwarded header is not available or doesn't contain "for"
57 // parameter, fall back to X-Forwarded-For
58 rem := r.Header.Get("X-Forwarded-For")
59 if rem == "" {
60 rr = r.RemoteAddr
61 } else {
62 // X-Forwarded-For can be comma-separated or space-separated;
63 // use the first (client) IP for comma-separated, or handle
64 // space-separated format from some proxies.
65 parts := strings.Split(rem, ",")
66 if len(parts) > 1 {
67 rr = strings.TrimSpace(parts[0])
68 } else {
69 splitted := strings.Split(rem, " ")
70 rr = strings.TrimSpace(splitted[0])
71 }
72 }
73 return
74 }
75 76 // LogProxyInfo logs comprehensive proxy information for debugging
77 func LogProxyInfo(r *http.Request, prefix string) {
78 proxyHeaders := map[string]string{
79 "X-Forwarded-For": r.Header.Get("X-Forwarded-For"),
80 "X-Real-IP": r.Header.Get("X-Real-IP"),
81 "X-Forwarded-Proto": r.Header.Get("X-Forwarded-Proto"),
82 "X-Forwarded-Host": r.Header.Get("X-Forwarded-Host"),
83 "X-Forwarded-Port": r.Header.Get("X-Forwarded-Port"),
84 "Forwarded": r.Header.Get("Forwarded"),
85 "Host": r.Header.Get("Host"),
86 "User-Agent": r.Header.Get("User-Agent"),
87 }
88 89 var info []string
90 for header, value := range proxyHeaders {
91 if value != "" {
92 info = append(info, header+":"+value)
93 }
94 }
95 96 if len(info) > 0 {
97 log.T.F("%s proxy info: %s", prefix, strings.Join(info, " "))
98 }
99 }
100