github.com/magodo/terraform@v0.11.12-beta1/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 }