package main import ( "bufio" "encoding/xml" "errors" "io" "log" "net/http" "strings" "time" ) const ICAL_TIME = "20060102T150405" const ICAL_DATE = "20060102" type CalDAV struct { ServerSettings httpClient *http.Client } type ICal struct { data []string eventData []string // sub slice } func (i ICal) Summary() string { for _, s := range i.eventData { if strings.HasPrefix(s, "SUMMARY:") { return s[len("SUMMARY:"):] } } return "" } func (i ICal) UID() string { for _, s := range i.eventData { if strings.HasPrefix(s, "UID:") { return s[len("UID:"):] } } return "" } func (i ICal) getTimeField(f string) time.Time { for _, s := range i.eventData { if strings.HasPrefix(s, f) { st := s[strings.LastIndex(s, ":")+1:] // BEWARE, we don't parse possible ICal timezones (yet). If it is not // a UTC time, we simply assume local time. zone := time.Local if st[len(st)-1:] == "Z" { st = st[:len(st)-1] zone = time.UTC } t, err := time.ParseInLocation(ICAL_TIME, st, zone) if err != nil { log.Printf("Cannot decode %s '%s': %s", f, st, err) } return t } } return time.Time{} } func (i ICal) Start() time.Time { return i.getTimeField("DTSTART") } func (i ICal) End() time.Time { return i.getTimeField("DTEND") } func (i *ICal) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { s := "" err := d.DecodeElement(&s, &start) if err != nil { return err } ical, err := ParseICal(strings.NewReader(s)) if err != nil { return err } *i = *ical return nil } func ParseICal(r io.Reader) (*ICal, error) { var data []string scanner := bufio.NewScanner(r) eventStart := -1 eventEnd := -1 i := 0 for scanner.Scan() { line := scanner.Text() data = append(data, line) if eventStart == -1 && line == "BEGIN:VEVENT" { eventStart = i } if eventEnd == -1 && line == "END:VEVENT" { eventEnd = i } i++ } err := scanner.Err() if err != nil { return nil, err } // If we didn't find a start or an end, abort. if eventStart < 0 || eventEnd <= eventStart { return nil, errors.New("calendar item without event") } return &ICal{ data: data, eventData: data[eventStart+1 : eventEnd], }, err } func NewCalDAV(settings ServerSettings) *CalDAV { return &CalDAV{ httpClient: http.DefaultClient, ServerSettings: settings, } } type CalDAVItem struct { HRef string ICal } func (c *CalDAV) GetEvents() ([]CalDAVItem, error) { req, err := http.NewRequest("PROPFIND", c.URL, strings.NewReader(``)) if err != nil { return nil, err } req.Header.Set("Content-Type", "text/xml") req.SetBasicAuth(c.Username, c.Password) resp, err := http.DefaultClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() ms := &MultiStatus{} err = xml.NewDecoder(resp.Body).Decode(ms) if err != nil { return nil, err } result := make([]CalDAVItem, 0, len(ms.Response)) for _, p := range ms.Response { item := CalDAVItem{p.HRef, p.PropStat.Prop.CalendarData} result = append(result, item) } return result, nil } type MultiStatus struct { Response []PropFindResponse `xml:"response"` } type PropFindResponse struct { HRef string `xml:"href"` PropStat struct { Prop struct { CalendarData ICal `xml:"calendar-data"` } `xml:"prop"` } `xml:"propstat"` }