aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/registry/regsrc/friendly_host.go
blob: 14b4dce9ce086f193b90b3adae17af9725adbc2d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package regsrc

import (
	"regexp"
	"strings"

	"github.com/hashicorp/terraform/svchost"
)

var (
	// InvalidHostString is a placeholder returned when a raw host can't be
	// converted by IDNA spec. It will never be returned for any host for which
	// Valid() is true.
	InvalidHostString = "<invalid host>"

	// urlLabelEndSubRe is a sub-expression that matches any character that's
	// allowed at the start or end of a URL label according to RFC1123.
	urlLabelEndSubRe = "[0-9A-Za-z]"

	// urlLabelEndSubRe is a sub-expression that matches any character that's
	// allowed at in a non-start or end of a URL label according to RFC1123.
	urlLabelMidSubRe = "[0-9A-Za-z-]"

	// urlLabelUnicodeSubRe is a sub-expression that matches any non-ascii char
	// in an IDN (Unicode) display URL. It's not strict - there are only ~15k
	// valid Unicode points in IDN RFC (some with conditions). We are just going
	// with being liberal with matching and then erroring if we fail to convert
	// to punycode later (which validates chars fully). This at least ensures
	// ascii chars dissalowed by the RC1123 parts above don't become legal
	// again.
	urlLabelUnicodeSubRe = "[^[:ascii:]]"

	// hostLabelSubRe is the sub-expression that matches a valid hostname label.
	// It does not anchor the start or end so it can be composed into more
	// complex RegExps below. Note that for sanity we don't handle disallowing
	// raw punycode in this regexp (esp. since re2 doesn't support negative
	// lookbehind, but we can capture it's presence here to check later).
	hostLabelSubRe = "" +
		// Match valid initial char, or unicode char
		"(?:" + urlLabelEndSubRe + "|" + urlLabelUnicodeSubRe + ")" +
		// Optionally, match 0 to 61 valid URL or Unicode chars,
		// followed by one valid end char or unicode char
		"(?:" +
		"(?:" + urlLabelMidSubRe + "|" + urlLabelUnicodeSubRe + "){0,61}" +
		"(?:" + urlLabelEndSubRe + "|" + urlLabelUnicodeSubRe + ")" +
		")?"

	// hostSubRe is the sub-expression that matches a valid host prefix.
	// Allows custom port.
	hostSubRe = hostLabelSubRe + "(?:\\." + hostLabelSubRe + ")+(?::\\d+)?"

	// hostRe is a regexp that matches a valid host prefix. Additional
	// validation of unicode strings is needed for matches.
	hostRe = regexp.MustCompile("^" + hostSubRe + "$")
)

// FriendlyHost describes a registry instance identified in source strings by a
// simple bare hostname like registry.terraform.io.
type FriendlyHost struct {
	Raw string
}

func NewFriendlyHost(host string) *FriendlyHost {
	return &FriendlyHost{Raw: host}
}

// ParseFriendlyHost attempts to parse a valid "friendly host" prefix from the
// given string. If no valid prefix is found, host will be nil and rest will
// contain the full source string. The host prefix must terminate at the end of
// the input or at the first / character. If one or more characters exist after
// the first /, they will be returned as rest (without the / delimiter).
// Hostnames containing punycode WILL be parsed successfully since they may have
// come from an internal normalized source string, however should be considered
// invalid if the string came from a user directly. This must be checked
// explicitly for user-input strings by calling Valid() on the
// returned host.
func ParseFriendlyHost(source string) (host *FriendlyHost, rest string) {
	parts := strings.SplitN(source, "/", 2)

	if hostRe.MatchString(parts[0]) {
		host = &FriendlyHost{Raw: parts[0]}
		if len(parts) == 2 {
			rest = parts[1]
		}
		return
	}

	// No match, return whole string as rest along with nil host
	rest = source
	return
}

// Valid returns whether the host prefix is considered valid in any case.
// Example of invalid prefixes might include ones that don't conform to the host
// name specifications. Not that IDN prefixes containing punycode are not valid
// input which we expect to always be in user-input or normalised display form.
func (h *FriendlyHost) Valid() bool {
	return svchost.IsValid(h.Raw)
}

// Display returns the host formatted for display to the user in CLI or web
// output.
func (h *FriendlyHost) Display() string {
	return svchost.ForDisplay(h.Raw)
}

// Normalized returns the host formatted for internal reference or comparison.
func (h *FriendlyHost) Normalized() string {
	host, err := svchost.ForComparison(h.Raw)
	if err != nil {
		return InvalidHostString
	}
	return string(host)
}

// String returns the host formatted as the user originally typed it assuming it
// was parsed from user input.
func (h *FriendlyHost) String() string {
	return h.Raw
}

// Equal compares the FriendlyHost against another instance taking normalization
// into account. Invalid hosts cannot be compared and will always return false.
func (h *FriendlyHost) Equal(other *FriendlyHost) bool {
	if other == nil {
		return false
	}

	otherHost, err := svchost.ForComparison(other.Raw)
	if err != nil {
		return false
	}

	host, err := svchost.ForComparison(h.Raw)
	if err != nil {
		return false
	}

	return otherHost == host
}