/// <summary> /// Returns a Response XML tree for a prop request with all the property names /// and property values specified in the request. /// </summary> /// <param name="url"></param> /// <param name="calendarResourceId">Name of the resource</param> /// <param name="propertiesNameNamespace">List of requested properties (key=name; value=namespace)</param> /// <param name="principal"></param> /// <returns></returns> private async Task <XmlTreeStructure> PropFillTree(string url, string calendarResourceId, List <KeyValuePair <string, string> > propertiesNameNamespace, Principal principal) { //a "response xml element is added for each collection or resource" #region Adding the response of the collection or resource. var treeChild = new XmlTreeStructure("response", "DAV:"); #region Adding the <D:href>/api/v1/caldav/{userEmail}/calendars/{collectionName}/{calendarResourceId}?</D:href> //an href with the corresponding url is added to the response var href = new XmlTreeStructure("href", "DAV:"); href.AddValue(SystemProperties._baseUrl + url); treeChild.AddChild(href); #endregion #region Adding the propstats #region Selecting properties CalendarCollection collection; CalendarResource resource; var propertiesCol = new List <XmlTreeStructure>(); var propertiesOk = new List <XmlTreeStructure>(); var propertiesWrong = new List <XmlTreeStructure>(); var errorStack = new Stack <string>(); //the current-user-privilege-set is generated per request //it needs the DAV:acl property and the principalID Property aclProperty = null; //It take the list of requested properties and tries to get the corresponding property from db. //The methods are called for a resource or a collection accordingly its circumstances. //The properties are stored inside the propertiesCol. Where if the value is null it means that the collection could not be //retrieve. if (calendarResourceId == null) { collection = _collectionRepository.Get(url); if (propertiesNameNamespace != null) { foreach (var addProperty in propertiesNameNamespace) { //gets the property from database var property = await _collectionRepository.GetProperty(url, addProperty); //Builds the xmlTreeExtructure checking that if the value is null thats because //the property was not found. IXMLTreeStructure prop; if (property != null) { prop = property.Value == null ? new XmlTreeStructure(property.Name, property.Namespace) { Value = "" } } : XmlTreeStructure.Parse(property.Value); else { prop = new XmlTreeStructure(addProperty.Key, addProperty.Value); } propertiesCol.Add((XmlTreeStructure)prop); } //take the acl property aclProperty = collection.Properties.FirstOrDefault(x => x.Name == "acl"); } } else { resource = _resourceRespository.Get(url); if (propertiesNameNamespace != null) { foreach (var addProperty in propertiesNameNamespace) { //gets the property from database var property = await _resourceRespository.GetProperty(url, addProperty); //Builds the xmlTreeExtructure checking that if the value is null thats because //the property was not found. IXMLTreeStructure prop; if (property != null) { prop = property.Value == null ? new XmlTreeStructure(property.Name, property.Namespace) { Value = "" } } : XmlTreeStructure.Parse(property.Value); else { prop = new XmlTreeStructure(addProperty.Key, addProperty.Value); } propertiesCol.Add((XmlTreeStructure)prop); } //take the acl property aclProperty = resource.Properties.FirstOrDefault(x => x.Name == "acl"); } } //add the additional properties that are generated per request if (propertiesNameNamespace != null) { foreach (var pair in propertiesNameNamespace) { switch (pair.Key) { case "current-user-privilege-set": propertiesCol.RemoveAll(x => x.NodeName == pair.Key); propertiesCol.Add(principal.GetCurrentUserPermissions(aclProperty)); break; } } } //Here, properties are divided between recovered and error recovering foreach (var propTree in propertiesCol) { if (propTree.Value != null) { propertiesOk.Add(propTree); } else { propertiesWrong.Add(propTree); } } #endregion //For each returned status a "propstat" is created, containing a "prop" with all properties that belong to that current status. // And a "status" containing the message of the corresponding status. //Right Now there are only two "propstat" taking place OK and Wrong an therefore only two "status" //200 OK and 400 Not Found. //More should be added when ACL is working entairly. //TODO: Add the status forbidden for authentication permissions problems. #region Adding nested propOK var propstatOk = new XmlTreeStructure("propstat", "DAV:"); var propOk = new XmlTreeStructure("prop", "DAV:"); //Here i add all properties to the prop. foreach (var property in propertiesOk) { propOk.AddChild(property); } propstatOk.AddChild(propOk); //This when i group the OK properties #region Adding nested status OK var statusOk = new XmlTreeStructure("status", "DAV:"); statusOk.AddValue("HTTP/1.1 200 OK"); propstatOk.AddChild(statusOk); #endregion #endregion //Here the same is made. The Wrong properties are grouped. #region Adding nested propWrong var propstatWrong = new XmlTreeStructure("propstat", "DAV:"); var propWrong = new XmlTreeStructure("prop", "DAV:"); //Here i add all properties to the prop. foreach (var property in propertiesWrong) { propWrong.AddChild(property); } propstatWrong.AddChild(propWrong); #region Adding nested status Not Found var statusWrong = new XmlTreeStructure("status", "DAV:"); statusWrong.AddValue("HTTP/1.1 400 Not Found"); propstatWrong.AddChild(statusWrong); #endregion #region Adding responseDescription when wrong var responseDescrpWrong = new XmlTreeStructure("responsedescription", "DAV:"); responseDescrpWrong.AddValue("The properties doesn't exist"); propstatWrong.AddChild(responseDescrpWrong); #endregion #endregion //If anyone of the property groups is empty it is not included in the response. if (propertiesOk.Count > 0) { treeChild.AddChild(propstatOk); } if (propertiesWrong.Count > 0) { treeChild.AddChild(propstatWrong); } #endregion return(treeChild); #endregion }
/// <summary> /// Take the prop node that specified the properties and /// component that are requested, extract this data from /// the system and the VCalendar and return the container /// node with this data. /// </summary> /// <param name="incomPropNode"> /// This node contains the requested data. Is the first prop node /// of the calendar-query. /// </param> /// <param name="resource">The calendar where to extract the data.</param> /// <returns>Return the prop node that contains the requested data</returns> private async Task <List <IXMLTreeStructure> > ProccessPropNode(IXMLTreeStructure incomPropNode, KeyValuePair <string, VCalendar> resource) { var output = new List <IXMLTreeStructure>(); var resPropertiesOk = new List <XmlTreeStructure>(); var resPropertiesNotExist = new List <XmlTreeStructure>(); var href = resource.Key[0] != '/' ? "/" + resource.Key : resource.Key; var calResource = _resourceRepository.Get(href); foreach (var prop in incomPropNode.Children) { //create an instance of a XMlTreeStrucure with the same name and //ns that the requested var currentPropNode = new XmlTreeStructure(prop.NodeName, prop.MainNamespace); switch (prop.NodeName) { //if the requested prop is calendar data then take the content of the //resource case "calendar-data": //see if the calendar-data describes pros to take // if does then take them if not take it all currentPropNode.AddValue(prop.Children.Any() ? resource.Value.ToString(prop) : resource.Value.ToString()); resPropertiesOk.Add(currentPropNode); break; //if not try to take the property from the resource's properties default: var currentProperty = calResource.Properties.FirstOrDefault(p => p.Name == prop.NodeName); currentPropNode.AddValue(currentProperty != null ? currentProperty.PropertyRealValue() : ""); if (currentProperty != null) { resPropertiesOk.Add(currentPropNode); } else { resPropertiesNotExist.Add(currentPropNode); } break; } } #region Adding nested propOK //This procedure has been explained in another method. //Here the retrieve properties are grouped. var propstatOK = new XmlTreeStructure("propstat", "DAV:"); var propOk = new XmlTreeStructure("prop", "DAV:"); //Here i add all properties to the prop. foreach (var property in resPropertiesOk) { propOk.AddChild(property); } propstatOK.AddChild(propOk); #endregion #region Adding nested status OK var statusOK = new XmlTreeStructure("status", "DAV:"); statusOK.AddValue("HTTP/1.1 200 OK"); propstatOK.AddChild(statusOK); #endregion #region Adding nested propWrong //Here the properties that could not be retrieved are grouped. var propstatWrong = new XmlTreeStructure("propstat", "DAV:"); var propWrong = new XmlTreeStructure("prop", "DAV:"); //Here i add all properties to the prop. foreach (var property in resPropertiesNotExist) { propWrong.AddChild(property); } propstatWrong.AddChild(propWrong); #endregion #region Adding nested status Not Found var statusWrong = new XmlTreeStructure("status", "DAV:"); statusWrong.AddValue("HTTP/1.1 400 Not Found"); propstatWrong.AddChild(statusWrong); #endregion #region Adding responseDescription when wrong //Here i add an description for explain the errors. //This should be aplied in all method with an similar structure but for the moment is only used here. //However this is not required. var responseDescrpWrong = new XmlTreeStructure("responsedescription", "DAV:"); responseDescrpWrong.AddValue("The properties doesn't exist"); propstatWrong.AddChild(responseDescrpWrong); #endregion if (resPropertiesOk.Any()) { output.Add(propstatOK); } if (resPropertiesNotExist.Any()) { output.Add(propstatWrong); } return(output); }
public async Task <bool> PreconditionsOK(Dictionary <string, string> propertiesAndHeaders, HttpResponse response) { #region Extracting Properties var url = propertiesAndHeaders["url"]; var contentSize = propertiesAndHeaders["content-length"]; var body = propertiesAndHeaders["body"]; VCalendar iCalendar; try { iCalendar = new VCalendar(body); //lo que no estoy seguro que en el body solo haya el iCal string } catch (Exception) { response.StatusCode = (int)HttpStatusCode.BadRequest; return(false); } #endregion //check that resourceId don't exist but the collection does. if ( !StorageManagement.ExistCalendarCollection(url.Remove(url.LastIndexOf("/", StringComparison.Ordinal) + 1))) { response.StatusCode = (int)HttpStatusCode.NotFound; return(false); } //check that if the resource exist then all its components different of VTIMEZONE has to have the same UID //if the resource not exist can not be another resource with the same uid. if (!StorageManagement.ExistCalendarObjectResource(url)) { var component = iCalendar.CalendarComponents.FirstOrDefault(comp => comp.Key != "VTIMEZONE").Value; var uid = component.FirstOrDefault()?.Properties["UID"].StringValue; // var resource = db.GetCalendarResource(userEmail, collectionName, calendarResourceId); var collection = _collectionRepository.Get(url.Remove(url.LastIndexOf("/", StringComparison.Ordinal) + 1)); foreach (var calendarresource in collection.CalendarResources) { if (uid == calendarresource.Uid) { response.StatusCode = (int)HttpStatusCode.Conflict; response.Body.Write( $@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <no-uid-conflict xmlns='urn:ietf:params:xml:ns:caldav'> <href xmlns='DAV:'>{ SystemProperties._baseUrl + calendarresource .Href}</href> </no-uid-conflict> </error>"); return(false); } } } else { //If the resource exist the procedure is update and for that the uid has to be the same. var components = iCalendar.CalendarComponents.FirstOrDefault(comp => comp.Key != "VTIMEZONE").Value; var calendarComponent = components.FirstOrDefault(); if (calendarComponent != null) { var uid = calendarComponent.Properties["UID"].StringValue; var resource = _resourceRespository.Get(url); if (resource.Uid != null && resource.Uid != uid) { response.StatusCode = (int)HttpStatusCode.Conflict; response.Body.Write( $@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <no-uid-conflict xmlns='urn:ietf:params:xml:ns:caldav'> <href xmlns='DAV:'>{ SystemProperties._baseUrl + resource .Href}</href> </no-uid-conflict> </error>"); return(false); } } } if (propertiesAndHeaders.ContainsKey("If-Match")) { //check that the value do exist if (!StorageManagement.ExistCalendarObjectResource(url)) { response.StatusCode = (int)HttpStatusCode.PreconditionFailed; return(false); } } if (propertiesAndHeaders.ContainsKey("If-None-Match")) { //check that the value do not exist if (StorageManagement.ExistCalendarObjectResource(url)) { response.StatusCode = (int)HttpStatusCode.PreconditionFailed; return(false); } } //it does not contain more than two calendar components //and if it has 2, one must be VTIMEZONE if (iCalendar.CalendarComponents.Count > 2) { if (!iCalendar.CalendarComponents.ContainsKey("VTIMEZONE")) { response.StatusCode = (int)HttpStatusCode.Conflict; response.Body.Write(@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <valid-calendar-object-resource xmlns='urn:ietf:params:xml:ns:caldav'></valid-calendar-object-resource> <error-description xmlns='http://twistedmatrix.com/xml_namespace/dav/'> VTimezone Calendar Component Must be present. </error-description> </error>"); return(false); } var calendarComponents = iCalendar.CalendarComponents.FirstOrDefault(comp => comp.Key != "VTIMEZONE").Value; //A Calendar Component can be separated in multiples calendar components but all MUST //have the same UID. var calendarComponent = calendarComponents.FirstOrDefault(); if (calendarComponent != null) { var uid = calendarComponent.Properties["UID"].StringValue; foreach (var component in calendarComponents) { var uidComp = component.Properties["UID"].StringValue; if (uid != uidComp) { response.StatusCode = (int)HttpStatusCode.Conflict; response.Body.Write(@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <valid-calendar-object-resource xmlns='urn:ietf:params:xml:ns:caldav'></valid-calendar-object-resource> <error-description xmlns='http://twistedmatrix.com/xml_namespace/dav/'> If the count of calendar components execeds 2 including VTimezone the rest must have the same Uid and the same type. </error-description> </error>"); return(false); } } } // response.StatusCode = (int)HttpStatusCode.Conflict; // response.Body.Write(@"<?xml version='1.0' encoding='UTF-8'?> //<error xmlns='DAV:'> //<valid-calendar-object-resource xmlns='urn:ietf:params:xml:ns:caldav'></valid-calendar-object-resource> //<error-description xmlns='http://twistedmatrix.com/xml_namespace/dav/'> //Wrong amount of calendar components //</error-description> //</error>"); // return false; } //precondition responsible of check that an VTIMEZONE is obligatory if (iCalendar.CalendarComponents.Count == 2) { if (!iCalendar.CalendarComponents.ContainsKey("VTIMEZONE")) { response.StatusCode = (int)HttpStatusCode.Conflict; response.Body.Write(@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <valid-calendar-object-resource xmlns='urn:ietf:params:xml:ns:caldav'></valid-calendar-object-resource> <error-description xmlns='http://twistedmatrix.com/xml_namespace/dav/'> VTimezone Calendar Component Must be present. </error-description> </error>"); return(false); } } //var uidCalendar = ((ComponentProperty<string>)iCalendar.Properties["UID"]).Value; ////Check that if the operation is create there is not another element in the collection with the same UID //if (!StorageManagement.ExistCalendarObjectResource(calendarResourceId)) //{ // using (db) // { // if ((from calendarResource in db.CalendarResources // where calendarResource.Uid == uidCalendar // select calendarResource).Count() > 0) // return false; // } //} ////Check if the operation is update the element to be updated must have the same UID. //else //{ // using (db) // { // if ((from calendarResource in db.CalendarResources // where calendarResource.Uid == uidCalendar // select calendarResource).Count() == 0) // return false; // } //} var methodProp = iCalendar.GetComponentProperties("METHOD"); //iCalendar object MUST NOT implement METHOD property if (!string.IsNullOrEmpty(methodProp?.StringValue)) { response.StatusCode = (int)HttpStatusCode.Conflict; response.Body.Write(@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <valid-calendar-object-resource xmlns='urn:ietf:params:xml:ns:caldav'></valid-calendar-object-resource> <error-description xmlns='http://twistedmatrix.com/xml_namespace/dav/'> Method prop must not be present </error-description> </error>"); return(false); } //This precondition is the one in charge of check that the size of the body of the resource //included in the request dont exceeds the max-resource-size property of the colletion. int contentSizeInt; //for that i need that the controller has as request header content-size available if (!string.IsNullOrEmpty(contentSize) && int.TryParse(contentSize, out contentSizeInt)) { var collection = _collectionRepository.Get(url.Remove(url.LastIndexOf("/", StringComparison.Ordinal) + 1)); //here the max-resource-property of the collection is called. var maxSize = collection.Properties.FirstOrDefault( p => p.Name == "max-resource-size" && p.Namespace == "urn:ietf:params:xml:ns:caldav"); int maxSizeInt; if (int.TryParse(XmlTreeStructure.Parse(maxSize?.Value).Value, out maxSizeInt) && contentSizeInt > maxSizeInt) { response.StatusCode = (int)HttpStatusCode.Conflict; response.Body.Write(@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <max-resource-size xmlns='urn:ietf:params:xml:ns:caldav'></max-resource-size> <error-description xmlns='http://twistedmatrix.com/xml_namespace/dav/'> Content size exceeds max size allowed. </error-description> </error>"); return(false); } } //TODO: Checking that all DateTime values are less-equal than //the max-date-time //TODO: Checking that all DateTime values are grater-equal than //the min-date-time //TODO: Checking that the number of recurring instances is less-equal //than the max-instances property value. return(await Task.FromResult(true)); }
public async Task <bool> DeleteCalendarObjectResource(Dictionary <string, string> propertiesAndHeaders, HttpResponse response) { #region Extracting Properties string url; propertiesAndHeaders.TryGetValue("url", out url); string ifmatch; var ifMatchEtags = new List <string>(); propertiesAndHeaders.TryGetValue("If-Match", out ifmatch); if (ifmatch != null) { ifMatchEtags = ifmatch.Split(',').ToList(); } #endregion //if the collection doesnt exist in the user folder // the can't do anything var collectionUrl = url?.Remove(url.LastIndexOf("/", StringComparison.Ordinal) + 1); if (!StorageManagement.ExistCalendarCollection(collectionUrl) && !await _collectionRespository.Exist(collectionUrl)) { return(true); } var resource = _resourceRespository.Get(url); if (ifMatchEtags.Count > 0) { if (resource != null) { var resourceEtag = XmlTreeStructure.Parse(resource.Properties.FirstOrDefault(x => x.Name == "getetag")?.Value) .Value; if (resourceEtag != null && ifMatchEtags.Contains(resourceEtag)) { response.StatusCode = (int)HttpStatusCode.NoContent; await _resourceRespository.Remove(resource); //updating the ctag var stack = new Stack <string>(); await _collectionRespository.CreateOrModifyProperty(collectionUrl, "getctag", _namespacesSimple["S"], $@"<S:getctag {_namespaces["S"]} >{Guid.NewGuid()}</S:getctag>", stack, true); return(StorageManagement.DeleteCalendarObjectResource(url)); } } } if (resource != null) { response.StatusCode = (int)HttpStatusCode.NoContent; await _resourceRespository.Remove(resource); //updating the ctag var stack = new Stack <string>(); await _collectionRespository.CreateOrModifyProperty(collectionUrl, "getctag", _namespacesSimple["S"], $@"<S:getctag {_namespaces["S"]} >{Guid.NewGuid()}</S:getctag>", stack, true); return(StorageManagement.DeleteCalendarObjectResource(url)); } return(StorageManagement.DeleteCalendarObjectResource(url)); }