package icalendar import ( "fmt" "github.com/dolanor/caldav-go/icalendar/properties" "github.com/dolanor/caldav-go/utils" "log" "reflect" "regexp" "strconv" "strings" ) var _ = log.Print var splitter = regexp.MustCompile("\r?\n") type token struct { name string components map[string][]*token properties map[properties.PropertyName][]*properties.Property } func tokenize(encoded string) (*token, error) { if encoded = strings.TrimSpace(encoded); encoded == "" { return nil, utils.NewError(tokenize, "no content to tokenize", encoded, nil) } return tokenizeSlice(splitter.Split(encoded, -1)) } func tokenizeSlice(slice []string, name ...string) (*token, error) { tok := new(token) size := len(slice) if len(name) > 0 { tok.name = name[0] } else if size <= 0 { return nil, utils.NewError(tokenizeSlice, "token has no content", slice, nil) } tok.properties = make(map[properties.PropertyName][]*properties.Property, 0) tok.components = make(map[string][]*token, 0) for i := 0; i < size; i++ { // Handle iCalendar's space-indented line break format // See: https://www.ietf.org/rfc/rfc2445.txt section 4.1 // "a long line can be split between any two characters by inserting a CRLF immediately followed by a single // linear white space character" line := slice[i] for ; i < size-1 && strings.HasPrefix(slice[i+1], " "); i++ { next := slice[i+1] line += next[1:len(next)] } prop := properties.UnmarshalProperty(line) if prop.Name.Equals("begin") { for j := i; j < size; j++ { end := strings.Replace(line, "BEGIN", "END", 1) if slice[j] == end { if component, err := tokenizeSlice(slice[i+1:j], prop.Value); err != nil { msg := fmt.Sprintf("unable to tokenize %s component", prop.Value) return nil, utils.NewError(tokenizeSlice, msg, slice, err) } else { existing, _ := tok.components[prop.Value] tok.components[prop.Value] = append(existing, component) i = j break } } } } else if existing, ok := tok.properties[prop.Name]; ok { tok.properties[prop.Name] = []*properties.Property{prop} } else { tok.properties[prop.Name] = append(existing, prop) } } return tok, nil } func hydrateInterface(v reflect.Value, prop *properties.Property) (bool, error) { // unable to decode into empty values if isInvalidOrEmptyValue(v) { return false, nil } var i = v.Interface() var hasValue = false // decode a value if possible if decoder, ok := i.(properties.CanDecodeValue); ok { if err := decoder.DecodeICalValue(prop.Value); err != nil { return false, utils.NewError(hydrateInterface, "error decoding property value", v, err) } else { hasValue = true } } // decode any params, if supported if len(prop.Params) > 0 { if decoder, ok := i.(properties.CanDecodeParams); ok { if err := decoder.DecodeICalParams(prop.Params); err != nil { return false, utils.NewError(hydrateInterface, "error decoding property parameters", v, err) } } } // finish with any validation if validator, ok := i.(properties.CanValidateValue); ok { if err := validator.ValidateICalValue(); err != nil { return false, utils.NewError(hydrateInterface, "error validating property value", v, err) } } return hasValue, nil } func hydrateLiteral(v reflect.Value, prop *properties.Property) (reflect.Value, error) { literal := dereferencePointerValue(v) switch literal.Kind() { case reflect.Bool: if i, err := strconv.ParseBool(prop.Value); err != nil { return literal, utils.NewError(hydrateLiteral, "unable to decode bool "+prop.Value, literal.Interface(), err) } else { literal.SetBool(i) } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if i, err := strconv.ParseInt(prop.Value, 10, 64); err != nil { return literal, utils.NewError(hydrateLiteral, "unable to decode int "+prop.Value, literal.Interface(), err) } else { literal.SetInt(i) } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: if i, err := strconv.ParseUint(prop.Value, 10, 64); err != nil { return literal, utils.NewError(hydrateLiteral, "unable to decode uint "+prop.Value, literal.Interface(), err) } else { literal.SetUint(i) } case reflect.Float32, reflect.Float64: if i, err := strconv.ParseFloat(prop.Value, 64); err != nil { return literal, utils.NewError(hydrateLiteral, "unable to decode float "+prop.Value, literal.Interface(), err) } else { literal.SetFloat(i) } case reflect.String: literal.SetString(prop.Value) default: return literal, utils.NewError(hydrateLiteral, "unable to decode value as literal "+prop.Value, literal.Interface(), nil) } return literal, nil } func hydrateProperty(v reflect.Value, prop *properties.Property) error { // check to see if the interface handles it's own hydration if handled, err := hydrateInterface(v, prop); err != nil { return utils.NewError(hydrateProperty, "unable to hydrate interface", v, err) } else if handled { return nil // exit early if handled by the interface } // if we got here, we need to create a new instance to // set into the property. var vnew, varr = newValue(v) var vlit bool // check to see if the new value handles it's own hydration if handled, err := hydrateInterface(vnew, prop); err != nil { return utils.NewError(hydrateProperty, "unable to hydrate new interface value", vnew, err) } else if vlit = !handled; vlit { // if not, treat it as a literal if vnewlit, err := hydrateLiteral(vnew, prop); err != nil { return utils.NewError(hydrateProperty, "unable to hydrate new literal value", vnew, err) } else if _, err := hydrateInterface(vnewlit, prop); err != nil { return utils.NewError(hydrateProperty, "unable to hydrate new literal interface value", vnewlit, err) } } // now we can set the value vnewval := dereferencePointerValue(vnew) voldval := dereferencePointerValue(v) // make sure we can set the new value into the provided pointer if varr { // for arrays, append the new value into the array structure if !voldval.CanSet() { return utils.NewError(hydrateProperty, "unable to set array value", v, nil) } else { voldval.Set(reflect.Append(voldval, vnewval)) } } else if vlit { // for literals, set the dereferenced value if !voldval.CanSet() { return utils.NewError(hydrateProperty, "unable to set literal value", v, nil) } else { voldval.Set(vnewval) } } else if !v.CanSet() { return utils.NewError(hydrateProperty, "unable to set pointer value", v, nil) } else { // everything else should be a pointer, set it directly v.Set(vnew) } return nil } 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 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 } 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 } func hydrateComponents(v reflect.Value, components []*token) error { vdref := dereferencePointerValue(v) for i, component := range components { velem := reflect.New(vdref.Type().Elem()) if err := hydrateComponent(velem, component); err != nil { msg := fmt.Sprintf("unable to hydrate component %d", i) return utils.NewError(hydrateComponent, msg, component, err) } else { v.Set(reflect.Append(vdref, velem)) } } return nil } func hydrateValue(v reflect.Value, component *token) error { if !v.IsValid() || v.Kind() != reflect.Ptr { return utils.NewError(hydrateValue, "unmarshal target must be a valid pointer", v, nil) } // handle any encodable properties if encoder, isprop := v.Interface().(properties.CanEncodeName); isprop { if name, err := encoder.EncodeICalName(); err != nil { return utils.NewError(hydrateValue, "unable to lookup property name", v, err) } else if properties, found := component.properties[name]; !found || len(properties) == 0 { return utils.NewError(hydrateValue, "no matching propery values found for "+string(name), v, nil) } else if len(properties) > 1 { return utils.NewError(hydrateValue, "more than one property value matches single property interface", v, nil) } else { return hydrateProperty(v, properties[0]) } } // handle components vkind := dereferencePointerValue(v).Kind() if tag, err := extractTagFromValue(v); err != nil { return utils.NewError(hydrateValue, "unable to extract component tag", v, err) } else if components, found := component.components[tag]; !found || len(components) == 0 { msg := fmt.Sprintf("unable to find matching component for %s", tag) return utils.NewError(hydrateValue, msg, v, nil) } else if vkind == reflect.Array || vkind == reflect.Slice { return hydrateComponents(v, components) } else if len(components) > 1 { return utils.NewError(hydrateValue, "non-array interface provided but more than one component found!", v, nil) } else { return hydrateComponent(v, components[0]) } } // 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) } }