package handlers import ( "fmt" "net/http" "encoding/xml" "github.com/samedi/caldav-go/lib" "github.com/samedi/caldav-go/global" "github.com/samedi/caldav-go/data" "github.com/samedi/caldav-go/ixml" ) // Wraps a multistatus response. It contains the set of `Responses` // that will serve to build the final XML. Multistatus responses are // used by the REPORT and PROPFIND methods. type multistatusResp struct { // The set of multistatus responses used to build each of the nodes. Responses []msResponse // Flag that XML should be minimal or not // [defined in the draft https://tools.ietf.org/html/draft-murchison-webdav-prefer-05] Minimal bool } type msResponse struct { Href string Found bool Propstats msPropstats } type msPropstats map[int]msProps // Adds a msProp to the map with the key being the prop status. func (stats msPropstats) Add(prop msProp) { stats[prop.Status] = append(stats[prop.Status], prop) } func (stats msPropstats) Clone() msPropstats { clone := make(msPropstats) for k, v := range stats { clone[k] = v } return clone } type msProps []msProp type msProp struct { Tag xml.Name Content string Contents []string Status int } // Function that processes all the required props for a given resource. // ## Params // resource: the target calendar resource. // reqprops: set of required props that must be processed for the resource. // ## Returns // The set of props (msProp) processed. Each prop is mapped to a HTTP status code. // So if a prop is found and processed ok, it'll be mapped to 200. If it's not found, // it'll be mapped to 404, and so on. func (ms *multistatusResp) Propstats(resource *data.Resource, reqprops []xml.Name) msPropstats { if resource == nil { return nil } result := make(msPropstats) for _, ptag := range reqprops { pvalue := msProp{ Tag: ptag, Status: http.StatusOK, } pfound := false switch ptag { case ixml.CALENDAR_DATA_TG: pvalue.Content, pfound = resource.GetContentData() if pfound { pvalue.Content = ixml.EscapeText(pvalue.Content) } case ixml.GET_ETAG_TG: pvalue.Content, pfound = resource.GetEtag() case ixml.GET_CONTENT_TYPE_TG: pvalue.Content, pfound = resource.GetContentType() case ixml.GET_CONTENT_LENGTH_TG: pvalue.Content, pfound = resource.GetContentLength() case ixml.DISPLAY_NAME_TG: pvalue.Content, pfound = resource.GetDisplayName() if pfound { pvalue.Content = ixml.EscapeText(pvalue.Content) } case ixml.GET_LAST_MODIFIED_TG: pvalue.Content, pfound = resource.GetLastModified(http.TimeFormat) case ixml.OWNER_TG: pvalue.Content, pfound = resource.GetOwnerPath() case ixml.GET_CTAG_TG: pvalue.Content, pfound = resource.GetEtag() case ixml.PRINCIPAL_URL_TG, ixml.PRINCIPAL_COLLECTION_SET_TG, ixml.CALENDAR_USER_ADDRESS_SET_TG, ixml.CALENDAR_HOME_SET_TG: pvalue.Content, pfound = ixml.HrefTag(resource.Path), true case ixml.RESOURCE_TYPE_TG: if resource.IsCollection() { pvalue.Content, pfound = ixml.Tag(ixml.COLLECTION_TG, "") + ixml.Tag(ixml.CALENDAR_TG, ""), true if resource.IsPrincipal() { pvalue.Content += ixml.Tag(ixml.PRINCIPAL_TG, "") } } else { // resourcetype must be returned empty for non-collection elements pvalue.Content, pfound = "", true } case ixml.CURRENT_USER_PRINCIPAL_TG: if global.User != nil { path := fmt.Sprintf("/%s/", global.User.Name) pvalue.Content, pfound = ixml.HrefTag(path), true } case ixml.SUPPORTED_CALENDAR_COMPONENT_SET_TG: if resource.IsCollection() { for _, component := range supportedComponents { // TODO: use ixml somehow to build the below tag compTag := fmt.Sprintf(``, component) pvalue.Contents = append(pvalue.Contents, compTag) } pfound = true } } if !pfound { pvalue.Status = http.StatusNotFound } result.Add(pvalue) } return result } // Adds a new `msResponse` to the `Responses` array. func (ms *multistatusResp) AddResponse(href string, found bool, propstats msPropstats) { ms.Responses = append(ms.Responses, msResponse{ Href: href, Found: found, Propstats: propstats, }) } func (ms *multistatusResp) ToXML() string { // init multistatus var bf lib.StringBuffer bf.Write(``) bf.Write(``, ixml.Namespaces()) // iterate over event hrefs and build multistatus XML on the fly for _, response := range ms.Responses { bf.Write("") bf.Write(ixml.HrefTag(response.Href)) if response.Found { propstats := response.Propstats.Clone() if ms.Minimal { delete(propstats, http.StatusNotFound) if len(propstats) == 0 { bf.Write("") bf.Write("") bf.Write(ixml.StatusTag(http.StatusOK)) bf.Write("") bf.Write("") continue } } for status, props := range propstats { bf.Write("") bf.Write("") for _, prop := range props { bf.Write(ms.propToXML(prop)) } bf.Write("") bf.Write(ixml.StatusTag(status)) bf.Write("") } } else { // if does not find the resource set 404 bf.Write(ixml.StatusTag(http.StatusNotFound)) } bf.Write("") } bf.Write("") return bf.String() } func (ms *multistatusResp) propToXML(prop msProp) string { for _, content := range prop.Contents { prop.Content += content } xmlString := ixml.Tag(prop.Tag, prop.Content) return xmlString }