diff --git a/registry/regsrc/friendly_host.go b/registry/regsrc/friendly_host.go index 648e2a193..28ca9b0aa 100644 --- a/registry/regsrc/friendly_host.go +++ b/registry/regsrc/friendly_host.go @@ -4,7 +4,7 @@ import ( "regexp" "strings" - "github.com/hashicorp/terraform/svchost" + "golang.org/x/net/idna" ) var ( @@ -95,26 +95,42 @@ func ParseFriendlyHost(source string) (host *FriendlyHost, rest string) { // 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) + if h.Display() == InvalidHostString { + return false + } + if h.Normalized() == InvalidHostString { + return false + } + if containsPuny(h.Raw) { + return false + } + return true } // Display returns the host formatted for display to the user in CLI or web // output. func (h *FriendlyHost) Display() string { - hostname, err := svchost.ForComparison(h.Raw) + parts := strings.SplitN(h.Raw, ":", 2) + var err error + parts[0], err = idna.Display.ToUnicode(parts[0]) if err != nil { return InvalidHostString } - return hostname.ForDisplay() + return strings.Join(parts, ":") } // Normalized returns the host formatted for internal reference or comparison. func (h *FriendlyHost) Normalized() string { - hostname, err := svchost.ForComparison(h.Raw) + // For now IDNA does all the normalisation we need including case-folding + // pure ASCII to lower. But breaks if a custom port is included while we + // want to allow that and normalize comparison including it, + parts := strings.SplitN(h.Raw, ":", 2) + var err error + parts[0], err = idna.Lookup.ToASCII(parts[0]) if err != nil { return InvalidHostString } - return hostname.String() + return strings.Join(parts, ":") } // String returns the host formatted as the user originally typed it assuming it diff --git a/registry/regsrc/friendly_host_test.go b/registry/regsrc/friendly_host_test.go index 740395bf6..e87774cfe 100644 --- a/registry/regsrc/friendly_host_test.go +++ b/registry/regsrc/friendly_host_test.go @@ -95,36 +95,26 @@ func TestFriendlyHost(t *testing.T) { if v := gotHost.String(); v != tt.wantHost { t.Fatalf("String() = %v, want %v", v, tt.wantHost) } - if v := gotHost.Valid(); v != tt.wantValid { - t.Fatalf("Valid() = %v, want %v", v, tt.wantValid) - } - - // FIXME: should we allow punycode as input - if !tt.wantValid { - return - } - if v := gotHost.Display(); v != tt.wantDisplay { t.Fatalf("Display() = %v, want %v", v, tt.wantDisplay) } if v := gotHost.Normalized(); v != tt.wantNorm { t.Fatalf("Normalized() = %v, want %v", v, tt.wantNorm) } + if v := gotHost.Valid(); v != tt.wantValid { + t.Fatalf("Valid() = %v, want %v", v, tt.wantValid) + } if gotRest != strings.TrimLeft(sfx, "/") { t.Fatalf("ParseFriendlyHost() rest = %v, want %v", gotRest, strings.TrimLeft(sfx, "/")) } // Also verify that host compares equal with all the variants. if !gotHost.Equal(&FriendlyHost{Raw: tt.wantDisplay}) { - t.Fatalf("Equal() should be true for %s and %t", tt.wantHost, tt.wantValid) + t.Fatalf("Equal() should be true for %s and %s", tt.wantHost, tt.wantValid) + } + if !gotHost.Equal(&FriendlyHost{Raw: tt.wantNorm}) { + t.Fatalf("Equal() should be true for %s and %s", tt.wantHost, tt.wantNorm) } - - // FIXME: Do we need to accept normalized input? - //if !gotHost.Equal(&FriendlyHost{Raw: tt.wantNorm}) { - // fmt.Println(gotHost.Normalized(), tt.wantNorm) - // fmt.Println(" ", (&FriendlyHost{Raw: tt.wantNorm}).Normalized()) - // t.Fatalf("Equal() should be true for %s and %s", tt.wantHost, tt.wantNorm) - //} }) } diff --git a/registry/regsrc/module.go b/registry/regsrc/module.go index b6671c8a4..3080cddb6 100644 --- a/registry/regsrc/module.go +++ b/registry/regsrc/module.go @@ -33,12 +33,13 @@ var ( fmt.Sprintf("^(%s)\\/(%s)\\/(%s)(?:\\/\\/(.*))?$", nameSubRe, nameSubRe, providerSubRe)) - // disallowed is a set of hostnames that have special usage in modules and - // can't be registry hosts - disallowed = map[string]bool{ - "github.com": true, - "bitbucket.org": true, - } + // NameRe is a regular expression defining the format allowed for namespace + // or name fields in module registry implementations. + NameRe = regexp.MustCompile("^" + nameSubRe + "$") + + // ProviderRe is a regular expression defining the format allowed for + // provider fields in module registry implementations. + ProviderRe = regexp.MustCompile("^" + providerSubRe + "$") ) // Module describes a Terraform Registry Module source. @@ -84,10 +85,8 @@ func NewModule(host, namespace, name, provider, submodule string) *Module { func ParseModuleSource(source string) (*Module, error) { // See if there is a friendly host prefix. host, rest := ParseFriendlyHost(source) - if host != nil { - if !host.Valid() || disallowed[host.Display()] { - return nil, ErrInvalidModuleSource - } + if host != nil && !host.Valid() { + return nil, ErrInvalidModuleSource } matches := moduleSourceRe.FindStringSubmatch(rest) @@ -132,12 +131,6 @@ func (m *Module) String() string { return m.formatWithPrefix(hostPrefix, true) } -// Module returns just the registry ID of the module, without a hostname or -// suffix. -func (m *Module) Module() string { - return fmt.Sprintf("%s/%s/%s", m.RawNamespace, m.RawName, m.RawProvider) -} - // Equal compares the module source against another instance taking // normalization into account. func (m *Module) Equal(other *Module) bool { diff --git a/registry/regsrc/module_test.go b/registry/regsrc/module_test.go index bae502b0d..19d9dfa19 100644 --- a/registry/regsrc/module_test.go +++ b/registry/regsrc/module_test.go @@ -96,16 +96,6 @@ func TestModule(t *testing.T) { source: "foo.com/var/baz?otherthing", wantErr: true, }, - { - name: "disallow github", - source: "github.com/HashiCorp/Consul/aws", - wantErr: true, - }, - { - name: "disallow bitbucket", - source: "bitbucket.org/HashiCorp/Consul/aws", - wantErr: true, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {