Write on the margins of the internet. Powered by the AT Protocol.
margin.at
extension
web
atproto
comments
1package verification
2
3import (
4 "fmt"
5 "io"
6 "net/http"
7 "net/url"
8 "regexp"
9 "strings"
10 "time"
11
12 "margin.at/internal/logger"
13)
14
15var client = &http.Client{
16 Timeout: 10 * time.Second,
17 CheckRedirect: func(req *http.Request, via []*http.Request) error {
18 if len(via) >= 3 {
19 return fmt.Errorf("too many redirects")
20 }
21 return nil
22 },
23}
24
25var linkTagPattern = regexp.MustCompile(`<link[^>]+rel=["']site\.standard\.document["'][^>]+href=["']([^"']+)["'][^>]*/?>|<link[^>]+href=["']([^"']+)["'][^>]+rel=["']site\.standard\.document["'][^>]*/?>`)
26
27func VerifyPublication(pubURL, expectedURI string) error {
28 pubURL = strings.TrimRight(pubURL, "/")
29
30 parsed, err := url.Parse(pubURL)
31 if err != nil {
32 return fmt.Errorf("invalid publication URL: %w", err)
33 }
34
35 wellKnownPath := "/.well-known/site.standard.publication"
36 if parsed.Path != "" && parsed.Path != "/" {
37 wellKnownPath += parsed.Path
38 }
39 wellKnownURL := fmt.Sprintf("%s://%s%s", parsed.Scheme, parsed.Host, wellKnownPath)
40
41 req, err := http.NewRequest("GET", wellKnownURL, nil)
42 if err != nil {
43 return fmt.Errorf("invalid URL: %w", err)
44 }
45 req.Header.Set("User-Agent", "Margin/1.0 (Standard.site verification)")
46
47 resp, err := client.Do(req)
48 if err != nil {
49 return fmt.Errorf("failed to fetch %s: %w", wellKnownURL, err)
50 }
51 defer resp.Body.Close()
52
53 if resp.StatusCode != http.StatusOK {
54 return fmt.Errorf("well-known endpoint returned %d", resp.StatusCode)
55 }
56
57 body, err := io.ReadAll(io.LimitReader(resp.Body, 1024))
58 if err != nil {
59 return fmt.Errorf("failed to read response: %w", err)
60 }
61
62 returnedURI := strings.TrimSpace(string(body))
63 if returnedURI != expectedURI {
64 return fmt.Errorf("URI mismatch: expected %s, got %s", expectedURI, returnedURI)
65 }
66
67 return nil
68}
69
70func VerifyDocument(docURL, expectedURI string) error {
71 req, err := http.NewRequest("GET", docURL, nil)
72 if err != nil {
73 return fmt.Errorf("invalid URL: %w", err)
74 }
75 req.Header.Set("User-Agent", "Margin/1.0 (Standard.site verification)")
76
77 resp, err := client.Do(req)
78 if err != nil {
79 return fmt.Errorf("failed to fetch %s: %w", docURL, err)
80 }
81 defer resp.Body.Close()
82
83 if resp.StatusCode != http.StatusOK {
84 return fmt.Errorf("document URL returned %d", resp.StatusCode)
85 }
86
87 body, err := io.ReadAll(io.LimitReader(resp.Body, 256*1024))
88 if err != nil {
89 return fmt.Errorf("failed to read document: %w", err)
90 }
91
92 html := string(body)
93
94 matches := linkTagPattern.FindAllStringSubmatch(html, -1)
95 for _, m := range matches {
96 href := m[1]
97 if href == "" {
98 href = m[2]
99 }
100 if strings.TrimSpace(href) == expectedURI {
101 return nil
102 }
103 }
104
105 return fmt.Errorf("no matching <link rel=\"site.standard.document\"> tag found for %s", expectedURI)
106}
107
108func VerifyPublicationAsync(pubURL, uri string, onVerified func(string)) {
109 go func() {
110 if err := VerifyPublication(pubURL, uri); err != nil {
111 return
112 }
113 logger.Info("Publication verified: %s", uri)
114 if onVerified != nil {
115 onVerified(uri)
116 }
117 }()
118}
119
120func VerifyDocumentAsync(docURL, uri string, onVerified func(string)) {
121 go func() {
122 if err := VerifyDocument(docURL, uri); err != nil {
123 return
124 }
125 logger.Info("Document verified: %s", uri)
126 if onVerified != nil {
127 onVerified(uri)
128 }
129 }()
130}