func hydrateNestedComponent(v reflect.Value, component *token) error { // create a new object to hold the property value var vnew, varr = newValue(v) if err := hydrateComponent(vnew, component); err != nil { return utils.NewError(hydrateNestedComponent, "unable to decode component", component, err) } if varr { // for arrays, append the new value into the array structure voldval := dereferencePointerValue(v) if !voldval.CanSet() { return utils.NewError(hydrateNestedComponent, "unable to set array value", v, nil) } else { voldval.Set(reflect.Append(voldval, vnew)) } } else if !v.CanSet() { return utils.NewError(hydrateNestedComponent, "unable to set pointer value", v, nil) } else { // everything else should be a pointer, set it directly v.Set(vnew) } return nil }
func (c *Calendar) ValidateICalValue() error { for i, e := range c.Events { if e == nil { continue // skip nil events } if err := e.ValidateICalValue(); err != nil { msg := fmt.Sprintf("event %d failed validation", i) return utils.NewError(c.ValidateICalValue, msg, c, err) } if e.DateStart == nil && c.Method == "" { msg := fmt.Sprintf("no value for method and no start date defined on event %d", i) return utils.NewError(c.ValidateICalValue, msg, c, nil) } } if c.UsingTimeZone() && !c.UsingGlobalTimeZone() { for i, t := range c.TimeZones { if t == nil || t.Id != c.TimeZoneId { msg := fmt.Sprintf("timezone ID does not match timezone %d", i) return utils.NewError(c.ValidateICalValue, msg, c, nil) } } } return nil }
// decodes the duration of time from iCalendar format func (d *Duration) DecodeICalValue(value string) error { var seconds int64 var isPast = strings.HasPrefix(value, "-P") var matches = durationRegEx.FindAllStringSubmatch(value, -1) for _, match := range matches { var multiplier int64 ivalue, err := strconv.ParseInt(match[1], 10, 64) if err != nil { return utils.NewError(d.DecodeICalValue, "unable to decode duration value "+match[1], d, nil) } switch match[2] { case "S": multiplier = 1 case "M": multiplier = 60 case "H": multiplier = 60 * 60 case "D": multiplier = 60 * 60 * 24 case "W": multiplier = 60 * 60 * 24 * 7 default: return utils.NewError(d.DecodeICalValue, "unable to decode duration segment "+match[2], d, nil) } seconds = seconds + multiplier*ivalue } d.d = time.Duration(seconds) * time.Second if isPast { d.d = -d.d } return nil }
// attempts to fetch an event on the remote CalDAV server func (c *Client) QueryEvents(path string, query *cent.CalendarQuery) (events []*components.Event, oerr error) { ms := new(cent.Multistatus) if req, err := c.Server().WebDAV().NewRequest("REPORT", path, query); err != nil { oerr = utils.NewError(c.QueryEvents, "unable to create request", c, err) } else if resp, err := c.WebDAV().Do(req); err != nil { oerr = utils.NewError(c.QueryEvents, "unable to execute request", c, err) } else if resp.StatusCode == http.StatusNotFound { return // no events if not found } else if resp.StatusCode != webdav.StatusMulti { err := new(entities.Error) msg := fmt.Sprintf("unexpected server response %s", resp.Status) resp.Decode(err) oerr = utils.NewError(c.QueryEvents, msg, c, err) } else if err := resp.Decode(ms); err != nil { msg := "unable to decode response" oerr = utils.NewError(c.QueryEvents, msg, c, err) } else { for i, r := range ms.Responses { for j, p := range r.PropStats { if p.Prop == nil || p.Prop.CalendarData == nil { continue } else if cal, err := p.Prop.CalendarData.CalendarComponent(); err != nil { msg := fmt.Sprintf("unable to decode property %d of response %d", j, i) oerr = utils.NewError(c.QueryEvents, msg, c, err) return } else { events = append(events, cal.Events...) } } } } return }
// returns an error if the server does not support WebDAV func (c *Client) ValidateServer(path string) error { if features, err := c.Features(path); err != nil { return utils.NewError(c.ValidateServer, "feature detection failed", c, err) } else if len(features) <= 0 { return utils.NewError(c.ValidateServer, "no DAV headers found", c, err) } else { return nil } }
// fetches a list of WebDAV features supported by the server // returns an error if the server does not support DAV func (c *Client) Features(path string) ([]string, error) { if req, err := c.Server().NewRequest("OPTIONS", path); err != nil { return []string{}, utils.NewError(c.Features, "unable to create request", c, err) } else if resp, err := c.Do(req); err != nil { return []string{}, utils.NewError(c.Features, "unable to execute request", c, err) } else { return resp.Features(), nil } }
// checks if a resource exists given a particular path func (c *Client) Exists(path string) (bool, error) { if req, err := c.Server().NewRequest("HEAD", path); err != nil { return false, utils.NewError(c.Exists, "unable to create request", c, err) } else if resp, err := c.Do(req); err != nil { return false, utils.NewError(c.Exists, "unable to execute request", c, err) } else { return resp.StatusCode != nhttp.StatusNotFound, nil } }
// fetches a list of CalDAV features and checks if a certain one is supported by the server // returns an error if the server does not support DAV func (c *Client) ValidateServer(path string) error { if found, err := c.SupportsFeature("access", path); err != nil { return utils.NewError(c.SupportsFeature, "feature detection failed", c, err) } else if !found { return utils.NewError(c.SupportsFeature, "calendar access feature missing", c, nil) } else { return nil } }
// creates or updates one or more events on the remote CalDAV server func (c *Client) PutEvents(path string, events ...*components.Event) error { if len(events) <= 0 { return utils.NewError(c.PutEvents, "no calendar events provided", c, nil) } else if cal := components.NewCalendar(events...); events[0] == nil { return utils.NewError(c.PutEvents, "icalendar event must not be nil", c, nil) } else if err := c.PutCalendars(path, cal); err != nil { return utils.NewError(c.PutEvents, "unable to put calendar", c, err) } return nil }
func (c *CalendarData) CalendarComponent() (*components.Calendar, error) { cal := new(components.Calendar) if content := strings.TrimSpace(c.Content); content == "" { return nil, utils.NewError(c.CalendarComponent, "no calendar data to decode", c, nil) } else if err := icalendar.Unmarshal(content, cal); err != nil { return nil, utils.NewError(c.CalendarComponent, "decoding calendar data failed", c, err) } else { return cal, nil } }
func hydrateComponent(v reflect.Value, component *token) error { if tag, err := extractTagFromValue(v); err != nil { return utils.NewError(hydrateComponent, "error extracting tag from value", component, err) } else if tag != component.name { msg := fmt.Sprintf("expected %s and found %s", tag, component.name) return utils.NewError(hydrateComponent, msg, component, nil) } else if err := hydrateProperties(v, component); err != nil { return utils.NewError(hydrateComponent, "unable to hydrate properties", component, err) } return nil }
// decodes the geo value from the iCalendar specification func (g *Geo) DecodeICalValue(value string) error { if latlng := strings.Split(value, " "); len(latlng) < 2 { return utils.NewError(g.DecodeICalValue, "geo value must have both a latitude and longitude component", g, nil) } else if lat, err := strconv.ParseFloat(latlng[0], 64); err != nil { return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err) } else if lng, err := strconv.ParseFloat(latlng[1], 64); err != nil { return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err) } else { *g = Geo{coords: []float64{lat, lng}} return nil } }
// creates or updates one or more calendars on the remote CalDAV server func (c *Client) PutCalendars(path string, calendars ...*components.Calendar) error { if req, err := c.Server().NewRequest("PUT", path, calendars); err != nil { return utils.NewError(c.PutCalendars, "unable to encode request", c, err) } else if resp, err := c.Do(req); err != nil { return utils.NewError(c.PutCalendars, "unable to execute request", c, err) } else if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent { err := new(entities.Error) resp.WebDAV().Decode(err) msg := fmt.Sprintf("unexpected server response %s", resp.Status) return utils.NewError(c.PutCalendars, msg, c, err) } return nil }
// creates a new CalDAV request object func NewRequest(method string, urlstr string, icaldata ...interface{}) (*Request, error) { if buffer, err := icalToReadCloser(icaldata...); err != nil { return nil, utils.NewError(NewRequest, "unable to encode icalendar data", icaldata, err) } else if r, err := http.NewRequest(method, urlstr, buffer); err != nil { return nil, utils.NewError(NewRequest, "unable to create request", urlstr, err) } else { if buffer != nil { // set the content type to XML if we have a body r.Native().Header.Set("Content-Type", "text/calendar; charset=UTF-8") } return (*Request)(r), nil } }
// deletes a resource if it exists on a particular path func (c *Client) Delete(path string) error { if req, err := c.Server().NewRequest("DELETE", path); err != nil { return utils.NewError(c.Delete, "unable to create request", c, err) } else if resp, err := c.Do(req); err != nil { return utils.NewError(c.Delete, "unable to execute request", c, err) } else if resp.StatusCode != nhttp.StatusNoContent && resp.StatusCode != nhttp.StatusNotFound { err := new(entities.Error) resp.Decode(err) msg := fmt.Sprintf("unexpected server response %s", resp.Status) return utils.NewError(c.Delete, msg, c, err) } else { return nil } }
// decodes the datetime params from the iCalendar specification func (d *DateTime) DecodeICalParams(params properties.Params) error { layout := DateTimeFormatString value := d.t.Format(layout) if name, found := params[properties.TimeZoneIdPropertyName]; !found { return nil } else if loc, err := time.LoadLocation(name); err != nil { return utils.NewError(d.DecodeICalValue, "unable to parse timezone", d, err) } else if t, err := time.ParseInLocation(layout, value, loc); err != nil { return utils.NewError(d.DecodeICalValue, "unable to parse datetime value", d, err) } else { d.t = t return nil } }
// creates a new calendar collection on a given path func (c *Client) MakeCalendar(path string) error { if req, err := c.Server().NewRequest("MKCALENDAR", path); err != nil { return utils.NewError(c.MakeCalendar, "unable to create request", c, err) } else if resp, err := c.Do(req); err != nil { return utils.NewError(c.MakeCalendar, "unable to execute request", c, err) } else if resp.StatusCode != http.StatusCreated { err := new(entities.Error) resp.Decode(err) msg := fmt.Sprintf("unexpected server response %s", resp.Status) return utils.NewError(c.MakeCalendar, msg, c, err) } else { return nil } }
// decodes a CalDAV iCalendar response into the provided interface func (r *Response) Decode(into interface{}) error { if body := r.Body; body == nil { return nil } else if encoded, err := ioutil.ReadAll(body); err != nil { return utils.NewError(r.Decode, "unable to read response body", r, err) } else { // log.Printf("IN: %+v", string(encoded)) if err := icalendar.Unmarshal(string(encoded), into); err != nil { return utils.NewError(r.Decode, "unable to decode response body", r, err) } else { return nil } } }
func hydrateProperties(v reflect.Value, component *token) error { vdref := dereferencePointerValue(v) vtype := vdref.Type() vkind := vdref.Kind() if vkind != reflect.Struct { return utils.NewError(hydrateProperties, "unable to hydrate properties of non-struct", v, nil) } n := vtype.NumField() for i := 0; i < n; i++ { prop := properties.PropertyFromStructField(vtype.Field(i)) if prop == nil { continue // skip if field is ignored } vfield := vdref.Field(i) // first try to hydrate property values if properties, ok := component.properties[prop.Name]; ok { for _, prop := range properties { if err := hydrateProperty(vfield, prop); err != nil { msg := fmt.Sprintf("unable to hydrate property %s", prop.Name) return utils.NewError(hydrateProperties, msg, v, err) } } } // then try to hydrate components vtemp, _ := newValue(vfield) if tag, err := extractTagFromValue(vtemp); err != nil { msg := fmt.Sprintf("unable to extract tag from property %s", prop.Name) return utils.NewError(hydrateProperties, msg, v, err) } else if components, ok := component.components[tag]; ok { for _, comp := range components { if err := hydrateNestedComponent(vfield, comp); err != nil { msg := fmt.Sprintf("unable to hydrate component %s", prop.Name) return utils.NewError(hydrateProperties, msg, v, err) } } } } return nil }
// decodes encoded icalendar data into a native interface func Unmarshal(encoded string, into interface{}) error { if component, err := tokenize(encoded); err != nil { return utils.NewError(Unmarshal, "unable to tokenize encoded data", encoded, err) } else { return hydrateValue(reflect.ValueOf(into), component) } }
// encodes a list of datetime values for the iCalendar specification func (ds *DateTimes) DecodeICalValue(value string) error { csv := new(CSV) if err := csv.DecodeICalValue(value); err != nil { return utils.NewError(ds.DecodeICalValue, "unable to decode datetime list as CSV", ds, err) } for i, value := range *csv { d := new(DateTime) if err := d.DecodeICalValue(value); err != nil { msg := fmt.Sprintf("unable to decode datetime at index %d", i) return utils.NewError(ds.DecodeICalValue, msg, ds, err) } else { *ds = append(*ds, d) } } return nil }
// executes an HTTP request func (c *Client) Do(req *Request) (*Response, error) { if resp, err := c.Native().Do((*http.Request)(req)); err != nil { return nil, utils.NewError(c.Do, "unable to execute HTTP request", c, err) } else { return NewResponse(resp), nil } }
// validates the URL for iCalendar format func (u *Url) ValidateICalValue() error { if _, err := url.Parse(u.u.String()); err != nil { return utils.NewError(u.ValidateICalValue, "invalid URL object", u, err) } else { return nil } }
func csvToInts(value string) (ints []int, err error) { csv := new(CSV) if ierr := csv.DecodeICalValue(value); err != nil { err = utils.NewError(csvToInts, "unable to decode CSV value", value, ierr) return } for _, v := range *csv { if i, ierr := strconv.ParseInt(v, 10, 64); err != nil { err = utils.NewError(csvToInts, "unable to parse int value "+v, value, ierr) return } else { ints = append(ints, int(i)) } } return }
// creates a reference to a CalDAV server func NewServer(baseUrlStr string) (*Server, error) { if s, err := webdav.NewServer(baseUrlStr); err != nil { return nil, utils.NewError(NewServer, "unable to create WebDAV server", baseUrlStr, err) } else { return (*Server)(s), nil } }
// executes a CalDAV request func (c *Client) Do(req *Request) (*Response, error) { if resp, err := c.WebDAV().Do((*webdav.Request)(req)); err != nil { return nil, utils.NewError(c.Do, "unable to execute CalDAV request", c, err) } else { return NewResponse(resp), nil } }
// validates the datetime value against the iCalendar specification func (d *DateTime) ValidateICalValue() error { loc := d.t.Location() if loc == time.Local { msg := "DateTime location may not Local, please use UTC or explicit Location" return utils.NewError(d.ValidateICalValue, msg, d, nil) } if loc.String() == "" { msg := "DateTime location must have a valid name" return utils.NewError(d.ValidateICalValue, msg, d, nil) } return nil }
// attempts to fetch an event on the remote CalDAV server func (c *Client) GetEvents(path string) ([]*components.Event, error) { cal := new(components.Calendar) if req, err := c.Server().NewRequest("GET", path); err != nil { return nil, utils.NewError(c.GetEvents, "unable to create request", c, err) } else if resp, err := c.Do(req); err != nil { return nil, utils.NewError(c.GetEvents, "unable to execute request", c, err) } else if resp.StatusCode != http.StatusOK { err := new(entities.Error) resp.WebDAV().Decode(err) msg := fmt.Sprintf("unexpected server response %s", resp.Status) return nil, utils.NewError(c.GetEvents, msg, c, err) } else if err := resp.Decode(cal); err != nil { return nil, utils.NewError(c.GetEvents, "unable to decode response", c, err) } else { return cal.Events, nil } }
// decodes the recurrence rule value from the iCalendar specification func (r *RecurrenceRule) DecodeICalValue(value string) error { matches := rruleParamRegExp.FindAllStringSubmatch(value, -1) if len(matches) <= 0 { return utils.NewError(r.DecodeICalValue, "no recurrence rules found", r, nil) } for _, match := range matches { if err := r.decodeICalValue(match[1], match[2]); err != nil { msg := fmt.Sprintf("unable to decode %s value", match[1]) return utils.NewError(r.DecodeICalValue, msg, r, err) } } return nil }
// decodes the URL from iCalendar format func (u *Url) DecodeICalValue(value string) error { if parsed, err := url.Parse(value); err != nil { return utils.NewError(u.ValidateICalValue, "unable to parse url", u, err) } else { u.u = *parsed return nil } }