2021-12-03 01:58:37 +00:00
|
|
|
package domain
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2021-12-25 18:53:49 +00:00
|
|
|
"net/url"
|
2021-12-29 20:08:30 +00:00
|
|
|
"strconv"
|
2021-12-03 01:58:37 +00:00
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
http "github.com/valyala/fasthttp"
|
2021-12-25 18:53:49 +00:00
|
|
|
"golang.org/x/xerrors"
|
2021-12-03 01:58:37 +00:00
|
|
|
)
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
// Me is a URL user identifier.
|
2021-12-03 01:58:37 +00:00
|
|
|
type Me struct {
|
2021-12-25 18:53:49 +00:00
|
|
|
me *http.URI
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// ParseMe parse string as me URL identifier.
|
2022-02-01 17:27:48 +00:00
|
|
|
//nolint: funlen, cyclop
|
2022-01-29 14:54:37 +00:00
|
|
|
func ParseMe(raw string) (*Me, error) {
|
2021-12-25 18:53:49 +00:00
|
|
|
me := http.AcquireURI()
|
|
|
|
if err := me.Parse(nil, []byte(raw)); err != nil {
|
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: err.Error(),
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
scheme := string(me.Scheme())
|
2021-12-03 01:58:37 +00:00
|
|
|
if scheme != "http" && scheme != "https" {
|
2021-12-25 18:53:49 +00:00
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: "profile URL MUST have either an https or http scheme",
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
path := string(me.PathOriginal())
|
2021-12-03 01:58:37 +00:00
|
|
|
if path == "" || strings.Contains(path, "/.") || strings.Contains(path, "/..") {
|
2021-12-25 18:53:49 +00:00
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: "profile URL MUST contain a path component (/ is a valid path), MUST NOT " +
|
|
|
|
"contain single-dot or double-dot path segments",
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
if me.Hash() != nil {
|
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: "profile URL MUST NOT contain a fragment component",
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
if me.Username() != nil || me.Password() != nil {
|
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: "profile URL MUST NOT contain a username or password component",
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
domain := string(me.Host())
|
|
|
|
if domain == "" {
|
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: "profile host name MUST be a domain name",
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
if _, port, _ := net.SplitHostPort(domain); port != "" {
|
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: "profile MUST NOT contain a port",
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
if net.ParseIP(domain) != nil {
|
|
|
|
return nil, Error{
|
2022-01-29 14:33:50 +00:00
|
|
|
Code: ErrorCodeInvalidRequest,
|
2021-12-25 18:53:49 +00:00
|
|
|
Description: "profile MUST NOT be ipv4 or ipv6 addresses",
|
|
|
|
URI: "https://indieauth.net/source/#user-profile-url",
|
2022-01-29 17:50:45 +00:00
|
|
|
State: "",
|
2022-01-29 14:33:50 +00:00
|
|
|
frame: xerrors.Caller(1),
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
return &Me{me: me}, nil
|
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// TestMe returns valid random generated me for tests.
|
2022-01-29 13:49:27 +00:00
|
|
|
func TestMe(tb testing.TB, src string) *Me {
|
2021-12-25 18:53:49 +00:00
|
|
|
tb.Helper()
|
|
|
|
|
2022-01-29 14:54:37 +00:00
|
|
|
me, err := ParseMe(src)
|
2022-01-30 17:49:25 +00:00
|
|
|
if err != nil {
|
2022-02-01 17:27:48 +00:00
|
|
|
tb.Fatal(err)
|
2022-01-30 17:49:25 +00:00
|
|
|
}
|
2021-12-25 18:53:49 +00:00
|
|
|
|
|
|
|
return me
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// UnmarshalForm implements custom unmarshler for form values.
|
2021-12-29 20:08:30 +00:00
|
|
|
func (m *Me) UnmarshalForm(v []byte) error {
|
2022-01-29 14:54:37 +00:00
|
|
|
me, err := ParseMe(string(v))
|
2021-12-25 18:53:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("UnmarshalForm: %w", err)
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-29 20:08:30 +00:00
|
|
|
*m = *me
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// UnmarshalJSON implements custom unmarshler for JSON.
|
2021-12-29 20:08:30 +00:00
|
|
|
func (m *Me) UnmarshalJSON(v []byte) error {
|
|
|
|
src, err := strconv.Unquote(string(v))
|
|
|
|
if err != nil {
|
2022-01-29 17:50:45 +00:00
|
|
|
return fmt.Errorf("UnmarshalJSON: %w", err)
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 14:54:37 +00:00
|
|
|
me, err := ParseMe(src)
|
2021-12-29 20:08:30 +00:00
|
|
|
if err != nil {
|
2022-01-29 17:50:45 +00:00
|
|
|
return fmt.Errorf("UnmarshalJSON: %w", err)
|
2021-12-29 20:08:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
*m = *me
|
2021-12-25 18:53:49 +00:00
|
|
|
|
|
|
|
return nil
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// MarshalJSON implements custom marshler for JSON.
|
2022-01-12 18:04:40 +00:00
|
|
|
func (m Me) MarshalJSON() ([]byte, error) {
|
|
|
|
return []byte(strconv.Quote(m.String())), nil
|
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// URI returns copy of parsed me in *fasthttp.URI representation.
|
2021-12-03 01:58:37 +00:00
|
|
|
// This copy MUST be released via fasthttp.ReleaseURI.
|
2022-01-12 18:04:40 +00:00
|
|
|
func (m Me) URI() *http.URI {
|
2021-12-29 20:08:30 +00:00
|
|
|
if m.me == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-03 01:58:37 +00:00
|
|
|
u := http.AcquireURI()
|
2021-12-25 18:53:49 +00:00
|
|
|
m.me.CopyTo(u)
|
2021-12-03 01:58:37 +00:00
|
|
|
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// URL returns copy of parsed me in *url.URL representation.
|
2022-01-12 18:04:40 +00:00
|
|
|
func (m Me) URL() *url.URL {
|
2021-12-29 20:08:30 +00:00
|
|
|
if m.me == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
return &url.URL{
|
2022-02-01 17:27:48 +00:00
|
|
|
ForceQuery: false,
|
|
|
|
Fragment: string(m.me.Hash()),
|
|
|
|
Host: string(m.me.Host()),
|
|
|
|
Opaque: "",
|
|
|
|
Path: string(m.me.Path()),
|
|
|
|
RawFragment: "",
|
|
|
|
RawPath: string(m.me.PathOriginal()),
|
|
|
|
RawQuery: string(m.me.QueryString()),
|
|
|
|
Scheme: string(m.me.Scheme()),
|
|
|
|
User: nil,
|
2021-12-25 18:53:49 +00:00
|
|
|
}
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 17:50:45 +00:00
|
|
|
// String returns string representation of me.
|
2022-01-12 18:04:40 +00:00
|
|
|
func (m Me) String() string {
|
2021-12-29 20:08:30 +00:00
|
|
|
if m.me == nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2021-12-25 18:53:49 +00:00
|
|
|
return m.me.String()
|
2021-12-03 01:58:37 +00:00
|
|
|
}
|