public static Uri AlignServerUrl(Uri configuredServerUrl, WebResourceName referenceResourceNameOrNull) { if (referenceResourceNameOrNull != null) { var filename = Path.GetFileName(referenceResourceNameOrNull.OriginalAbsolutePath); if (!string.IsNullOrEmpty(filename)) { var filenameIndex = referenceResourceNameOrNull.OriginalAbsolutePath.LastIndexOf(filename); if (filenameIndex != -1) { var resourcePath = referenceResourceNameOrNull.OriginalAbsolutePath.Remove(filenameIndex); var newUri = new Uri(configuredServerUrl, resourcePath); // Only if new aligned Uri has a different encoded AbsolutePath but is identical when decoded return newUri // else we assume filename truncation didn't work and return the original configuredServerUrl if (newUri.AbsolutePath != configuredServerUrl.AbsolutePath) { if (DecodeUrlString(newUri.AbsolutePath) == DecodeUrlString(configuredServerUrl.AbsolutePath)) { return(newUri); } s_logger.DebugFormat("Aligned decoded resource uri path '{0}' different from server uri '{1}'", newUri.AbsolutePath, configuredServerUrl.AbsolutePath); } } } } return(configuredServerUrl); }
private IReadOnlyList <EntityVersion <WebResourceName, string> > ExtractVersions(XmlDocumentWithNamespaceManager responseXml) { var responseNodes = responseXml.XmlDocument.SelectNodes("/D:multistatus/D:response", responseXml.XmlNamespaceManager); if (responseNodes == null) { return(new EntityVersion <WebResourceName, string> [0]); } var entities = new List <EntityVersion <WebResourceName, string> >(); // ReSharper disable once LoopCanBeConvertedToQuery // ReSharper disable once PossibleNullReferenceException foreach (XmlElement responseElement in responseNodes) { var urlNode = responseElement.SelectSingleNode("D:href", responseXml.XmlNamespaceManager); var etagNode = responseElement.SelectSingleNode("D:propstat/D:prop/D:getetag", responseXml.XmlNamespaceManager); if (urlNode != null && etagNode != null && _serverUrl.AbsolutePath != UriHelper.DecodeUrlString(urlNode.InnerText)) { var uri = new WebResourceName(urlNode.InnerText); var etag = HttpUtility.GetQuotedEtag(etagNode.InnerText); if (s_logger.IsDebugEnabled) { s_logger.DebugFormat($"Got version '{uri}': '{etag}'"); } entities.Add(EntityVersion.Create(uri, etag)); } } return(entities); }
public Task <EntityVersion <WebResourceName, string> > TryUpdateEntity(WebResourceName url, string etag, string iCalData) { var path = Path.Combine(_directory.FullName, url.OriginalAbsolutePath); File.WriteAllText(path, iCalData); return(Task.FromResult(EntityVersion.Create(url, File.GetLastWriteTimeUtc(path).ToString("o")))); }
public async Task <IReadOnlyList <EntityVersion <WebResourceName, string> > > GetVersions(IEnumerable <WebResourceName> eventUrls) { WebResourceName firstResourceNameOrNull = null; var requestBody = @"<?xml version=""1.0""?> <C:calendar-multiget xmlns:C=""urn:ietf:params:xml:ns:caldav"" xmlns:D=""DAV:""> <D:prop> <D:getetag/> </D:prop> " + String.Join( "\r\n", eventUrls.Select(u => { if (firstResourceNameOrNull == null) { firstResourceNameOrNull = u; } return($"<D:href>{SecurityElement.Escape(u.OriginalAbsolutePath)}</D:href>"); })) + @" </C:calendar-multiget>"; var responseXml = await _webDavClient.ExecuteWebDavRequestAndReadResponse( UriHelper.AlignServerUrl(_serverUrl, firstResourceNameOrNull), "REPORT", 1, null, null, "application/xml", requestBody ); return(ExtractVersions(responseXml)); }
public async Task DeleteEntity(WebResourceName uri, string etag) { s_logger.DebugFormat("Deleting entity '{0}'", uri); var absoluteEventUrl = new Uri(_serverUrl, uri.OriginalAbsolutePath); s_logger.DebugFormat("Absolute entity location: '{0}'", absoluteEventUrl); IHttpHeaders responseHeaders = await _webDavClient.ExecuteWebDavRequestAndReturnResponseHeaders( absoluteEventUrl, "DELETE", null, etag, null, null, string.Empty); IEnumerable <string> errorValues; if (responseHeaders.TryGetValues("X-Dav-Error", out errorValues)) { var errorList = errorValues.ToList(); if (errorList.Any(v => v != "200 No error")) { throw new Exception(string.Format("Error deleting entity with url '{0}' and etag '{1}': {2}", uri, etag, string.Join(",", errorList))); } } }
public async Task <IReadOnlyList <EntityWithId <WebResourceName, string> > > GetEntities(IEnumerable <WebResourceName> eventUrls) { WebResourceName firstResourceNameOrNull = null; var requestBody = @"<?xml version=""1.0""?> <C:calendar-multiget xmlns:C=""urn:ietf:params:xml:ns:caldav"" xmlns:D=""DAV:""> <D:prop> <D:getetag/> <D:displayname/> <C:calendar-data/> </D:prop> " + String.Join( "\r\n", eventUrls.Select( u => { if (firstResourceNameOrNull == null) { firstResourceNameOrNull = u; } return($"<D:href>{SecurityElement.Escape(u.OriginalAbsolutePath)}</D:href>"); })) + @" </C:calendar-multiget>"; var responseXml = await _webDavClient.ExecuteWebDavRequestAndReadResponse( UriHelper.AlignServerUrl(_serverUrl, firstResourceNameOrNull), "REPORT", 1, null, null, "application/xml", requestBody ); XmlNodeList responseNodes = responseXml.XmlDocument.SelectNodes("/D:multistatus/D:response", responseXml.XmlNamespaceManager); var entities = new List <EntityWithId <WebResourceName, string> >(); if (responseNodes == null) { return(entities); } // ReSharper disable once LoopCanBeConvertedToQuery // ReSharper disable once PossibleNullReferenceException foreach (XmlElement responseElement in responseNodes) { var urlNode = responseElement.SelectSingleNode("D:href", responseXml.XmlNamespaceManager); var dataNode = responseElement.SelectSingleNode("D:propstat/D:prop/C:calendar-data", responseXml.XmlNamespaceManager); if (urlNode != null && !string.IsNullOrEmpty(dataNode?.InnerText)) { entities.Add(EntityWithId.Create(new WebResourceName(urlNode.InnerText), dataNode.InnerText)); } } return(entities); }
public async Task <EntityVersion <WebResourceName, string> > TryUpdateEntity(WebResourceName url, string etag, string contents) { s_logger.DebugFormat("Updating entity '{0}'", url); var absoluteEventUrl = new Uri(_serverUrl, url.OriginalAbsolutePath); s_logger.DebugFormat("Absolute entity location: '{0}'", absoluteEventUrl); IHttpHeaders responseHeaders; try { responseHeaders = await _webDavClient.ExecuteWebDavRequestAndReturnResponseHeaders( absoluteEventUrl, "PUT", null, etag, null, "text/calendar", contents); } catch (WebDavClientException x) when(x.StatusCode == HttpStatusCode.NotFound || x.StatusCode == HttpStatusCode.PreconditionFailed) { return(null); } if (s_logger.IsDebugEnabled) { s_logger.DebugFormat("Updated entity. Server response header: '{0}'", responseHeaders.ToString().Replace("\r\n", " <CR> ")); } Uri effectiveEventUrl; if (responseHeaders.LocationOrNull != null) { s_logger.DebugFormat("Server sent new location: '{0}'", responseHeaders.LocationOrNull); effectiveEventUrl = responseHeaders.LocationOrNull.IsAbsoluteUri ? responseHeaders.LocationOrNull : new Uri(_serverUrl, responseHeaders.LocationOrNull); s_logger.DebugFormat("New entity location: '{0}'", effectiveEventUrl); } else { effectiveEventUrl = absoluteEventUrl; } var newEtag = responseHeaders.ETagOrNull; string version; if (newEtag != null) { version = newEtag; } else { version = await GetEtag(effectiveEventUrl); } return(new EntityVersion <WebResourceName, string> (new WebResourceName(effectiveEventUrl), version)); }
public Task <bool> TryDeleteEntity(WebResourceName uri, string etag) { var path = Path.Combine(_directory.FullName, uri.OriginalAbsolutePath); if (!File.Exists(path)) { return(Task.FromResult(false)); } File.Delete(path); return(Task.FromResult(true)); }
public async Task <IReadOnlyList <EntityVersion <WebResourceName, string> > > GetVersions(IEnumerable <WebResourceName> urls) { WebResourceName firstResourceNameOrNull = null; var requestBody = @"<?xml version=""1.0"" encoding=""utf-8"" ?> <A:addressbook-multiget xmlns:D=""DAV:"" xmlns:A=""urn:ietf:params:xml:ns:carddav""> <D:prop> <D:getetag/> <D:getcontenttype/> </D:prop> " + String.Join( "\r\n", urls.Select(u => { if (firstResourceNameOrNull == null) { firstResourceNameOrNull = u; } return($"<D:href>{SecurityElement.Escape(u.OriginalAbsolutePath)}</D:href>"); })) + @" </A:addressbook-multiget> "; try { var responseXml = await _webDavClient.ExecuteWebDavRequestAndReadResponse( UriHelper.AlignServerUrl(_serverUrl, firstResourceNameOrNull), "REPORT", 0, null, null, "application/xml", requestBody ); return(ExtractVersions(responseXml)); } catch (WebDavClientException x) { if (x.StatusCode == HttpStatusCode.NotFound) { return new EntityVersion <WebResourceName, string>[] { } } ; throw; } }
public async Task <EntityVersion <WebResourceName, string> > UpdateEntity(WebResourceName url, string etag, string contents) { s_logger.DebugFormat("Updating entity '{0}'", url); var absoluteContactUrl = new Uri(_serverUrl, url.OriginalAbsolutePath); s_logger.DebugFormat("Absolute entity location: '{0}'", absoluteContactUrl); IHttpHeaders responseHeaders = await _webDavClient.ExecuteWebDavRequestAndReturnResponseHeaders( absoluteContactUrl, "PUT", null, etag, null, "text/vcard", contents); if (s_logger.IsDebugEnabled) { s_logger.DebugFormat("Updated entity. Server response header: '{0}'", responseHeaders.ToString().Replace("\r\n", " <CR> ")); } Uri effectiveContactUrl; if (responseHeaders.Location != null) { s_logger.DebugFormat("Server sent new location: '{0}'", responseHeaders.Location); effectiveContactUrl = responseHeaders.Location.IsAbsoluteUri ? responseHeaders.Location : new Uri(_serverUrl, responseHeaders.Location); s_logger.DebugFormat("New entity location: '{0}'", effectiveContactUrl); } else { effectiveContactUrl = absoluteContactUrl; } var newEtag = responseHeaders.ETag; string version; if (newEtag != null) { version = newEtag; } else { version = await GetEtag(effectiveContactUrl); } return(new EntityVersion <WebResourceName, string> (new WebResourceName(effectiveContactUrl), version)); }
private async Task <IReadOnlyList <EntityVersion <WebResourceName, string> > > GetVersions(DateTimeRange?range, string entityType) { var entities = new List <EntityVersion <WebResourceName, string> >(); try { var responseXml = await _webDavClient.ExecuteWebDavRequestAndReadResponse( _serverUrl, "REPORT", 1, null, null, "application/xml", string.Format( @"<?xml version=""1.0""?> <C:calendar-query xmlns:C=""urn:ietf:params:xml:ns:caldav""> <D:prop xmlns:D=""DAV:""> <D:getetag/> </D:prop> <C:filter> <C:comp-filter name=""VCALENDAR""> <C:comp-filter name=""{0}""> {1} </C:comp-filter> </C:comp-filter> </C:filter> </C:calendar-query> ", entityType, range == null ? string.Empty : string.Format(@"<C:time-range start=""{0}"" end=""{1}""/>", range.Value.From.ToString(s_calDavDateTimeFormatString), range.Value.To.ToString(s_calDavDateTimeFormatString)) )); XmlNodeList responseNodes = responseXml.XmlDocument.SelectNodes("/D:multistatus/D:response", responseXml.XmlNamespaceManager); // ReSharper disable once LoopCanBeConvertedToQuery // ReSharper disable once PossibleNullReferenceException foreach (XmlElement responseElement in responseNodes) { var urlNode = responseElement.SelectSingleNode("D:href", responseXml.XmlNamespaceManager); var etagNode = responseElement.SelectSingleNode("D:propstat/D:prop/D:getetag", responseXml.XmlNamespaceManager); if (urlNode != null && etagNode != null && _serverUrl.AbsolutePath != UriHelper.DecodeUrlString(urlNode.InnerText)) { var uri = new WebResourceName(urlNode.InnerText); entities.Add(EntityVersion.Create(uri, HttpUtility.GetQuotedEtag(etagNode.InnerText))); } } } catch (WebDavClientException x) { // Workaround for Synology NAS, which returns 404 insteaod of an empty response if no events are present if (x.StatusCode == HttpStatusCode.NotFound && await IsResourceCalender()) { return(entities); } throw; } return(entities); }
public async Task <IReadOnlyList <EntityWithId <WebResourceName, string> > > GetEntities(IEnumerable <WebResourceName> urls) { s_logger.Debug("Entered GetEntities."); WebResourceName firstResourceNameOrNull = null; var requestBody = @"<?xml version=""1.0"" encoding=""utf-8"" ?> <A:addressbook-multiget xmlns:D=""DAV:"" xmlns:A=""urn:ietf:params:xml:ns:carddav""> <D:prop> <D:getetag/> <D:getcontenttype/> <A:address-data/> </D:prop> " + String.Join("\r\n", urls.Select(u => { if (s_logger.IsDebugEnabled) { s_logger.Debug($"Requesting: '{u}'"); } if (firstResourceNameOrNull == null) { firstResourceNameOrNull = u; } return($"<D:href>{SecurityElement.Escape (u.OriginalAbsolutePath)}</D:href>"); })) + @" </A:addressbook-multiget> "; var responseXml = await _webDavClient.ExecuteWebDavRequestAndReadResponse( UriHelper.AlignServerUrl(_serverUrl, firstResourceNameOrNull), "REPORT", 0, null, null, "application/xml", requestBody ); XmlNodeList responseNodes = responseXml.XmlDocument.SelectNodes("/D:multistatus/D:response", responseXml.XmlNamespaceManager); var entities = new List <EntityWithId <WebResourceName, string> >(); if (responseNodes == null) { return(entities); } // ReSharper disable once LoopCanBeConvertedToQuery // ReSharper disable once PossibleNullReferenceException foreach (XmlElement responseElement in responseNodes) { var urlNode = responseElement.SelectSingleNode("D:href", responseXml.XmlNamespaceManager); var dataNode = responseElement.SelectSingleNode("D:propstat/D:prop/A:address-data", responseXml.XmlNamespaceManager); var contentTypeNode = responseElement.SelectSingleNode("D:propstat/D:prop/D:getcontenttype", responseXml.XmlNamespaceManager); string contentType = contentTypeNode?.InnerText ?? string.Empty; if (urlNode != null && dataNode != null && !string.IsNullOrEmpty(dataNode.InnerText) && _contentTypePredicate(contentType)) { if (s_logger.IsDebugEnabled) { s_logger.DebugFormat($"Got: '{urlNode.InnerText}'"); } entities.Add(EntityWithId.Create(new WebResourceName(urlNode.InnerText), dataNode.InnerText)); } } s_logger.Debug("Exiting GetEntities."); return(entities); }