Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485package hostutil
import ( "fmt" "net" "net/http" "net/url" "syscall" "time")
// isBlockedIP reports whether ip is loopback, private, link-local (incl. the// 169.254.169.254 metadata endpoint), multicast, or unspecified.func isBlockedIP(ip net.IP) bool { return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsUnspecified()}
// safeDialer rejects dials to non-public addresses. the Control hook runs after// dns resolution, so it also covers rebinding and redirects. disabled in dev.func safeDialer(dev bool) *net.Dialer { d := &net.Dialer{ Timeout: 10 * time.Second, KeepAlive: 30 * time.Second, } if dev { return d } d.Control = func(_, address string, _ syscall.RawConn) error { host, _, err := net.SplitHostPort(address) if err != nil { return fmt.Errorf("invalid dial address %q: %w", address, err) } ip := net.ParseIP(host) if ip == nil { return fmt.Errorf("dial address %q did not resolve to an IP", address) } if isBlockedIP(ip) { return fmt.Errorf("refusing to dial %s: reserved or private address", ip) } return nil } return d}
// ValidateExternalURL checks raw is a well-formed http(s) url and rejects// ip-literal hosts in blocked ranges; dns hosts are re-checked at dial time.func ValidateExternalURL(raw string, dev bool) error { u, err := url.Parse(raw) if err != nil { return fmt.Errorf("invalid URL: %w", err) } if u.Scheme != "http" && u.Scheme != "https" { return fmt.Errorf("URL must use http or https") } if u.Hostname() == "" { return fmt.Errorf("URL must include a host") } if dev { return nil } if ip := net.ParseIP(u.Hostname()); ip != nil && isBlockedIP(ip) { return fmt.Errorf("URL host is a reserved or private address") } return nil}
// SafeClient returns an http.Client for fetching untrusted urls (e.g.// webhooks): it blocks internal address ranges and won't follow redirects.func SafeClient(dev bool, timeout time.Duration) *http.Client { return &http.Client{ Timeout: timeout, Transport: &http.Transport{ DialContext: safeDialer(dev).DialContext, }, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, }}