🏷️ Created me domain

This commit is contained in:
Maxim Lebedev 2021-12-03 06:58:37 +05:00
parent 42e8e8b989
commit dd70b34dcb
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
2 changed files with 184 additions and 0 deletions

114
internal/domain/me.go Normal file
View File

@ -0,0 +1,114 @@
package domain
import (
"fmt"
"net"
"strings"
"testing"
"github.com/stretchr/testify/require"
http "github.com/valyala/fasthttp"
)
// Me is a user URL identifier.
type Me struct {
uri *http.URI
isValid bool
}
// UnmarshalForm implements a custom form.Unmarshaler.
func (me *Me) UnmarshalForm(v []byte) error {
if err := me.Parse(v); err != nil {
return fmt.Errorf("cannot unmarshal form: %w", err)
}
return nil
}
// Parse parse and validate me identifier.
func (me *Me) Parse(v []byte) error {
if me.uri != nil {
http.ReleaseURI(me.uri)
}
me.uri = http.AcquireURI()
if err := me.uri.Parse(nil, v); err != nil {
return fmt.Errorf("cannot parse me: %w", err)
}
// NOTE(toby3d): MUST have either an https or http scheme
scheme := string(me.uri.Scheme())
if scheme != "http" && scheme != "https" {
return nil
}
// NOTE(toby3d): MUST contain a path component (/ is a valid path)
// NOTE(toby3d): MUST NOT contain single-dot or double-dot path segments
path := string(me.uri.PathOriginal())
if path == "" || strings.Contains(path, "/.") || strings.Contains(path, "/..") {
return nil
}
// NOTE(toby3d): MUST NOT contain a fragment component
if me.uri.Hash() != nil {
return nil
}
// NOTE(toby3d): MUST NOT contain a username or password component
if me.uri.Username() != nil || me.uri.Password() != nil {
return nil
}
// NOTE(toby3d): host names MUST be domain names
host := string(me.uri.Host())
if host == "" {
return nil
}
// NOTE(toby3d): MUST NOT contain a port
if _, _, err := net.SplitHostPort(host); err == nil {
return nil
}
// NOTE(toby3d): MUST NOT be ipv4 or ipv6 addresses
if net.ParseIP(host) != nil {
return nil
}
me.isValid = true
return nil
}
// String returns string representation of Me.
func (me *Me) String() string {
if me.uri == nil {
return ""
}
return me.uri.String()
}
// URI returns copy of parsed *fasthttp.URI.
// This copy MUST be released via fasthttp.ReleaseURI.
func (me *Me) URI() *http.URI {
u := http.AcquireURI()
me.uri.CopyTo(u)
return u
}
// IsValid returns true if Me is a valid identifier.
func (me *Me) IsValid() bool {
return me.isValid
}
// TestMe returns a valid testing Me.
func TestMe(tb testing.TB) *Me {
tb.Helper()
me := new(Me)
require.NoError(tb, me.Parse([]byte("https://user.example.net/")))
return me
}

View File

@ -0,0 +1,70 @@
package domain_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"source.toby3d.me/website/oauth/internal/domain"
)
func TestMeIsValid(t *testing.T) {
t.Parallel()
for _, testCase := range []struct {
name string
input string
isValid bool
}{{
name: "valid",
input: "https://example.com/",
isValid: true,
}, {
name: "valid with path",
input: "https://example.com/username",
isValid: true,
}, {
name: "valid with query",
input: "https://example.com/users?id=100",
isValid: true,
}, {
name: "missing scheme",
input: "example.com",
isValid: false,
}, {
name: "invalid scheme",
input: "mailto:user@example.com",
isValid: false,
}, {
name: "contains a double-dot path segment",
input: "https://example.com/foo/../bar",
isValid: false,
}, {
name: "contains a fragment",
input: "https://example.com/#me",
isValid: false,
}, {
name: "contains a username and password",
input: "https://user:pass@example.com/",
isValid: false,
}, {
name: "contains a port",
input: "https://example.com:8443/",
isValid: false,
}, {
name: "host is an IP address",
input: "https://172.28.92.51/",
isValid: false,
}} {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
me := new(domain.Me)
require.NoError(t, me.Parse([]byte(testCase.input)))
assert.Equal(t, testCase.isValid, me.IsValid())
})
}
}