aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/svchost/svchost.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/svchost/svchost.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/svchost/svchost.go207
1 files changed, 207 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/svchost/svchost.go b/vendor/github.com/hashicorp/terraform/svchost/svchost.go
new file mode 100644
index 0000000..4eded14
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/svchost/svchost.go
@@ -0,0 +1,207 @@
1// Package svchost deals with the representations of the so-called "friendly
2// hostnames" that we use to represent systems that provide Terraform-native
3// remote services, such as module registry, remote operations, etc.
4//
5// Friendly hostnames are specified such that, as much as possible, they
6// are consistent with how web browsers think of hostnames, so that users
7// can bring their intuitions about how hostnames behave when they access
8// a Terraform Enterprise instance's web UI (or indeed any other website)
9// and have this behave in a similar way.
10package svchost
11
12import (
13 "errors"
14 "fmt"
15 "strconv"
16 "strings"
17
18 "golang.org/x/net/idna"
19)
20
21// Hostname is specialized name for string that indicates that the string
22// has been converted to (or was already in) the storage and comparison form.
23//
24// Hostname values are not suitable for display in the user-interface. Use
25// the ForDisplay method to obtain a form suitable for display in the UI.
26//
27// Unlike user-supplied hostnames, strings of type Hostname (assuming they
28// were constructed by a function within this package) can be compared for
29// equality using the standard Go == operator.
30type Hostname string
31
32// acePrefix is the ASCII Compatible Encoding prefix, used to indicate that
33// a domain name label is in "punycode" form.
34const acePrefix = "xn--"
35
36// displayProfile is a very liberal idna profile that we use to do
37// normalization for display without imposing validation rules.
38var displayProfile = idna.New(
39 idna.MapForLookup(),
40 idna.Transitional(true),
41)
42
43// ForDisplay takes a user-specified hostname and returns a normalized form of
44// it suitable for display in the UI.
45//
46// If the input is so invalid that no normalization can be performed then
47// this will return the input, assuming that the caller still wants to
48// display _something_. This function is, however, more tolerant than the
49// other functions in this package and will make a best effort to prepare
50// _any_ given hostname for display.
51//
52// For validation, use either IsValid (for explicit validation) or
53// ForComparison (which implicitly validates, returning an error if invalid).
54func ForDisplay(given string) string {
55 var portPortion string
56 if colonPos := strings.Index(given, ":"); colonPos != -1 {
57 given, portPortion = given[:colonPos], given[colonPos:]
58 }
59 portPortion, _ = normalizePortPortion(portPortion)
60
61 ascii, err := displayProfile.ToASCII(given)
62 if err != nil {
63 return given + portPortion
64 }
65 display, err := displayProfile.ToUnicode(ascii)
66 if err != nil {
67 return given + portPortion
68 }
69 return display + portPortion
70}
71
72// IsValid returns true if the given user-specified hostname is a valid
73// service hostname.
74//
75// Validity is determined by complying with the RFC 5891 requirements for
76// names that are valid for domain lookup (section 5), with the additional
77// requirement that user-supplied forms must not _already_ contain
78// Punycode segments.
79func IsValid(given string) bool {
80 _, err := ForComparison(given)
81 return err == nil
82}
83
84// ForComparison takes a user-specified hostname and returns a normalized
85// form of it suitable for storage and comparison. The result is not suitable
86// for display to end-users because it uses Punycode to represent non-ASCII
87// characters, and this form is unreadable for non-ASCII-speaking humans.
88//
89// The result is typed as Hostname -- a specialized name for string -- so that
90// other APIs can make it clear within the type system whether they expect a
91// user-specified or display-form hostname or a value already normalized for
92// comparison.
93//
94// The returned Hostname is not valid if the returned error is non-nil.
95func ForComparison(given string) (Hostname, error) {
96 var portPortion string
97 if colonPos := strings.Index(given, ":"); colonPos != -1 {
98 given, portPortion = given[:colonPos], given[colonPos:]
99 }
100
101 var err error
102 portPortion, err = normalizePortPortion(portPortion)
103 if err != nil {
104 return Hostname(""), err
105 }
106
107 if given == "" {
108 return Hostname(""), fmt.Errorf("empty string is not a valid hostname")
109 }
110
111 // First we'll apply our additional constraint that Punycode must not
112 // be given directly by the user. This is not an IDN specification
113 // requirement, but we prohibit it to force users to use human-readable
114 // hostname forms within Terraform configuration.
115 labels := labelIter{orig: given}
116 for ; !labels.done(); labels.next() {
117 label := labels.label()
118 if label == "" {
119 return Hostname(""), fmt.Errorf(
120 "hostname contains empty label (two consecutive periods)",
121 )
122 }
123 if strings.HasPrefix(label, acePrefix) {
124 return Hostname(""), fmt.Errorf(
125 "hostname label %q specified in punycode format; service hostnames must be given in unicode",
126 label,
127 )
128 }
129 }
130
131 result, err := idna.Lookup.ToASCII(given)
132 if err != nil {
133 return Hostname(""), err
134 }
135 return Hostname(result + portPortion), nil
136}
137
138// ForDisplay returns a version of the receiver that is appropriate for display
139// in the UI. This includes converting any punycode labels to their
140// corresponding Unicode characters.
141//
142// A round-trip through ForComparison and this ForDisplay method does not
143// guarantee the same result as calling this package's top-level ForDisplay
144// function, since a round-trip through the Hostname type implies stricter
145// handling than we do when doing basic display-only processing.
146func (h Hostname) ForDisplay() string {
147 given := string(h)
148 var portPortion string
149 if colonPos := strings.Index(given, ":"); colonPos != -1 {
150 given, portPortion = given[:colonPos], given[colonPos:]
151 }
152 // We don't normalize the port portion here because we assume it's
153 // already been normalized on the way in.
154
155 result, err := idna.Lookup.ToUnicode(given)
156 if err != nil {
157 // Should never happen, since type Hostname indicates that a string
158 // passed through our validation rules.
159 panic(fmt.Errorf("ForDisplay called on invalid Hostname: %s", err))
160 }
161 return result + portPortion
162}
163
164func (h Hostname) String() string {
165 return string(h)
166}
167
168func (h Hostname) GoString() string {
169 return fmt.Sprintf("svchost.Hostname(%q)", string(h))
170}
171
172// normalizePortPortion attempts to normalize the "port portion" of a hostname,
173// which begins with the first colon in the hostname and should be followed
174// by a string of decimal digits.
175//
176// If the port portion is valid, a normalized version of it is returned along
177// with a nil error.
178//
179// If the port portion is invalid, the input string is returned verbatim along
180// with a non-nil error.
181//
182// An empty string is a valid port portion representing the absense of a port.
183// If non-empty, the first character must be a colon.
184func normalizePortPortion(s string) (string, error) {
185 if s == "" {
186 return s, nil
187 }
188
189 if s[0] != ':' {
190 // should never happen, since caller tends to guarantee the presence
191 // of a colon due to how it's extracted from the string.
192 return s, errors.New("port portion is missing its initial colon")
193 }
194
195 numStr := s[1:]
196 num, err := strconv.Atoi(numStr)
197 if err != nil {
198 return s, errors.New("port portion contains non-digit characters")
199 }
200 if num == 443 {
201 return "", nil // ":443" is the default
202 }
203 if num > 65535 {
204 return s, errors.New("port number is greater than 65535")
205 }
206 return fmt.Sprintf(":%d", num), nil
207}