1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
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
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:"string"`
URL *URL `json:"url"`
Vouches []*Vouch `json:"vouches"`
}
|