Example #1
0
        /// <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
        }
Example #2
0
        /// <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));
        }
Example #4
0
        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));
        }