github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/netaddress.go (about)

     1  package modules
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/NebulousLabs/Sia/build"
    10  )
    11  
    12  // A NetAddress contains the information needed to contact a peer.
    13  type NetAddress string
    14  
    15  // Host removes the port from a NetAddress, returning just the host. If the
    16  // address is not of the form "host:port" the empty string is returned. The
    17  // port will still be returned for invalid NetAddresses (e.g. "unqualified:0"
    18  // will return "unqualified"), but in general you should only call Host on
    19  // valid addresses.
    20  func (na NetAddress) Host() string {
    21  	host, _, err := net.SplitHostPort(string(na))
    22  	// 'host' is not always the empty string if an error is returned.
    23  	if err != nil {
    24  		return ""
    25  	}
    26  	return host
    27  }
    28  
    29  // Port returns the NetAddress object's port number. If the address is not of
    30  // the form "host:port" the empty string is returned. The port will still be
    31  // returned for invalid NetAddresses (e.g. "localhost:0" will return "0"), but
    32  // in general you should only call Port on valid addresses.
    33  func (na NetAddress) Port() string {
    34  	_, port, err := net.SplitHostPort(string(na))
    35  	// 'port' will not always be the empty string if an error is returned.
    36  	if err != nil {
    37  		return ""
    38  	}
    39  	return port
    40  }
    41  
    42  // isLoopback returns true for ip addresses that are on the same machine.
    43  func (na NetAddress) isLoopback() bool {
    44  	host, _, err := net.SplitHostPort(string(na))
    45  	if err != nil {
    46  		return false
    47  	}
    48  	if host == "localhost" {
    49  		return true
    50  	}
    51  	if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() {
    52  		return true
    53  	}
    54  	return false
    55  }
    56  
    57  // IsValid returns an error if the NetAddress is invalid. A valid NetAddress
    58  // is of the form "host:port", such that "host" is either a valid IPv4/IPv6
    59  // address or a valid hostname, and "port" is an integer in the range
    60  // [1,65535]. Furthermore, "host" may not be a loopback address (except during
    61  // testing). Valid IPv4 addresses, IPv6 addresses, and hostnames are detailed
    62  // in RFCs 791, 2460, and 952, respectively.
    63  func (na NetAddress) IsValid() error {
    64  	host, port, err := net.SplitHostPort(string(na))
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	portInt, err := strconv.Atoi(port)
    70  	if err != nil {
    71  		return errors.New("port is not an integer")
    72  	} else if portInt < 1 || portInt > 65535 {
    73  		return errors.New("port is invalid")
    74  	}
    75  
    76  	// This check must come after the valid port check so that a host such as
    77  	// "localhost:badport" will fail.
    78  	if na.isLoopback() {
    79  		if build.Release == "testing" {
    80  			return nil
    81  		}
    82  		return errors.New("host is a loopback address")
    83  	}
    84  
    85  	// First try to parse host as an IP address; if that fails, assume it is a
    86  	// hostname.
    87  	if ip := net.ParseIP(host); ip != nil {
    88  		if ip.IsUnspecified() {
    89  			return errors.New("host is the unspecified address")
    90  		}
    91  	} else {
    92  		// Hostnames can have a trailing dot (which indicates that the hostname is
    93  		// fully qualified), but we ignore it for validation purposes.
    94  		if strings.HasSuffix(host, ".") {
    95  			host = host[:len(host)-1]
    96  		}
    97  		if len(host) < 1 || len(host) > 253 {
    98  			return errors.New("invalid hostname length")
    99  		}
   100  		labels := strings.Split(host, ".")
   101  		if len(labels) == 1 {
   102  			return errors.New("unqualified hostname")
   103  		}
   104  		for _, label := range labels {
   105  			if len(label) < 1 || len(label) > 63 {
   106  				return errors.New("hostname contains label with invalid length")
   107  			}
   108  			if strings.HasPrefix(label, "-") || strings.HasSuffix(label, "-") {
   109  				return errors.New("hostname contains label that starts or ends with a hyphen")
   110  			}
   111  			for _, r := range strings.ToLower(label) {
   112  				isLetter := 'a' <= r && r <= 'z'
   113  				isNumber := '0' <= r && r <= '9'
   114  				isHyphen := r == '-'
   115  				if !(isLetter || isNumber || isHyphen) {
   116  					return errors.New("host contains invalid characters")
   117  				}
   118  			}
   119  		}
   120  	}
   121  
   122  	return nil
   123  }