Flamenco/internal/own_url/own_url.go
Sybren A. Stüvel c0cd3ca5ad UPnP/SSDP: Cleaner, easier to test (and actually tested) code
Convert "get own URLs" code into nicer chunks, and test those.

This minimises the code that actually depends on the available network
interfaces, and increases test coverage. Found a few bugs too.
2022-03-08 12:11:47 +01:00

103 lines
2.6 KiB
Go

// Package own_url provides a way for a process to find a URL on which it can be reached.
package own_url
import (
"fmt"
"net"
"net/url"
"github.com/rs/zerolog/log"
)
func AvailableURLs(schema, listen string) ([]url.URL, error) {
if listen == "" {
panic("empty 'listen' parameter")
}
hostURL := specificHostURL(schema, listen)
if hostURL != nil {
return []url.URL{*hostURL}, nil
}
log.Debug().Str("listen", listen).Msg("not listening on any specific host")
addrs, err := networkInterfaces()
if err != nil {
return nil, err
}
log.Debug().Msg("iterating network interfaces to find possible URLs for Flamenco Manager.")
return urlsForNetworkInterfaces(schema, listen, addrs)
}
// specificHostURL returns the hosts's URL if the "listen" string is specific enough, otherwise nil.
// Examples: "192.168.0.1:8080" is specific enough, "0.0.0.0:8080" and ":8080" are not.
func specificHostURL(scheme, listen string) *url.URL {
var (
host string
err error
)
// Figure out which port we're supposted to listen on.
if host, _, err = net.SplitHostPort(listen); err != nil {
// This is annoying. SplitHostPort() doesn't return specific errors, so we
// have to test on the error message to see what's the problem.
// A missing port is fine, but other errors are not.
addrErr := err.(*net.AddrError)
if addrErr.Err != "missing port in address" {
log.Warn().Str("address", listen).Err(err).Msg("unable to split host and port in address")
return nil
}
// 'listen' doesn't have a port number, so it's just the host.
host = listen
}
if host == "" {
// An empty host is never specific enough.
return nil
}
ip := net.ParseIP(host)
if ip != nil && ip.IsUnspecified() {
// The host is "::0" or "0.0.0.0"; not specific.
return nil
}
// We can just construct a URL here, since we know it's a specific host anyway.
return &url.URL{
Scheme: scheme,
Host: listen,
Path: "/",
}
}
func urlsForNetworkInterfaces(scheme, listen string, addrs []net.IP) ([]url.URL, error) {
// Find the port number in the 'listen' string.
var (
port string
err error
)
// Get the port number as integer.
if _, port, err = net.SplitHostPort(listen); err != nil {
return nil, fmt.Errorf("unable to split host and port in address %q", listen)
}
links := make([]url.URL, 0)
for _, addr := range addrs {
var strAddr string
if ipv4 := addr.To4(); ipv4 != nil {
strAddr = ipv4.String()
} else {
strAddr = fmt.Sprintf("[%s]", addr)
}
urlForAddr := url.URL{
Scheme: scheme,
Host: fmt.Sprintf("%s:%s", strAddr, port),
Path: "/",
}
links = append(links, urlForAddr)
}
return links, nil
}