public async Task ReadCalendarObjectResource(Dictionary <string, string> propertiesAndHeaders, HttpResponse response) { #region Extracting Properties string url; propertiesAndHeaders.TryGetValue("url", out url); #endregion //An easy way of accessing the headers of the http response response.GetTypedHeaders(); //StorageManagement.SetUserAndCollection(principalUrl, collectionName); //Must return the Etag header of the COR var calendarRes = _resourceRespository.Get(url); if (calendarRes == null || !StorageManagement.ExistCalendarObjectResource(url)) { response.StatusCode = (int)HttpStatusCode.NotFound; return; } var resourceBody = await StorageManagement.GetCalendarObjectResource(url); var etagProperty = calendarRes.Properties.FirstOrDefault(x => x.Name == "getetag"); if (etagProperty != null) { var etag = XmlTreeStructure.Parse(etagProperty.Value).Value; response.Headers["etag"] = etag; } await response.WriteAsync(resourceBody); }
public void UnitTest3() { var calStr = @"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VTODO DTSTAMP:20060205T235300Z DUE;TZID=US/Eastern:20060106T120000 LAST-MODIFIED:20060205T235308Z SEQUENCE:1 STATUS:NEEDS-ACTION SUMMARY:Task #2 UID:[email protected] BEGIN:VALARM ACTION:AUDIO TRIGGER;RELATED=START:-PT10M END:VALARM END:VTODO END:VCALENDAR"; var xmlStr = @"<C:filter xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VCALENDAR"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VTODO""> <C:comp-filter name=""VALARM""> <C:time-range start=""20060106T100000Z"" end=""20060107T100000Z""/> </C:comp-filter> </C:comp-filter> </C:comp-filter></C:filter>"; var calendar = new VCalendar(calStr); var xmlTree = XmlTreeStructure.Parse(xmlStr); var result = calendar.FilterResource(xmlTree); Assert.True(result); }
public void RetrievalofAllPendingToDos() { var xmlStr = @"<C:filter xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VCALENDAR"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VTODO""> <C:prop-filter name=""COMPLETED""> <C:is-not-defined/> </C:prop-filter> <C:prop-filter name=""STATUS""> <C:text-match negate-condition=""yes"">CANCELLED</C:text-match> </C:prop-filter> </C:comp-filter> </C:comp-filter></C:filter>"; var calStr = @"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VTODO DTSTAMP:20060205T235335Z DUE;VALUE=DATE:20060104 STATUS:NEEDS-ACTION SUMMARY:Task #1 UID:[email protected] BEGIN:VALARM ACTION:AUDIO TRIGGER;RELATED=START:-PT10M END:VALARM END:VTODO END:VCALENDAR"; var calendar = new VCalendar(calStr); var xmlTree = XmlTreeStructure.Parse(xmlStr); var result = calendar.FilterResource(xmlTree); Assert.True(result); }
public void UnitTest2() { var xmlStr = @"<C:filter xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VCALENDAR""> <C:comp-filter name=""VFREEBUSY""> <C:time-range start=""20060102T000000Z"" end=""20060103T000000Z""/> </C:comp-filter> </C:comp-filter> </C:filter>"; var calStr = @"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VFREEBUSY ORGANIZER;CN=""Bernard Desruisseaux"":mailto:[email protected] UID:[email protected] DTSTAMP:20050530T123421Z DTSTART:20060101T100000Z DTEND:20060108T100000Z FREEBUSY;FBTYPE=BUSY-TENTATIVE:20060102T100000Z/20060102T120000Z END:VFREEBUSY END:VCALENDAR"; var calendar = VCalendar.Parse(calStr); var xmlTree = XmlTreeStructure.Parse(xmlStr); var result = calendar.FilterResource(xmlTree); Assert.True(result); }
/// <summary> /// Process the report depending on the values of the header /// and the body. /// </summary> /// <returns></returns> public async Task ProcessRequest(HttpContext httpContext) { var body = new StreamReader(httpContext.Request.Body).ReadToEnd(); // var node = xmlBody.Children.First(); var xmlBody = XmlTreeStructure.Parse(body); //take the target url that is the identifier of the collection var urlId = httpContext.Request.GetRealUrl(); //take the first node of the xml and process the request //by the name of the first node switch (xmlBody.NodeName) { case "calendar-query": await CalendarQuery(xmlBody, urlId, httpContext); break; case "calendar-multiget": await CalendarMultiget(xmlBody, httpContext); break; default: throw new NotImplementedException( $"The REPORT request {xmlBody.NodeName} with ns equal to {xmlBody.MainNamespace} is not implemented yet ."); } }
public void RecursiveSeekerTest3() { var calStr = @"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:[email protected] DTSTAMP:20060206T001220Z DTSTART;TZID=US/Eastern:20060104T100000 DURATION:PT1H LAST-MODIFIED:20060206T001330Z ORGANIZER:mailto:[email protected] SEQUENCE:1 STATUS:TENTATIVE SUMMARY:Event #3 UID:[email protected] X-ABC-GUID:[email protected] END:VEVENT END:VCALENDAR"; var xmlStr = @"<C:comp-filter name=""VCALENDAR"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VTODO""> <C:prop-filter name=""UID""> <C:text-match collation=""i;octet"" >[email protected]</C:text-match> </C:prop-filter> </C:comp-filter> </C:comp-filter>"; var calendar = new VCalendar(calStr); var xmlTree = XmlTreeStructure.Parse(xmlStr); IXMLTreeStructure tree; ICalendarComponent comp; var result = calendar.ComponentSeeker(xmlTree, out tree, out comp); Assert.False(result); /*Assert.Equal("VEVENT", comp.Name); * Assert.Equal("VEVENT", tree.Attributes["name"]);*/ }
public void FilterResourceTest1() { var calStr = @"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:[email protected] DTSTAMP:20060206T001220Z DTSTART;TZID=US/Eastern:20060104T100000 DURATION:PT1H LAST-MODIFIED:20060206T001330Z ORGANIZER:mailto:[email protected] SEQUENCE:1 STATUS:TENTATIVE SUMMARY:Event #3 UID:[email protected] X-ABC-GUID:[email protected] END:VEVENT END:VCALENDAR"; var xmlStr = @"<C:filter xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VCALENDAR"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VEVENT""> <C:prop-filter name=""ATTENDEE""> <C:text-match collation=""i;ascii-casemap"">mailto:[email protected]</C:text-match> <C:param-filter name=""PARTSTAT""> <C:text-match collation=""i;ascii-casemap"">NEEDS-ACTION</C:text-match> </C:param-filter> </C:prop-filter> </C:comp-filter> </C:comp-filter></C:filter>"; var calendar = new VCalendar(calStr); var xmlTree = XmlTreeStructure.Parse(xmlStr); var result = calendar.FilterResource(xmlTree); Assert.True(result); }
public async Task ProcessRequest(HttpRequest request, HttpResponse response) { //check the depth of the header // This report is only defined when the Depth header has value "0"; // other values result in a 400 (Bad Request) error response. if (request.Headers.ContainsKey("Depth")) { var depth = request.Headers["Depth"]; if (depth != "\"0\"") { response.StatusCode = 400; return; } } string href = request.Path; //TODO: take here the email of the user by calling //to the authentication api var userEmail = ""; response = null; //take the string representation of the body var bodyStr = request.Body.ToString(); var xmlbody = XmlTreeStructure.Parse(bodyStr); switch (xmlbody.NodeName) { case "acl-principal-prop-set": await AclPrincipalPropSet(xmlbody, response); break; case "principal-match": await PrincipalMatch(xmlbody, userEmail, href, response); break; case "principal-property-search": await PrincipalPropertySearch(xmlbody, request, response); break; case "principal-search-property-set": await PrincipalSearchPropertySet(response); break; } }
public void UnitTest4() { var doc = @"<?xml version=""1.0"" encoding=""utf-8"" ?> <C:calendar-query xmlns:D=""DAV:"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <D:prop> <D:getetag/> <C:calendar-data> <C:comp name=""VCALENDAR""> <C:prop name=""VERSION""/> <C:comp name=""VEVENT""> <C:prop name=""SUMMARY""/> <C:prop name=""UID""/> <C:prop name=""DTSTART""/> <C:prop name=""DTEND""/> <C:prop name=""DURATION""/> <C:prop name=""RRULE""/> <C:prop name=""RDATE""/> <C:prop name=""EXRULE""/> <C:prop name=""EXDATE""/> <C:prop name=""RECURRENCE-ID""/> </C:comp> <C:comp name=""VTIMEZONE""/> </C:comp> </C:calendar-data> </D:prop> <C:filter> <C:comp-filter name=""VCALENDAR""> <C:comp-filter name=""VEVENT""> <C:time-range start=""20060104T000000Z"" end=""20060105T000000Z""/> </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query>"; var xDoc = XDocument.Parse(doc); xDoc.ToString(); var temp1 = xDoc.Root.Attributes().Where(x => x.IsNamespaceDeclaration); var item = xDoc.CreateWriter(); var result = XmlTreeStructure.Parse(doc); IXMLTreeStructure filter; Assert.True(result.GetChildAtAnyLevel("filter", out filter)); Assert.Equal(filter.GetChild("comp-filter").Attributes["name"], "VCALENDAR"); }
public void IXmlTreeStrucureToString() { var doc = @"<?xml version=""1.0"" encoding=""utf-8"" ?> <C:calendar-query xmlns:D=""DAV:"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <D:prop> <D:getetag/> <C:calendar-data> <C:comp name=""VCALENDAR""> <C:prop name=""VERSION""/> <C:comp name=""VEVENT""> <C:prop name=""SUMMARY""/> <C:prop name=""UID""/> <C:prop name=""DTSTART""/> <C:prop name=""DTEND""/> <C:prop name=""DURATION""/> <C:prop name=""RRULE""/> <C:prop name=""RDATE""/> <C:prop name=""EXRULE""/> <C:prop name=""EXDATE""/> <C:prop name=""RECURRENCE-ID""/> </C:comp> <C:comp name=""VTIMEZONE""/> </C:comp> </C:calendar-data> </D:prop> <C:filter> <C:comp-filter name=""VCALENDAR""> <C:comp-filter name=""VEVENT""> <C:time-range start=""20060104T000000Z"" end=""20060105T000000Z""/> </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query>"; var xmlTreeStructure = XmlTreeStructure.Parse(doc); var xmlTreeStructure2 = XmlTreeStructure.Parse(xmlTreeStructure.ToString()); var xmlStr1 = xmlTreeStructure.ToString(); var xmlStr2 = xmlTreeStructure2.ToString(); Assert.Equal(xmlStr1, xmlStr2); }
public void PartialRetrievalofEventsbyTimeRange() { var xmlStr = @"<C:filter xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VCALENDAR"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp-filter name=""VEVENT""> <C:time-range start=""20060104T000000Z"" end=""20060105T000000Z""/> </C:comp-filter> </C:comp-filter></C:filter>"; var calStr = @"BEGIN:VCALENDAR VERSION:2.0 BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID=US/Eastern:20060102T120000 DURATION:PT1H RRULE:FREQ=DAILY;COUNT=5 SUMMARY:Event #2 UID:[email protected] END:VEVENT END:VCALENDAR"; var calendar = new VCalendar(calStr); var xmlTree = XmlTreeStructure.Parse(xmlStr); var result = calendar.FilterResource(xmlTree); Assert.True(result); }
public void UnitTest3() { var doc = @"<?xml version=""1.0"" encoding=""utf-8"" ?> <C:calendar-query xmlns:D=""DAV:"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <D:prop> <D:getetag/> <C:calendar-data> <C:comp name=""VCALENDAR""> <C:prop name=""VERSION""/> <C:comp name=""VEVENT""> <C:prop name=""SUMMARY""/> <C:prop name=""UID""/> <C:prop name=""DTSTART""/> <C:prop name=""DTEND""/> <C:prop name=""DURATION""/> <C:prop name=""RRULE""/> <C:prop name=""RDATE""/> <C:prop name=""EXRULE""/> <C:prop name=""EXDATE""/> <C:prop name=""RECURRENCE-ID""/> </C:comp> <C:comp name=""VTIMEZONE""/> </C:comp> </C:calendar-data> </D:prop> <C:filter> <C:comp-filter name=""VCALENDAR""> <C:comp-filter name=""VEVENT""> <C:time-range start=""20060104T000000Z"" end=""20060105T000000Z""/> </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query>"; var result = XmlTreeStructure.Parse(doc); IXMLTreeStructure filter; Assert.True(result.GetChildAtAnyLevel("filter", out filter)); Assert.NotNull(filter.GetChild("comp-filter")); }
public async Task <bool> PreconditionsOK(Dictionary <string, string> propertiesAndHeaders, HttpResponse response) { #region Extracting Properties var body = propertiesAndHeaders["body"]; var url = propertiesAndHeaders["url"]; #endregion if (fs.ExistCalendarCollection(url) || await _collectionRepository.Exist(url)) { response.StatusCode = (int)HttpStatusCode.Forbidden; response.Body.Write(@"<?xml version='1.0' encoding='UTF-8'?> <error xmlns='DAV:'> <resource-must-be-null/> </error>"); return(false); } if (!string.IsNullOrEmpty(body)) { var bodyTree = XmlTreeStructure.Parse(body); if (bodyTree == null) { response.StatusCode = (int)HttpStatusCode.Forbidden; response.Body.Write("Wrong Body"); return(false); } if (bodyTree.NodeName != "mkcalendar") { response.StatusCode = (int)HttpStatusCode.Forbidden; response.Body.Write("Wrong Body"); return(false); } } return(true); }
/// <summary> /// Call the method to perform a PROFIND over a /// principal. /// Initially the client could do a PROFIND over /// the server to discover all the user calendars /// or could PORFIND directly over a calendar URL. /// </summary> /// <param name="httpContext"></param> /// <returns>The request</returns> public async Task Profind(HttpContext httpContext) { var requestPath = httpContext.Request.Path; var streamReader = new StreamReader(httpContext.Request.Body); //read the body of the request var bodyString = streamReader.ReadToEnd(); //try to authenticate the request either with the cookies or the user credentials var principal = _authenticate.AuthenticateRequest(httpContext); //if the principal is null then there is some problem with the authentication //so return //if (principal == null) // return; var body = XmlTreeStructure.Parse(bodyString); //take the requested properties var reqProperties = ExtractPropertiesNameMainNS(body); await BuildResponse(httpContext.Response, requestPath, reqProperties, principal); }
/// <summary> /// Get the permission for the given principal /// in some resource. /// </summary> /// <param name="principal">The principal that wants to know his permissions.</param> /// <param name="property">The resource or collection's DAV:acl property</param> /// <returns>Return an I</returns> public static XmlTreeStructure GetCurrentUserPermissions(this Principal principal, Property property) { var pUrl = principal.PrincipalURL; var aclP = XDocument.Parse(property.Value).Root; IEnumerable <XElement> principalGrantPermissions = null; XName aceName = "ace"; //take the permission for the principal if any var descendants = aclP?.Descendants(); var aces = descendants.Where(x => x.Name.LocalName == aceName); var principalAce = aces.FirstOrDefault(ace => ace.Descendants() .FirstOrDefault(x => x.Name.LocalName == "href")?.Value == pUrl); if (principalAce != null) { principalGrantPermissions = principalAce.Descendants() .FirstOrDefault(x => x.Name.LocalName == "grant")?.Elements(); } //take the permission for all users if any var output = new XElement("current-user-privilege-set", new XAttribute(XNamespace.Xmlns + "D", "DAV:")); //add the permission to the response if (principalGrantPermissions != null) { foreach (var permission in principalGrantPermissions) { output.Add(permission); } } var outputStr = output.ToString(); var xmlTree = XmlTreeStructure.Parse(outputStr) as XmlTreeStructure; return(xmlTree); }
/// <summary> /// Build the xml of the body and write /// its string representation to the HttpRespose.Body /// </summary> /// <param name="response">The response of the request.</param> /// <param name="principalsAndProperties">The principals with its properties.</param> /// <returns></returns> public async Task WriteBody(HttpResponse response, Dictionary <Principal, IEnumerable <Property> > principalsAndProperties) { //build the root of the xml var multistatusNode = new XmlTreeStructure("multistatus", "DAV:") { Namespaces = new Dictionary <string, string> { { "D", "DAV:" }, { "C", "urn:ietf:params:xml:ns:caldav" } } }; //take the node that specified the comp and properties //to return foreach (var pp in principalsAndProperties) { IXMLTreeStructure statusNode; //each returned resource has is own response and href nodes var responseNode = new XmlTreeStructure("response", "DAV:"); var hrefNode = new XmlTreeStructure("href", "DAV:"); hrefNode.AddValue(pp.Key.PrincipalURL); //href is a child pf response responseNode.AddChild(hrefNode); //if the resource is null it was not foound so // add an error status if (pp.Value == null) { statusNode = new XmlTreeStructure("status", "DAV:"); statusNode.AddValue("HTTP/1.1 404 Not Found"); responseNode.AddChild(statusNode); } else { var propstatNode = new XmlTreeStructure("propstat", "DAV:"); var propNode = new XmlTreeStructure("prop", "DAV:"); //add the properties to the prop node. foreach (var property in pp.Value) { propNode.AddChild(XmlTreeStructure.Parse(property.Value)); } propstatNode.AddChild(propNode); //adding the status node // TODO: check the status!! statusNode = new XmlTreeStructure("status", "DAV:"); statusNode.AddValue("HTTP/1.1 200 OK"); propstatNode.AddChild(statusNode); responseNode.AddChild(propstatNode); } multistatusNode.AddChild(responseNode); await response.WriteAsync(multistatusNode.ToString()); } }
public async Task MkCalendar(Dictionary <string, string> propertiesAndHeaders, string body, HttpResponse response) { #region Extracting Properties string principalId; propertiesAndHeaders.TryGetValue("principalId", out principalId); string url; propertiesAndHeaders.TryGetValue("url", out url); #endregion propertiesAndHeaders.Add("body", body); PreconditionCheck = new MKCalendarPrecondition(StorageManagement, _collectionRespository); PosconditionCheck = new MKCalendarPosCondition(StorageManagement, _collectionRespository); //Checking that all precondition pass //Cheking Preconditions if (!await PreconditionCheck.PreconditionsOK(propertiesAndHeaders, response)) { return; } //I create here the collection already but i wait for other comprobations before save the database. await CreateDefaultCalendar(propertiesAndHeaders); response.StatusCode = (int)HttpStatusCode.Created; //If it has not body and Posconditions are OK, it is created with default values. if (string.IsNullOrEmpty(body)) { if (!await PosconditionCheck.PosconditionOk(propertiesAndHeaders, response)) { await DeleteCalendarCollection(propertiesAndHeaders, response); response.StatusCode = (int)HttpStatusCode.Forbidden; await response.WriteAsync("Poscondition Failed"); return; } await _collectionRespository.SaveChangeAsync(); return; } //If a body exist the it is parsed like an XmlTree var mkCalendarTree = XmlTreeStructure.Parse(body); //if it does not have set property it is treated as a empty body. if (mkCalendarTree.Children.Count == 0) { if (!await PosconditionCheck.PosconditionOk(propertiesAndHeaders, response)) { await DeleteCalendarCollection(propertiesAndHeaders, response); response.StatusCode = (int)HttpStatusCode.Forbidden; await response.WriteAsync("Poscondition Failed"); return; } await _collectionRespository.SaveChangeAsync(); return; } //now it is assumed that the body contains a set var setTree = mkCalendarTree.GetChild("set"); #region Response Construction in case of error //this only if error during processing. //Creating and filling the root of the xml tree response //All response of a request is conformed by a "multistatus" element. var multistatus = new XmlTreeStructure("multistatus", "DAV:"); multistatus.Namespaces.Add("D", "DAV:"); multistatus.Namespaces.Add("C", "urn:ietf:params:xml:ns:caldav"); var responseTree = new XmlTreeStructure("response", "DAV:"); multistatus.AddChild(responseTree); var href = new XmlTreeStructure("href", "DAV:"); href.AddValue(SystemProperties._baseUrl + url); #endregion //Check if any error occurred during body processing. var hasError = await BuiltResponseForSet(url, null, false, setTree, responseTree); if (hasError) { await DeleteCalendarCollection(propertiesAndHeaders, response); response.ContentType = "application/xml"; ChangeToDependencyError(responseTree); response.StatusCode = 207; await response.WriteAsync(multistatus.ToString()); return; } //Checking Preconditions if (await PosconditionCheck.PosconditionOk(propertiesAndHeaders, response)) { await _collectionRespository.SaveChangeAsync(); return; // return createdMessage; } await DeleteCalendarCollection(propertiesAndHeaders, response); response.StatusCode = (int)HttpStatusCode.Forbidden; await response.WriteAsync("Poscondition Failed"); }
public void UnitTest1() { var xmlStr = @"<?xml version=""1.0"" encoding=""utf-8"" ?> <C:calendar-query xmlns:D=""DAV:"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <D:prop> <D:getetag/> <C:calendar-data> <C:comp name=""VCALENDAR""> <C:prop name=""VERSION""/> <C:comp name=""VEVENT""> <C:prop name=""SUMMARY""/> <C:prop name=""UID""/> <C:prop name=""DTSTART""/> <C:prop name=""DTEND""/> <C:prop name=""DURATION""/> <C:prop name=""RRULE""/> <C:prop name=""RDATE""/> <C:prop name=""EXRULE""/> <C:prop name=""EXDATE""/> <C:prop name=""RECURRENCE-ID""/> </C:comp> <C:comp name=""VTIMEZONE""/> </C:comp> </C:calendar-data> </D:prop> <C:filter> <C:comp-filter name=""VCALENDAR""> <C:comp-filter name=""VEVENT""> <C:time-range start=""20060104T000000Z"" end=""20060105T000000Z""/> </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query>"; var calStr = @"BEGIN:VCALENDAR VERSION:2.0 BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID=US/Eastern:20060102T120000 DURATION:PT1H RRULE:FREQ=DAILY;COUNT=5 SUMMARY:Event #2 UID:[email protected] END:VEVENT BEGIN:VEVENT DTSTART;TZID=US/Eastern:20060104T140000 DURATION:PT1H RECURRENCE-ID;TZID=US/Eastern:20060104T120000 SUMMARY:Event #2 bis UID:[email protected] END:VEVENT BEGIN:VEVENT DTSTART;TZID=US/Eastern:20060106T140000 DURATION:PT1H RECURRENCE-ID;TZID=US/Eastern:20060106T120000 SUMMARY:Event #2 bis bis UID:[email protected] END:VEVENT END:VCALENDAR"; var calendar = new VCalendar(calStr); var xmlTree = XmlTreeStructure.Parse(xmlStr); var result = calendar.FilterResource(xmlTree); Assert.True(result); }
//TODO:Nacho public async Task PropPatch(Dictionary <string, string> propertiesAndHeaders, string body, HttpResponse response) { #region Docummentation //Proppatch is the method used by WebDAV for update, create and delete properties. //The body structure of a Proppatch request is declare as a "proppertyupdate" xml. //As a child of the "proppertyupdate" there are list of "set" and "remove" indicating the //operations that have to be process. This element have to be process in order (top to bottom). //There has to be at least one expected element inside "proppertyupdate". //Each "set" element is composed by a "prop" element witch contains the property name and value //of the properties that have to created or updated (if exists or not). //The same happens for the "remove" elements but these don't include the value of the property inside //the "prop" element. #endregion #region Extracting Properties string calendarResourceId; propertiesAndHeaders.TryGetValue("calendarResourceID", out calendarResourceId); string url; propertiesAndHeaders.TryGetValue("url", out url); #endregion //Checking precondition PreconditionCheck = new ProppatchPrecondition(_collectionRespository, _resourceRespository); if (!await PreconditionCheck.PreconditionsOK(propertiesAndHeaders, response)) { return; } //Creating and filling the root of the xml tree response //All response of a request is conformed by a "multistatus" element. var multistatus = new XmlTreeStructure("multistatus", "DAV:"); multistatus.Namespaces.Add("D", "DAV:"); multistatus.Namespaces.Add("C", "urn:ietf:params:xml:ns:caldav"); response.ContentType = "application/xml"; //getting the request body structure IXMLTreeStructure xmlTree; try { xmlTree = XmlTreeStructure.Parse(body); } catch (Exception) { response.StatusCode = StatusCodes.Status400BadRequest; return; } //checking that the request has propertyupdate node if (xmlTree.NodeName != "propertyupdate") { response.StatusCode = (int)HttpStatusCode.BadRequest; await response.WriteAsync( @"Body in bad format, body of proppatch must contain ""propertyupdate"" xml element"); return; } //throw new ArgumentException(@"Body in bad format, body of proppatch must contain ""propertyupdate"" xml element"); var propertyupdate = xmlTree; //aliasing the list with all "set" and "remove" structures inside "propertyupdate". var setsAndRemoves = propertyupdate.Children; //propertyupdate must have at least one element if (setsAndRemoves.Count == 0) { response.StatusCode = (int)HttpStatusCode.BadRequest; await response.WriteAsync("propertyupdate must have at least one element"); return; //throw new ArgumentException("propertyupdate must have at least one element"); } //The structure of a response for a proppatch has a "multistatus" //as root inside it, there is only one response because depth is not allowed. //Inside the "response" is necessary to add a "propstat" for each property. //This "propstat" is built with a "prop" element containing just the property name //and a "status" with the exit status code. var responseTree = new XmlTreeStructure("response", "DAV:"); multistatus.AddChild(responseTree); #region Adding the <D:href>/api/v1/collections/{userEmail}|{groupName}/{principalId}/{collectionName}/{calendarResourceId}?</D:href> var href = new XmlTreeStructure("href", "DAV:"); href.AddValue(SystemProperties._baseUrl + url); responseTree.AddChild(href); #endregion //Proppatch is atomic, though when an error occurred in one property, //all failed, an all other properties received a "424 failed dependency". var hasError = false; //Here it is garanted that if an error occured during the processing of the operations //The changes will not be stored in db thanks to a rollback. //For each set and remove try to execute the operation if something fails //put the Failed Dependency Error to every property before and after the error //even if the operation for the property was succesfully changed. foreach (var setOrRemove in setsAndRemoves) { if (setOrRemove.NodeName == "set") { hasError = await BuiltResponseForSet(url, calendarResourceId, hasError, setOrRemove, responseTree); } else { hasError = await BuiltResponseForRemove(url, calendarResourceId, hasError, setOrRemove, responseTree); } } if (hasError) { ChangeToDependencyError(responseTree); } else { await _collectionRespository.SaveChangeAsync(); } response.StatusCode = 207; await response.WriteAsync(multistatus.ToString()); }
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)); }
//TODO: Poner esto en la capa de datos public async Task AddCalendarObjectResource(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(); } string ifnonematch; var ifNoneMatchEtags = new List <string>(); propertiesAndHeaders.TryGetValue("If-None-Match", out ifnonematch); if (ifnonematch != null) { ifNoneMatchEtags = ifnonematch.Split(',').ToList(); } string body; propertiesAndHeaders.TryGetValue("body", out body); #endregion //Note: calendar object resource = COR //CheckAllPreconditions PreconditionCheck = new PutPrecondition(StorageManagement, _collectionRespository, _resourceRespository); if (!await PreconditionCheck.PreconditionsOK(propertiesAndHeaders, response)) { return; } var resourceExist = await _resourceRespository.Exist(url); //If the ifmatch is included i look for the etag in the resource, but first the resource has to exist. //If all is ok and the if-match etag matches the etag in the resource then i update the resource. //If the if-match dont match then i set that the precondition failed. if (ifMatchEtags.Count > 0) { if (resourceExist) { var resource = _resourceRespository.Get(url); var resourceEtag = XmlTreeStructure.Parse(resource.Properties.FirstOrDefault(x => x.Name == "getetag")?.Value) .Value; if (ifMatchEtags.Contains(resourceEtag)) { await UpdateCalendarObjectResource(propertiesAndHeaders, response); return; } response.StatusCode = (int)HttpStatusCode.PreconditionFailed; return; } } if (ifNoneMatchEtags.Count > 0 && ifNoneMatchEtags.Contains("*")) { if (!resourceExist) { await CreateCalendarObjectResource(propertiesAndHeaders, response); return; } response.StatusCode = (int)HttpStatusCode.PreconditionFailed; return; } if (resourceExist && StorageManagement.ExistCalendarObjectResource(url)) { await UpdateCalendarObjectResource(propertiesAndHeaders, response); return; } await CreateCalendarObjectResource(propertiesAndHeaders, response); //return HTTP 201 Created }
/// <summary> /// Having the principal and the requested properties /// then proccess the requestedProperties and build the /// response. /// </summary> /// <param name="response">The HttpResponse from the controller.</param> /// <param name="requestedUrl">The principal url if anyurl </param> /// <param name="reqProperties">Contains the requested properties for the principal. key=name, Value = ns</param> /// <param name="principal">The instance of the pricipal that is requested</param> /// <returns>The final response to return. Has the body with the response and the</returns> public async Task BuildResponse(HttpResponse response, string requestedUrl, List <KeyValuePair <string, string> > reqProperties, Principal principal) { //if the principal is not authenticated then set in the response statusCode if (principal == null) { response.StatusCode = StatusCodes.Status401Unauthorized; } var multistatusNode = new XmlTreeStructure("multistatus", "DAV:"); multistatusNode.Namespaces.Add("D", "DAV:"); multistatusNode.Namespaces.Add("C", "urn:ietf:params:xml:ns:caldav"); IEnumerable <IXMLTreeStructure> properties = null; //create the response node. var responseNode = new XmlTreeStructure("response", "DAV:"); //create the href node var hrefNode = new XmlTreeStructure("href", "DAV:"); //var url = requestedUrl.Replace(SystemProperties._baseUrl , ""); hrefNode.AddValue(requestedUrl); responseNode.AddChild(hrefNode); //in this section is where the "propstat" structure its build. var propstatNode = new XmlTreeStructure("propstat", "DAV:"); var propNode = new XmlTreeStructure("prop", "DAV:"); //check this because the principal could not be authenticated if (principal != null) { //add the requested properties to the propNode //if the properties exist in the principal properties = principal.Properties .Where(p => reqProperties.Contains(new KeyValuePair <string, string>(p.Name, p.Namespace))) .Select(x => XmlTreeStructure.Parse(x.Value)); } //check the properties that are generated per request //and are not contained in the principal's properties foreach (var reqProperty in reqProperties) { //here the additional properties for the principal that //are created per request switch (reqProperty.Key) { case "current-user-principal": propNode.AddChild(PropertyCreation.CreateCurrentUserPrincipal(principal)); break; case "principal-URL": propNode.AddChild(new XmlTreeStructure("principal-URL", "DAV:") { Value = principal.PrincipalURL }); break; } } if (properties != null) { //add the properties to the propNode foreach (var property in properties) { propNode.AddChild(property); } } var statusNode = new XmlTreeStructure("status", "DAV:") { Value = "HTTP/1.1 200 OK" }; //add the propNOde and the status node to the propStatNode propstatNode.AddChild(propNode).AddChild(statusNode); responseNode.AddChild(propstatNode); multistatusNode.AddChild(responseNode); //here the multistatus xml for the body is built //have to write it to the response body. var responseText = multistatusNode.ToString(); var responseBytes = Encoding.UTF8.GetBytes(responseText); response.ContentLength = responseBytes.Length; await response.Body.WriteAsync(responseBytes, 0, responseBytes.Length); }
/// <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> /// Returns a Response XML element with all the property names /// and property values of the visible properties. /// </summary> /// <param name="url"></param> /// <param name="calendarResourceId">Name of the resource</param> /// <param name="additionalProperties">List of additional requested properties (key=name; value=namespace)</param> /// <returns></returns> private async Task <XmlTreeStructure> AllPropFillTree(string url, string calendarResourceId, List <KeyValuePair <string, string> > additionalProperties) { #region Adding the response of the collection or resource. //Adding standard structure for a "response" element. var treeChild = new XmlTreeStructure("response", "DAV:"); #region Adding the <D:href>/api/v1/collections/users|groups/principalId/{collectionName}/{calendarResourceId}?</D:href> var href = new XmlTreeStructure("href", "DAV:"); href.AddValue(SystemProperties._baseUrl + url); treeChild.AddChild(href); #endregion #region Adding the propstat #region Selecting properties var propertiesCol = new List <XmlTreeStructure>(); var propertiesOk = new List <XmlTreeStructure>(); var propertiesWrong = new List <XmlTreeStructure>(); var errorStack = new Stack <string>(); //Here all visible properties are retrieve plus a collection of extra properties that can be //defined in the request body. if (calendarResourceId == null) { var properties = await _collectionRepository.GetAllProperties(url); foreach (var property in properties) { //TODO: Check that the property is accessible beyond its visibility. var tempTree = property.Value == null ? new XmlTreeStructure(property.Name, property.Namespace) { Value = "" } : XmlTreeStructure.Parse(property.Value); propertiesCol.Add((XmlTreeStructure)tempTree); } //looking for additional properties if (additionalProperties != null && additionalProperties.Count > 0) { foreach (var addProperty in additionalProperties) { //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); } } } else { var properties = await _resourceRespository.GetAllProperties(url); foreach (var property in properties) { //TODO: Check that the property is accessible beyond its visibility. var tempTree = property.Value == null ? new XmlTreeStructure(property.Name, property.Namespace) { Value = "" } : XmlTreeStructure.Parse(property.Value); propertiesCol.Add((XmlTreeStructure)tempTree); } //looking for additional properties if (additionalProperties != null && additionalProperties.Count > 0) { foreach (var addProperty in additionalProperties) { //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); } } } //Here there are divided all properties between recovered and error recovering foreach (var propTree in propertiesCol) { if (propTree.Value != null) { propertiesOk.Add(propTree); } else { propertiesWrong.Add(propTree); } } #endregion #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 propertiesOk) { propOk.AddChild(property); } propstatOk.AddChild(propOk); #region Adding nested status OK var statusOk = new XmlTreeStructure("status", "DAV:"); statusOk.AddValue("HTTP/1.1 200 OK"); propstatOk.AddChild(statusOk); #endregion #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 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 //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 #endregion //If any of the "status" group is empty, it is not included. if (propertiesOk.Count > 0) { treeChild.AddChild(propstatOk); } if (propertiesWrong.Count > 0) { treeChild.AddChild(propstatWrong); } #endregion return(treeChild); #endregion }
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)); }
/// <summary> /// Returns the real value of the given property. /// </summary> /// <param name="property"></param> /// <returns></returns> public static string PropertyRealValue(this Property property) { return(XmlTreeStructure.Parse(property.Value).Value); }
//private CalDavContext db { get; } #endregion #region PROPFIND methods //TODO: Nacho /// <summary> /// This PROFIND is used for the collection and the resources. /// </summary> /// <param name="propertiesAndHeaders">Put here: resourceURL, depth, calendarResourceId</param> /// <param name="body">The request body from the client.</param> /// <param name="response"> /// The Response property in the controller. We fill up the response object /// with out response. /// </param> public async Task PropFind(Dictionary <string, string> propertiesAndHeaders, string body, HttpResponse response) { #region Extracting Properties string calendarResourceId; propertiesAndHeaders.TryGetValue("calendarResourceID", out calendarResourceId); string url; propertiesAndHeaders.TryGetValue("url", out url); //Taking depth form headers. //Depth 0 means that it looks for prop only in the collection //Depth 1 means that it looks in their childs too. //And infinitum that looks in the entirely tree. int depth; string strDepth; propertiesAndHeaders.TryGetValue("depth", out strDepth); try { depth = strDepth != null?int.Parse(strDepth) : 0; } catch (Exception) { depth = -1; } #endregion //Creating and filling the root of the xml tree response //All response are composed of a "multistatus" xml element //witch contains a "response" element for each collection and resource analized witch url is included in a "href" element as a child of "response". //As a child of the "response" there is a list of "propstat". One for each different status obtained //trying to get the specified properties. //Inside every "propstatus" there are a xml element "prop" with all the properties that match with //the given "status" and a "status" xml containing the message of his "propstat". //Todo respuesta de propfind esta compuesta de un elemento xml "multistatus", //El cual contiene un elemento xml "response" por cada colleccion o recurso analizado. //Dentro de cada "response" hay una lista de "propstat", uno por cada status distinto obtenido //al intentar recobrar las propiedades especificadas. //Dentro de cada "propstatus" hay un xml "prop" con todas las propiedades que mapean con el //status correspondiente y un xml "status" que tiene el mensaje del estado de dicho "propstat". //checking Precondtions PreconditionCheck = new PropfindPrecondition(_collectionRespository, _resourceRespository); if (!await PreconditionCheck.PreconditionsOK(propertiesAndHeaders, response)) { return; } response.StatusCode = 207; response.ContentType = "application/xml"; var responseTree = new XmlTreeStructure("multistatus", "DAV:"); responseTree.Namespaces.Add("D", "DAV:"); responseTree.Namespaces.Add("C", "urn:ietf:params:xml:ns:caldav"); responseTree.Namespaces.Add("S", _namespacesSimple["S"]); //Tool that contains the methods for propfind. PropFindMethods = new CalDavPropfind(_collectionRespository, _resourceRespository); //if the body is empty assume that is an allprop request. if (string.IsNullOrEmpty(body)) { await PropFindMethods.AllPropMethod(url, calendarResourceId, depth, null, responseTree); await response.WriteAsync(responseTree.ToString()); return; } //parsing the body into a xml tree var xmlTree = XmlTreeStructure.Parse(body); //Managing if the body was ok if (xmlTree.NodeName != "propfind") { response.StatusCode = (int)HttpStatusCode.BadRequest; return; } //Finding the right method of propfind, it is found in the first child of the tree. //This methods take the response tree and they completed it with the necessary values and structure. var propType = xmlTree.Children[0]; switch (propType.NodeName) { case "prop": var props = ExtractPropertiesNameMainNS((XmlTreeStructure)xmlTree); //take the principalId from the properties var principalId = propertiesAndHeaders["principalId"]; var principal = _principalRepository.GetByIdentifier(principalId); await PropFindMethods.PropMethod(url, calendarResourceId, depth, props, responseTree, principal); break; case "allprop": var additionalProperties = ExtractIncludePropertiesNameMainNS((XmlTreeStructure)xmlTree); await PropFindMethods.AllPropMethod(url, calendarResourceId, depth, additionalProperties, responseTree); break; case "propname": await PropFindMethods.PropNameMethod(url, calendarResourceId, depth, responseTree); break; default: response.StatusCode = (int)HttpStatusCode.BadRequest; return; } var responseText = responseTree.ToString(); var responseBytes = Encoding.UTF8.GetBytes(responseText); response.ContentLength = responseBytes.Length; await response.WriteAsync(responseText); }
public void ToStringTest() { var calString = @"BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN X-WR-CALNAME:[email protected] X-WR-TIMEZONE:America/Los_Angeles BEGIN:VTIMEZONE TZID:America/Los_Angeles X-LIC-LOCATION:America/Los_Angeles BEGIN:DAYLIGHT TZOFFSETFROM:-0800 TZOFFSETTO:-0700 TZNAME:PDT DTSTART:19700308T020000 RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU END:DAYLIGHT BEGIN:STANDARD TZOFFSETFROM:-0700 TZOFFSETTO:-0800 TZNAME:PST DTSTART:19701101T020000 RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU RDATE:19450603T010000 END:STANDARD END:VTIMEZONE BEGIN:VEVENT DTSTART;TZID=America/Los_Angeles:20120629T130000 DTEND;TZID=America/Los_Angeles:20120629T140000 DTSTAMP:20120629T112428Z UID:[email protected] RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU CREATED:20120629T111935Z DESCRIPTION:foo LAST-MODIFIED:20120629T112428Z LOCATION:Barcelona SEQUENCE:0 STATUS:CONFIRMED SUMMARY:Demo B2G Calendar TRANSP:OPAQUE BEGIN:VALARM ACTION:EMAIL DESCRIPTION:This is an event reminder SUMMARY:Alarm notification ATTENDEE:mailto:[email protected] TRIGGER:-P0DT0H30M0S END:VALARM BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:This is an event reminder TRIGGER:-P0DT0H30M0S END:VALARM END:VEVENT END:VCALENDAR "; VCalendar calendar = VCalendar.Parse(calString); var xmlDoc = @"<?xml version=""1.0"" encoding=""utf-8"" ?> <C:calendar-query xmlns:D=""DAV:"" xmlns:C=""urn:ietf:params:xml:ns:caldav""> <D:prop> <D:getetag/> <C:calendar-data> <C:comp name=""VCALENDAR""> <C:prop name=""VERSION""/> <C:comp name=""VEVENT""> <C:prop name=""SUMMARY""/> <C:prop name=""UID""/> <C:prop name=""DTSTART""/> <C:prop name=""DTEND""/> </C:comp> <C:comp name=""VTIMEZONE""/> </C:comp> </C:calendar-data> </D:prop> <C:filter> <C:comp-filter name=""VCALENDAR""> <C:comp-filter name=""VEVENT""> <C:time-range start=""20060104T000000Z"" end=""20060105T000000Z""/> </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query>"; var xmlTree = XmlTreeStructure.Parse(xmlDoc); var node = xmlTree.GetChildAtAnyLevel("calendar-data"); var newCalString = calendar.ToString(node); var newCal = new VCalendar(newCalString); Assert.Equal(2, newCal.CalendarComponents.Count); Assert.Contains("VEVENT", newCal.CalendarComponents.Keys); Assert.Contains("VTIMEZONE", newCal.CalendarComponents.Keys); Assert.Equal(4, newCal.CalendarComponents["VEVENT"].First().Properties.Count); }
public void UnitTest3() { var calStr = @"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN BEGIN:VTIMEZONE LAST-MODIFIED:20040110T032845Z TZID:US/Eastern BEGIN:DAYLIGHT DTSTART:20000404T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZNAME:EDT TZOFFSETFROM:-0500 TZOFFSETTO:-0400 END:DAYLIGHT BEGIN:STANDARD DTSTART:20001026T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZNAME:EST TZOFFSETFROM:-0400 TZOFFSETTO:-0500 END:STANDARD END:VTIMEZONE BEGIN:VEVENT ATTENDEE;PARTSTAT=ACCEPTED;ROLE=CHAIR:mailto:[email protected] ATTENDEE;PARTSTAT=NEEDS-ACTION:mailto:[email protected] DTSTAMP:20060206T001220Z DTSTART;TZID=US/Eastern:20060104T100000 DURATION:PT1H RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 LAST-MODIFIED:20060206T001330Z ORGANIZER:mailto:[email protected] SEQUENCE:1 STATUS:TENTATIVE SUMMARY:Event #3 UID:[email protected] X-ABC-GUID:[email protected] END:VEVENT END:VCALENDAR"; var result = VCalendar.Parse(calStr); var xmlStr = @" <C:calendar-data xmlns:C=""urn:ietf:params:xml:ns:caldav""> <C:comp name=""VCALENDAR""> <C:prop name=""VERSION""/> <C:comp name=""VEVENT""> <C:prop name=""SUMMARY""/> <C:prop name=""UID""/> <C:prop name=""DTSTART""/> <C:prop name=""DTEND""/> <C:prop name=""DURATION""/> <C:prop name=""RRULE""/> <C:prop name=""ATTENDEE""/> <C:prop name=""EXRULE""/> <C:prop name=""EXDATE""/> <C:prop name=""RECURRENCE-ID""/> </C:comp> <C:comp name=""VTIMEZONE""/> </C:comp> </C:calendar-data>"; var calString = result.ToString(XmlTreeStructure.Parse(xmlStr)); }