// Package outbound holds the one rule for URLs quarry sends something to
// on its own initiative: a webhook's payload, a mirror's or chat
// bridge's credentials, a runner's token and the private tree it
// fetches back.
//
// https anywhere; plain http only to this machine. Over http to another
// host, whatever quarry sends crosses the network readable by anyone on
// the path, and a private repository's content with it (#225). Loopback
// stays allowed because it never leaves the box: a forge's own runner,
// a developer's MinIO, and the tests all live there.
package outbound
import (
"errors"
"net"
"net/url"
"strings"
)
// Check parses raw and refuses anything but https, or http to a
// loopback host. The error says which, in words an operator can act on.
func Check(raw string) (*url.URL, error) {
u, err := url.Parse(strings.TrimSpace(raw))
if err != nil || u.Host == "" {
return nil, errors.New("not a url with a host")
}
switch u.Scheme {
case "https":
return u, nil
case "http":
if Loopback(u.Hostname()) {
return u, nil
}
return nil, errors.New("must be https; plain http is allowed only to this machine (localhost, 127.0.0.0/8, ::1)")
}
return nil, errors.New("must be an https url")
}
// Loopback says whether host names this machine. "localhost" by name,
// or an address in 127.0.0.0/8 or ::1. A name that merely starts like
// one ("127.evil.example") is a name, and names other than localhost
// are resolved by somebody else.
func Loopback(host string) bool {
if strings.EqualFold(host, "localhost") {
return true
}
ip := net.ParseIP(host)
return ip != nil && ip.IsLoopback()
}