mirror of
https://github.com/gl-inet/glkvm-cloud.git
synced 2026-09-17 16:15:25 +00:00
606377fbf8
Remove the obsolete legacy main entry to avoid confusion and reduce maintenance overhead. Signed-off-by: GL.iNet-Yongping.Xie <yongping.xie@gl-inet.com>
233 lines
6.3 KiB
Go
Executable File
233 lines
6.3 KiB
Go
Executable File
package proxy
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type HostInfo struct {
|
|
Host string // pure host without port
|
|
Port string // external port if known
|
|
Scheme string // http/https
|
|
RawHost string // req.Host (may include port)
|
|
XFHost string // X-Forwarded-Host (raw)
|
|
XFProto string // X-Forwarded-Proto (raw)
|
|
XFPort string // X-Forwarded-Port (raw)
|
|
}
|
|
|
|
func GetHostInfoFromRequest(req *http.Request) HostInfo {
|
|
hi := HostInfo{
|
|
RawHost: req.Host,
|
|
XFHost: req.Header.Get("X-Forwarded-Host"),
|
|
XFProto: req.Header.Get("X-Forwarded-Proto"),
|
|
XFPort: req.Header.Get("X-Forwarded-Port"),
|
|
}
|
|
|
|
// host: prefer X-Forwarded-Host
|
|
host := strings.TrimSpace(hi.XFHost)
|
|
if host != "" {
|
|
host = strings.TrimSpace(strings.Split(host, ",")[0])
|
|
} else {
|
|
host = strings.TrimSpace(req.Host)
|
|
}
|
|
|
|
// split port if host contains it
|
|
if h, p, err := net.SplitHostPort(host); err == nil {
|
|
hi.Host = h
|
|
hi.Port = p
|
|
} else {
|
|
hi.Host = strings.TrimSuffix(host, ".")
|
|
}
|
|
|
|
// scheme
|
|
proto := strings.TrimSpace(hi.XFProto)
|
|
if proto != "" {
|
|
proto = strings.ToLower(strings.TrimSpace(strings.Split(proto, ",")[0]))
|
|
hi.Scheme = proto
|
|
} else if req.TLS != nil {
|
|
hi.Scheme = "https"
|
|
} else {
|
|
hi.Scheme = "http"
|
|
}
|
|
|
|
// forwarded port overrides
|
|
fp := strings.TrimSpace(hi.XFPort)
|
|
if fp != "" {
|
|
hi.Port = strings.TrimSpace(strings.Split(fp, ",")[0])
|
|
}
|
|
|
|
return hi
|
|
}
|
|
|
|
// IsIPHost checks whether host is an IP address.
|
|
func IsIPHost(host string) bool {
|
|
ip := net.ParseIP(strings.TrimSpace(host))
|
|
return ip != nil
|
|
}
|
|
|
|
// DomainAllowed checks whether host is allowed.
|
|
// Allow:
|
|
// - exact match: base
|
|
// - subdomain: *.base
|
|
func DomainAllowed(host, base string) bool {
|
|
host = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(host), "."))
|
|
base = strings.ToLower(strings.TrimSuffix(strings.TrimSpace(base), "."))
|
|
|
|
if host == "" || base == "" {
|
|
return false
|
|
}
|
|
if host == base {
|
|
return true
|
|
}
|
|
return strings.HasSuffix(host, "."+base)
|
|
}
|
|
|
|
// BuildRedirectHost removes the first label of the hostname and prepends devid.
|
|
// Rules:
|
|
// - "www.example.com" -> "devid.example.com"
|
|
// - "www.l1.example.com" -> "devid.l1.example.com"
|
|
// - "www.l1.l2.example.com" -> "devid.l1.l2.example.com"
|
|
// - Two-level domain "example.com" -> "devid.example.com"
|
|
// - Single label / abnormal cases -> "devid." + hostname (fallback)
|
|
//
|
|
// The input hostname must be a pure hostname without port.
|
|
func BuildRedirectHost(hostname, devid string) string {
|
|
// Allow FQDN with trailing dot like "example.com."
|
|
hostname = strings.TrimSuffix(hostname, ".")
|
|
|
|
// Split into labels
|
|
labels := strings.Split(hostname, ".")
|
|
// Remove empty labels (in case of consecutive dots)
|
|
compact := make([]string, 0, len(labels))
|
|
for _, l := range labels {
|
|
if l != "" {
|
|
compact = append(compact, l)
|
|
}
|
|
}
|
|
labels = compact
|
|
|
|
switch len(labels) {
|
|
case 0:
|
|
return devid // extreme case: just return devid
|
|
case 1:
|
|
// Single label (e.g., "localhost") keep original as suffix
|
|
return devid + "." + labels[0]
|
|
default:
|
|
// >=2: drop the leftmost label
|
|
suffix := strings.Join(labels[1:], ".")
|
|
return devid + "." + suffix
|
|
}
|
|
}
|
|
|
|
func JoinHostPortIfNeeded(host, scheme, port string) string {
|
|
if port == "" {
|
|
return host
|
|
}
|
|
// avoid adding default ports
|
|
if (scheme == "https" && port == "443") || (scheme == "http" && port == "80") {
|
|
return host
|
|
}
|
|
return net.JoinHostPort(host, port)
|
|
}
|
|
|
|
func BuildRedirectLocation(scheme, hostPort, path, sid string) string {
|
|
if path == "" {
|
|
path = "/"
|
|
}
|
|
u := &url.URL{
|
|
Scheme: scheme,
|
|
Host: hostPort,
|
|
Path: path,
|
|
}
|
|
q := u.Query()
|
|
q.Set("rttysid", sid)
|
|
u.RawQuery = q.Encode()
|
|
return u.String()
|
|
}
|
|
|
|
// GetRequestHostInfo extracts domain(host), port and scheme(proto) from request headers.
|
|
// Priority:
|
|
// 1) X-Forwarded-Host / X-Forwarded-Proto / X-Forwarded-Port (reverse proxy)
|
|
// 2) Host header / TLS info
|
|
func GetRequestHostInfo(req *http.Request) (host string, port string, proto string) {
|
|
// 1) Reverse-proxy headers
|
|
xfh := strings.TrimSpace(req.Header.Get("X-Forwarded-Host"))
|
|
xfp := strings.TrimSpace(req.Header.Get("X-Forwarded-Proto"))
|
|
xfport := strings.TrimSpace(req.Header.Get("X-Forwarded-Port"))
|
|
|
|
// X-Forwarded-Host may contain a comma-separated list. Take the first one.
|
|
if xfh != "" {
|
|
if i := strings.IndexByte(xfh, ','); i >= 0 {
|
|
xfh = strings.TrimSpace(xfh[:i])
|
|
}
|
|
host = xfh
|
|
}
|
|
|
|
// 2) Fallback to Host header
|
|
if host == "" {
|
|
host = strings.TrimSpace(req.Host)
|
|
}
|
|
|
|
// Split host:port if present
|
|
if h, p, err := net.SplitHostPort(host); err == nil {
|
|
host = h
|
|
port = p
|
|
} else {
|
|
// no explicit port in Host header
|
|
port = ""
|
|
}
|
|
|
|
// scheme/proto
|
|
if xfp != "" {
|
|
if i := strings.IndexByte(xfp, ','); i >= 0 {
|
|
xfp = strings.TrimSpace(xfp[:i])
|
|
}
|
|
proto = xfp
|
|
} else if req.TLS != nil {
|
|
proto = "https"
|
|
} else {
|
|
proto = "http"
|
|
}
|
|
|
|
// forwarded port overrides parsed port if present
|
|
if xfport != "" {
|
|
if i := strings.IndexByte(xfport, ','); i >= 0 {
|
|
xfport = strings.TrimSpace(xfport[:i])
|
|
}
|
|
port = xfport
|
|
}
|
|
|
|
return host, port, proto
|
|
}
|
|
|
|
// ExtractDeviceIDFromHost extracts deviceId from hostname.
|
|
// Rules:
|
|
// - IP address -> ("", false)
|
|
// - lv99862.example.com -> ("lv99862", true)
|
|
// - lv99862.l1.example.com -> ("lv99862", true)
|
|
// - localhost / single label -> ("localhost", true)
|
|
func ExtractDeviceIDFromHost(host string) (string, bool) {
|
|
host = strings.TrimSpace(host)
|
|
if host == "" {
|
|
return "", false
|
|
}
|
|
|
|
// remove trailing dot
|
|
host = strings.TrimSuffix(host, ".")
|
|
|
|
// If host is IP, skip
|
|
if ip := net.ParseIP(host); ip != nil {
|
|
return "", false
|
|
}
|
|
|
|
labels := strings.Split(host, ".")
|
|
for _, l := range labels {
|
|
if l != "" {
|
|
return l, true
|
|
}
|
|
}
|
|
return "", false
|
|
}
|