-
Notifications
You must be signed in to change notification settings - Fork 1
/
webcal.go
104 lines (86 loc) · 2.48 KB
/
webcal.go
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
package server
import (
"context"
"fmt"
"net/http"
"net/url"
"slices"
ics "github.com/arran4/golang-ical"
"github.com/brackendawson/webcal-proxy/cache"
)
func parseURLScheme(ctx context.Context, addr string) (string, error) {
addrURL, err := url.Parse(addr)
if err != nil {
log(ctx).Warnf("invalid calendar url: %s", err)
return "", newErrorWithMessage(
http.StatusBadRequest,
"Bad url. Include a protocol, host, and path, eg: webcal://example.com/events",
)
}
if addrURL.Scheme == "webcal" {
addrURL.Scheme = "http"
}
if !slices.Contains([]string{"http", "https"}, addrURL.Scheme) {
return "", newErrorWithMessage(
http.StatusBadRequest,
"Unsupported protocol scheme, url should be webcal, https, or http.",
)
}
return addrURL.String(), nil
}
func (s *Server) getUpstreamCalendar(ctx context.Context, url string) (*ics.Calendar, error) {
upstreamURL, err := parseURLScheme(ctx, url)
if err != nil {
return nil, err
}
upstream, err := s.fetch(upstreamURL)
if err != nil {
log(ctx).Warnf("Failed to fetch calendar %q: %s", upstreamURL, err)
return nil, newErrorWithMessage(
http.StatusBadGateway,
"Failed to fetch calendar",
)
}
return upstream, nil
}
func decodeCache(ctx context.Context, upstreamURL string, rawCache string) (*ics.Calendar, bool) {
if rawCache == "" {
return nil, false
}
cache, err := cache.ParseWebcal(rawCache)
if err != nil {
log(ctx).Warnf("Failed to parse cache: %s. Continuing without.")
return nil, false
}
if cache.URL != upstreamURL {
log(ctx).Debugf("Cache was for old URL %q, ignoring.", cache.URL)
return nil, false
}
log(ctx).Debug("Using cached calendar")
return cache.Calendar, true
}
func (s *Server) getUpstreamWithCache(ctx context.Context, upstreamURL string, rawCache string) (_ *ics.Calendar, usedCache bool, _ error) {
upstream, ok := decodeCache(ctx, upstreamURL, rawCache)
if !ok {
upstream, err := s.getUpstreamCalendar(ctx, upstreamURL)
if err != nil {
return nil, false, err
}
return upstream, false, nil
}
return upstream, true, nil
}
// fetch fetches the given url from the given IP address
func (s *Server) fetch(url string) (*ics.Calendar, error) {
s.semaphore <- struct{}{}
defer func() { <-s.semaphore }()
upstream, err := s.client.Get(url)
if err != nil {
return nil, err
}
defer upstream.Body.Close()
if upstream.StatusCode < 200 || upstream.StatusCode >= 300 {
return nil, fmt.Errorf("bad status: %s", upstream.Status)
}
return ics.ParseCalendar(upstream.Body)
}