Skip to content
quarry Artifact [926cea60d3]
Login

Artifact [926cea60d3]

Artifact 926cea60d3d43a42a7830914f6e940589effefa5612e66cd07d0221fe634bc8f:


// 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()
}