/// <summary> /// Used to build a response with an error /// </summary> /// <param name="response">The response that comes from the controller</param> /// <param name="errorMessage">The message to put in the error.</param> /// <param name="errorCode">The code of the error.</param> /// <param name="href">The requested href.</param> /// <returns></returns> private async Task ReturnError(HttpResponse response, string errorMessage, int errorCode, string href) { //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" } } }; //each returned resource has is own response and href nodes var responseNode = new XmlTreeStructure("response", "DAV:"); var hrefNode = new XmlTreeStructure("href", "DAV:"); hrefNode.AddValue(href); //href is a child pf response responseNode.AddChild(hrefNode); IXMLTreeStructure statusNode = new XmlTreeStructure("status", "DAV:"); statusNode.AddValue($"HTTP/1.1 {errorCode} {errorMessage}"); responseNode.AddChild(statusNode); multistatusNode.AddChild(responseNode); await response.WriteAsync(multistatusNode.ToString()); }
/// <summary> /// Take the calendar that passed the filter and /// create the multi-status xml. /// </summary> /// <param name="resources">The resources to be returned</param> /// <param name="calDataNode"> /// THis is the node with name ="prop" /// When used in a calendaring REPORT request, the CALDAV:calendar-data XML /// element specifies which parts of calendar object resources need to be returned in the /// response.If the CALDAV:calendar-data XML element doesn't contain any /// CALDAV:comp element, calendar object resources will be returned in their entirety. /// </param> /// <param name="httpContext"></param> /// <returns>The string representation of the multi-status Xml with the results.</returns> public async Task ReportResponseBuilder(IEnumerable <KeyValuePair <string, VCalendar> > resources, IXMLTreeStructure calDataNode, HttpContext httpContext) { 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 resource in resources) { IXMLTreeStructure statusNode; //each returned resource has is own response and nodes var responseNode = new XmlTreeStructure("response", "DAV:"); var hrefNode = new XmlTreeStructure("href", "DAV:"); var href = resource.Key[0] != '/' ? "/" + resource.Key : resource.Key; hrefNode.AddValue(href); //href is a child pf response responseNode.AddChild(hrefNode); //if the resource is null it was not foound so // add an error status if (resource.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:"); //that the requested data var propStats = await ProccessPropNode(calDataNode, resource); foreach (var propStat in propStats) { responseNode.AddChild(propStat); } } multistatusNode.AddChild(responseNode); } var responseText = multistatusNode.ToString(); var responseBytes = Encoding.UTF8.GetBytes(responseText); httpContext.Response.ContentLength = responseBytes.Length; await httpContext.Response.Body.WriteAsync(responseBytes, 0, responseBytes.Length); }
/// <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()); } }
/// <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); }
//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 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"); }
//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); }