Example #1
0
            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)));
                }
            }
        }
Example #6
0
        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);
        }
Example #7
0
        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));
        }
Example #9
0
        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));
        }
Example #11
0
        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);
        }
Example #12
0
        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);
        }