github.com/lukahartwig/terraform@v0.11.4-0.20180302171601-664391c254ea/registry/regsrc/friendly_host.go (about)

     1  package regsrc
     2  
     3  import (
     4  	"regexp"
     5  	"strings"
     6  
     7  	"github.com/hashicorp/terraform/svchost"
     8  )
     9  
    10  var (
    11  	// InvalidHostString is a placeholder returned when a raw host can't be
    12  	// converted by IDNA spec. It will never be returned for any host for which
    13  	// Valid() is true.
    14  	InvalidHostString = "<invalid host>"
    15  
    16  	// urlLabelEndSubRe is a sub-expression that matches any character that's
    17  	// allowed at the start or end of a URL label according to RFC1123.
    18  	urlLabelEndSubRe = "[0-9A-Za-z]"
    19  
    20  	// urlLabelEndSubRe is a sub-expression that matches any character that's
    21  	// allowed at in a non-start or end of a URL label according to RFC1123.
    22  	urlLabelMidSubRe = "[0-9A-Za-z-]"
    23  
    24  	// urlLabelUnicodeSubRe is a sub-expression that matches any non-ascii char
    25  	// in an IDN (Unicode) display URL. It's not strict - there are only ~15k
    26  	// valid Unicode points in IDN RFC (some with conditions). We are just going
    27  	// with being liberal with matching and then erroring if we fail to convert
    28  	// to punycode later (which validates chars fully). This at least ensures
    29  	// ascii chars dissalowed by the RC1123 parts above don't become legal
    30  	// again.
    31  	urlLabelUnicodeSubRe = "[^[:ascii:]]"
    32  
    33  	// hostLabelSubRe is the sub-expression that matches a valid hostname label.
    34  	// It does not anchor the start or end so it can be composed into more
    35  	// complex RegExps below. Note that for sanity we don't handle disallowing
    36  	// raw punycode in this regexp (esp. since re2 doesn't support negative
    37  	// lookbehind, but we can capture it's presence here to check later).
    38  	hostLabelSubRe = "" +
    39  		// Match valid initial char, or unicode char
    40  		"(?:" + urlLabelEndSubRe + "|" + urlLabelUnicodeSubRe + ")" +
    41  		// Optionally, match 0 to 61 valid URL or Unicode chars,
    42  		// followed by one valid end char or unicode char
    43  		"(?:" +
    44  		"(?:" + urlLabelMidSubRe + "|" + urlLabelUnicodeSubRe + "){0,61}" +
    45  		"(?:" + urlLabelEndSubRe + "|" + urlLabelUnicodeSubRe + ")" +
    46  		")?"
    47  
    48  	// hostSubRe is the sub-expression that matches a valid host prefix.
    49  	// Allows custom port.
    50  	hostSubRe = hostLabelSubRe + "(?:\\." + hostLabelSubRe + ")+(?::\\d+)?"
    51  
    52  	// hostRe is a regexp that matches a valid host prefix. Additional
    53  	// validation of unicode strings is needed for matches.
    54  	hostRe = regexp.MustCompile("^" + hostSubRe + "$")
    55  )
    56  
    57  // FriendlyHost describes a registry instance identified in source strings by a
    58  // simple bare hostname like registry.terraform.io.
    59  type FriendlyHost struct {
    60  	Raw string
    61  }
    62  
    63  func NewFriendlyHost(host string) *FriendlyHost {
    64  	return &FriendlyHost{Raw: host}
    65  }
    66  
    67  // ParseFriendlyHost attempts to parse a valid "friendly host" prefix from the
    68  // given string. If no valid prefix is found, host will be nil and rest will
    69  // contain the full source string. The host prefix must terminate at the end of
    70  // the input or at the first / character. If one or more characters exist after
    71  // the first /, they will be returned as rest (without the / delimiter).
    72  // Hostnames containing punycode WILL be parsed successfully since they may have
    73  // come from an internal normalized source string, however should be considered
    74  // invalid if the string came from a user directly. This must be checked
    75  // explicitly for user-input strings by calling Valid() on the
    76  // returned host.
    77  func ParseFriendlyHost(source string) (host *FriendlyHost, rest string) {
    78  	parts := strings.SplitN(source, "/", 2)
    79  
    80  	if hostRe.MatchString(parts[0]) {
    81  		host = &FriendlyHost{Raw: parts[0]}
    82  		if len(parts) == 2 {
    83  			rest = parts[1]
    84  		}
    85  		return
    86  	}
    87  
    88  	// No match, return whole string as rest along with nil host
    89  	rest = source
    90  	return
    91  }
    92  
    93  // Valid returns whether the host prefix is considered valid in any case.
    94  // Example of invalid prefixes might include ones that don't conform to the host
    95  // name specifications. Not that IDN prefixes containing punycode are not valid
    96  // input which we expect to always be in user-input or normalised display form.
    97  func (h *FriendlyHost) Valid() bool {
    98  	return svchost.IsValid(h.Raw)
    99  }
   100  
   101  // Display returns the host formatted for display to the user in CLI or web
   102  // output.
   103  func (h *FriendlyHost) Display() string {
   104  	return svchost.ForDisplay(h.Raw)
   105  }
   106  
   107  // Normalized returns the host formatted for internal reference or comparison.
   108  func (h *FriendlyHost) Normalized() string {
   109  	host, err := svchost.ForComparison(h.Raw)
   110  	if err != nil {
   111  		return InvalidHostString
   112  	}
   113  	return string(host)
   114  }
   115  
   116  // String returns the host formatted as the user originally typed it assuming it
   117  // was parsed from user input.
   118  func (h *FriendlyHost) String() string {
   119  	return h.Raw
   120  }
   121  
   122  // Equal compares the FriendlyHost against another instance taking normalization
   123  // into account. Invalid hosts cannot be compared and will always return false.
   124  func (h *FriendlyHost) Equal(other *FriendlyHost) bool {
   125  	if other == nil {
   126  		return false
   127  	}
   128  
   129  	otherHost, err := svchost.ForComparison(other.Raw)
   130  	if err != nil {
   131  		return false
   132  	}
   133  
   134  	host, err := svchost.ForComparison(h.Raw)
   135  	if err != nil {
   136  		return false
   137  	}
   138  
   139  	return otherHost == host
   140  }