package human import ( "encoding/json" "errors" "net/url" "strings" "time" ) const Version = "0.1.1" var ( stdPorts = map[string]string{ "http": "80", "https": "443", "gemini": "1965", } ) var ( ErrInvalidURL = errors.New("invalid url") ) // URL represents a standard [url.URL] following normalization rules of human.json. type URL url.URL // NewURL creates a new [URL]. func NewURL(s string) (*URL, error) { raw, err := url.Parse(s) if err != nil { return nil, err } u := URL(*raw) return &u, nil } // Match returns true if url is within the scope. // See https://codeberg.org/robida/human.json#url-matching. func (u *URL) Match(url *URL) bool { u.normalize() url.normalize() return u.Scheme == url.Scheme && u.Host == url.Host && (u.Path == url.Path || strings.HasPrefix(url.Path, u.Path+"/")) } func (u *URL) normalize() { if strings.Contains(u.Host, ":") { sp := strings.Split(u.Host, ":") if v, ok := stdPorts[u.Scheme]; ok && v == sp[1] { u.Host = sp[0] } } u.Path = strings.TrimSuffix(u.Path, "/") } func (u *URL) UnmarshalJSON(b []byte) error { var s string err := json.Unmarshal(b, &s) if err != nil { return err } raw, err := url.Parse(s) if err != nil { return err } *u = URL(*raw) u.normalize() return nil } func (u *URL) MarshalJSON() ([]byte, error) { u.normalize() return json.Marshal(u.String()) } func (u *URL) String() string { ur := url.URL(*u) return ur.String() } // Vouch a website to trust it. type Vouch struct { URL *URL `json:"url"` VouchedAt time.Time `json:"-"` } func (v *Vouch) UnmarshalJSON(b []byte) error { type t Vouch var r struct { t VouchedAt string `json:"vouched_at"` } err := json.Unmarshal(b, &r) if err != nil { return err } *v = Vouch(r.t) v.VouchedAt, err = time.Parse(time.DateOnly, r.VouchedAt) return err } func (v *Vouch) MarshalJSON() ([]byte, error) { type t Vouch r := struct { t VouchedAt string `json:"vouched_at"` }{t(*v), v.VouchedAt.Format(time.DateOnly)} return json.Marshal(r) } // Human represents the human.json file. type Human struct { Version string `json:"version"` URL *URL `json:"url"` Vouches []*Vouch `json:"vouches"` }