Skip to content

Commit

Permalink
feat: add resourcename.Validate
Browse files Browse the repository at this point in the history
Includes a copied implementation of the not-yet-public (due to some edge
cases) implementation of domain name validation in go/src/net.
  • Loading branch information
odsod committed May 24, 2021
1 parent d3e3707 commit b32fa33
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 0 deletions.
54 changes: 54 additions & 0 deletions resourcename/isdomainname.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package resourcename

// isDomainName is copied from go/src/net/dnsclient.go pending resolution of the following issues:
// - https://github.com/golang/go/issues/31671
// - https://github.com/golang/go/issues/17659
func isDomainName(s string) bool {
// See RFC 1035, RFC 3696.
// Presentation format has dots before every label except the first, and the
// terminal empty label is optional here because we assume fully-qualified
// (absolute) input. We must therefore reserve space for the first and last
// labels' length octets in wire format, where they are necessary and the
// maximum total length is 255.
// So our _effective_ maximum is 253, but 254 is not rejected if the last
// character is a dot.
l := len(s)
if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
return false
}
last := byte('.')
partlen := 0
for i := 0; i < len(s); i++ {
c := s[i]
switch {
default:
return false
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || '0' <= c && c <= '9':
partlen++
case c == '-':
// Byte before dash cannot be dot.
if last == '.' {
return false
}
partlen++
case c == '.':
// Byte before dot cannot be dot, dash.
if last == '.' || last == '-' {
return false
}
if partlen > 63 || partlen == 0 {
return false
}
partlen = 0
}
last = c
}
if last == '-' || partlen > 63 {
return false
}
return true
}
32 changes: 32 additions & 0 deletions resourcename/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package resourcename

import (
"fmt"
)

// Validate that a resource name conforms to the restrictions outlined in AIP-122, primarily that each segment
// must be a valid DNS name.
// See: https://google.aip.dev/122
func Validate(name string) error {
if name == "" {
return fmt.Errorf("resource name is empty")
}
var sc Scanner
sc.Init(name)
var i int
for sc.Scan() {
i++
switch {
case sc.Segment() == "":
return fmt.Errorf("segment %d is empty", i)
case sc.Segment() == Wildcard:
continue
case !isDomainName(string(sc.Segment())):
return fmt.Errorf("segment '%s': not a valid DNS name", sc.Segment())
}
}
if sc.Full() && !isDomainName(sc.ServiceName()) {
return fmt.Errorf("service '%s': not a valid DNS name", sc.Segment())
}
return nil
}
96 changes: 96 additions & 0 deletions resourcename/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package resourcename

import (
"testing"

"gotest.tools/v3/assert"
)

func TestValidate(t *testing.T) {
t.Parallel()
for _, tt := range []struct {
name string
input string
errorContains string
}{
{
name: "empty",
input: "",
errorContains: "empty",
},

{
name: "invalid DNS characters",
input: "ice cream is best",
errorContains: "not a valid DNS name",
},

{
name: "invalid DNS characters in segment",
input: "foo/bar/ice cream is best",
errorContains: "not a valid DNS name",
},

{
name: "invalid DNS characters in domain",
input: "//ice cream is best.com/foo/bar",
errorContains: "not a valid DNS name",
},

{
name: "singleton",
input: "foo",
},

{
name: "singleton wildcard",
input: "-",
},

{
name: "multi",
input: "foo/bar",
},

{
name: "multi wildcard at start",
input: "-/bar",
},

{
name: "multi wildcard at end",
input: "foo/-",
},

{
name: "multi wildcard at middle",
input: "foo/-/bar",
},

{
name: "numeric",
input: "foo/1234/bar",
},

{
name: "camelCase",
input: "FOO/1234/bAr",
},

{
name: "full",
input: "//example.com/foo/bar",
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
err := Validate(tt.input)
if tt.errorContains != "" {
assert.ErrorContains(t, err, tt.errorContains)
} else {
assert.NilError(t, err)
}
})
}
}

0 comments on commit b32fa33

Please sign in to comment.