host.go raw

   1  package http
   2  
   3  import (
   4  	"fmt"
   5  	"net"
   6  	"strconv"
   7  	"strings"
   8  )
   9  
  10  // ValidateEndpointHost validates that the host string passed in is a valid RFC
  11  // 3986 host. Returns error if the host is not valid.
  12  func ValidateEndpointHost(host string) error {
  13  	var errors strings.Builder
  14  	var hostname string
  15  	var port string
  16  	var err error
  17  
  18  	if strings.Contains(host, ":") {
  19  		hostname, port, err = net.SplitHostPort(host)
  20  		if err != nil {
  21  			errors.WriteString(fmt.Sprintf("\n endpoint %v, failed to parse, got ", host))
  22  			errors.WriteString(err.Error())
  23  		}
  24  
  25  		if !ValidPortNumber(port) {
  26  			errors.WriteString(fmt.Sprintf("port number should be in range [0-65535], got %v", port))
  27  		}
  28  	} else {
  29  		hostname = host
  30  	}
  31  
  32  	labels := strings.Split(hostname, ".")
  33  	for i, label := range labels {
  34  		if i == len(labels)-1 && len(label) == 0 {
  35  			// Allow trailing dot for FQDN hosts.
  36  			continue
  37  		}
  38  
  39  		if !ValidHostLabel(label) {
  40  			errors.WriteString("\nendpoint host domain labels must match \"[a-zA-Z0-9-]{1,63}\", but found: ")
  41  			errors.WriteString(label)
  42  		}
  43  	}
  44  
  45  	if len(hostname) == 0 && len(port) != 0 {
  46  		errors.WriteString("\nendpoint host with port must not be empty")
  47  	}
  48  
  49  	if len(hostname) > 255 {
  50  		errors.WriteString(fmt.Sprintf("\nendpoint host must be less than 255 characters, but was %d", len(hostname)))
  51  	}
  52  
  53  	if len(errors.String()) > 0 {
  54  		return fmt.Errorf("invalid endpoint host%s", errors.String())
  55  	}
  56  	return nil
  57  }
  58  
  59  // ValidPortNumber returns whether the port is valid RFC 3986 port.
  60  func ValidPortNumber(port string) bool {
  61  	i, err := strconv.Atoi(port)
  62  	if err != nil {
  63  		return false
  64  	}
  65  
  66  	if i < 0 || i > 65535 {
  67  		return false
  68  	}
  69  	return true
  70  }
  71  
  72  // ValidHostLabel returns whether the label is a valid RFC 3986 host label.
  73  func ValidHostLabel(label string) bool {
  74  	if l := len(label); l == 0 || l > 63 {
  75  		return false
  76  	}
  77  	for _, r := range label {
  78  		switch {
  79  		case r >= '0' && r <= '9':
  80  		case r >= 'A' && r <= 'Z':
  81  		case r >= 'a' && r <= 'z':
  82  		case r == '-':
  83  		default:
  84  			return false
  85  		}
  86  	}
  87  
  88  	return true
  89  }
  90