<commit_before>﻿// This file is Part of CalDavSynchronizer (http://outlookcaldavsynchronizer.sourceforge.net/)
// Copyright (c) 2015 <NAME>
// Copyright (c) 2015 <NAME>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
// 
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Security;
using System.Threading.Tasks;
using System.Xml;
using CalDavSynchronizer.Implementation.TimeRangeFiltering;
using CalDavSynchronizer.Ui.ConnectionTests;
using CalDavSynchronizer.Utilities;
using GenSync;
using log4net;

namespace CalDavSynchronizer.DataAccess
{
  public class CalDavDataAccess : WebDavDataAccess, ICalDavDataAccess
  {
    private static readonly ILog s_logger = LogManager.GetLogger (MethodInfo.GetCurrentMethod().DeclaringType);

    public CalDavDataAccess (Uri calendarUrl, IWebDavClient calDavWebClient)
        : base (calendarUrl, calDavWebClient)
    {
    }

    public Task<bool> IsCalendarAccessSupported ()
    {
      return HasOption ("calendar-access");
    }

    public Task<bool> IsCalendarProxySupported (Uri principalUrl)
    {
      return HasOption (principalUrl, "calendar-proxy");
    }

    public Task<bool> IsResourceCalender ()
    {
      return IsResourceType ("C", "calendar");
    }

    public Task<bool> DoesSupportCalendarQuery ()
    {
      return DoesSupportsReportSet (_serverUrl, 0, "C", "calendar-query");
    }

    public async Task<CalendarOwnerProperties> GetCalendarOwnerPropertiesOrNull()
    {
      return await GetCalendarOwnerPropertiesOrNull (_serverUrl);
    }

    private async Task<CalendarOwnerProperties> GetCalendarOwnerPropertiesOrNull (Uri resourceUri)
    {
      var owner = await GetOwnerUrlOrNull (resourceUri);
      var currentUserPrincipal = await GetCurrentUserPrincipalUrlOrNull (resourceUri);

      var isSharedCalendar = false;
      if (owner != null && currentUserPrincipal != null)
      {
        var ownerString = owner.OriginalString.EndsWith ("/") ? owner.OriginalString : owner.OriginalString + "/";
        var principalString = currentUserPrincipal.OriginalString.EndsWith ("/") ? currentUserPrincipal.OriginalString : currentUserPrincipal.OriginalString + "/";
        isSharedCalendar = StringComparer.InvariantCultureIgnoreCase.Compare (UriHelper.DecodeUrlString (ownerString), UriHelper.DecodeUrlString (principalString)) != 0;
      }
      
      var ownerPrincipalUrl = owner ?? currentUserPrincipal;

      if (ownerPrincipalUrl == null)
        return null;

      if (await GetUserEmailAddressOrNull(ownerPrincipalUrl) is var emailAddress && emailAddress != null)
        return new CalendarOwnerProperties(emailAddress, isSharedCalendar);

      return null;
    }

    public async Task<Uri> GetResourceUriOrNull(string displayName)
    {
      
      var resourceProperties = await ResourcePropSearchReport(displayName);

      var responseNodes = resourceProperties.XmlDocument.SelectNodes("/D:multistatus/D:response", resourceProperties.XmlNamespaceManager);

      if (responseNodes == null) return null;

      foreach (XmlElement responseElement in responseNodes)
      {
        var resourceIdNode = responseElement.SelectSingleNode("D:propstat/D:prop/D:resource-id", resourceProperties.XmlNamespaceManager);
        var calendarUsertypeNode = responseElement.SelectSingleNode("D:propstat/D:prop/C:calendar-user-type", resourceProperties.XmlNamespaceManager);
        if (resourceIdNode != null && calendarUsertypeNode != null)
        {
          if (!string.IsNullOrEmpty(calendarUsertypeNode.InnerText) && !string.IsNullOrEmpty(resourceIdNode.InnerText))
          {
            if (StringComparer.InvariantCultureIgnoreCase.Compare(calendarUsertypeNode.InnerText,"ROOM") == 0 ||
                StringComparer.InvariantCultureIgnoreCase.Compare(calendarUsertypeNode.InnerText,"RESOURCE") == 0 )
            {
              return new Uri(resourceIdNode.InnerText);
            }
          }
        }
      }

      return null;
    }

    private Task<XmlDocumentWithNamespaceManager> ResourcePropSearchReport(string displayName)
    { 
      return _webDavClient.ExecuteWebDavRequestAndReadResponse(
      _serverUrl,
      "REPORT",
      0,
      null,
      null,
      "application/xml",
      $@"<?xml version='1.0'?>
                        <D:principal-property-search xmlns:D=""DAV:"" xmlns:C=""urn:ietf:params:xml:ns:caldav"">
                            <D:property-search>
                                <D:prop>
                                    <D:displayname/>
                                </D:prop>
                                <D:match match-type=""equals"">{displayName}</D:match>
                            </D:property-search>
                            <D:prop>
                                <D:displayname/>
                                <D:resource-id/>
                                <C:calendar-user-type/>
                            </D:prop> 
                            <D:apply-to-principal-collection-set/>
                        </D:principal-property-search>
                 "
      );
  }<commit_msg>Use currentUserPrincipal and Ignore 400 errors, Google doesn't support principal-search-report.<commit_after>﻿// This file is Part of CalDavSynchronizer (http://outlookcaldavsynchronizer.sourceforge.net/)
// Copyright (c) 2015 <NAME>
// Copyright (c) 2015 <NAME>
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
// 
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Security;
using System.Threading.Tasks;
using System.Xml;
using CalDavSynchronizer.Implementation.TimeRangeFiltering;
using CalDavSynchronizer.Ui.ConnectionTests;
using CalDavSynchronizer.Utilities;
using GenSync;
using log4net;

namespace CalDavSynchronizer.DataAccess
{
  public class CalDavDataAccess : WebDavDataAccess, ICalDavDataAccess
  {
    private static readonly ILog s_logger = LogManager.GetLogger (MethodInfo.GetCurrentMethod().DeclaringType);

    public CalDavDataAccess (Uri calendarUrl, IWebDavClient calDavWebClient)
        : base (calendarUrl, calDavWebClient)
    {
    }

    public Task<bool> IsCalendarAccessSupported ()
    {
      return HasOption ("calendar-access");
    }

    public Task<bool> IsCalendarProxySupported (Uri principalUrl)
    {
      return HasOption (principalUrl, "calendar-proxy");
    }

    public Task<bool> IsResourceCalender ()
    {
      return IsResourceType ("C", "calendar");
    }

    public Task<bool> DoesSupportCalendarQuery ()
    {
      return DoesSupportsReportSet (_serverUrl, 0, "C", "calendar-query");
    }

    public async Task<CalendarOwnerProperties> GetCalendarOwnerPropertiesOrNull()
    {
      return await GetCalendarOwnerPropertiesOrNull (_serverUrl);
    }

    private async Task<CalendarOwnerProperties> GetCalendarOwnerPropertiesOrNull (Uri resourceUri)
    {
      var owner = await GetOwnerUrlOrNull (resourceUri);
      var currentUserPrincipal = await GetCurrentUserPrincipalUrlOrNull (resourceUri);

      var isSharedCalendar = false;
      if (owner != null && currentUserPrincipal != null)
      {
        var ownerString = owner.OriginalString.EndsWith ("/") ? owner.OriginalString : owner.OriginalString + "/";
        var principalString = currentUserPrincipal.OriginalString.EndsWith ("/") ? currentUserPrincipal.OriginalString : currentUserPrincipal.OriginalString + "/";
        isSharedCalendar = StringComparer.InvariantCultureIgnoreCase.Compare (UriHelper.DecodeUrlString (ownerString), UriHelper.DecodeUrlString (principalString)) != 0;
      }
      
      var ownerPrincipalUrl = owner ?? currentUserPrincipal;

      if (ownerPrincipalUrl == null)
        return null;

      if (await GetUserEmailAddressOrNull(ownerPrincipalUrl) is var emailAddress && emailAddress != null)
        return new CalendarOwnerProperties(emailAddress, isSharedCalendar);

      return null;
    }

    public async Task<Uri> GetResourceUriOrNull(string displayName)
    {
      XmlDocumentWithNamespaceManager resourceProperties;
      try
      {
        var currentUserPrincipal = await GetCurrentUserPrincipalUrlOrNull (_serverUrl);
        resourceProperties = await ResourcePropSearchReport(currentUserPrincipal ?? _serverUrl, displayName);
      }
      catch (Exception x)
      {
        s_logger.Info("Ignoring error, because some servers don't support principal-property-search reports.", x);
        return null;
      }
      var responseNodes = resourceProperties.XmlDocument.SelectNodes("/D:multistatus/D:response", resourceProperties.XmlNamespaceManager);

      if (responseNodes == null) return null;

      foreach (XmlElement responseElement in responseNodes)
      {
        var displayNameNode = responseElement.SelectSingleNode("D:propstat/D:prop/D:displayname", resourceProperties.XmlNamespaceManager);
        var resourceIdNode = responseElement.SelectSingleNode("D:propstat/D:prop/D:resource-id", resourceProperties.XmlNamespaceManager);
        var calendarUsertypeNode = responseElement.SelectSingleNode("D:propstat/D:prop/C:calendar-user-type", resourceProperties.XmlNamespaceManager);
       
        if ( !string.IsNullOrEmpty(displayNameNode?.InnerText) && 
             !string.IsNullOrEmpty(calendarUsertypeNode?.InnerText) && 
             !string.IsNullOrEmpty(resourceIdNode?.InnerText) )
        {
          if ( StringComparer.InvariantCultureIgnoreCase.Compare(displayNameNode.InnerText, displayName) == 0 &&
               ( StringComparer.InvariantCultureIgnoreCase.Compare(calendarUsertypeNode.InnerText, "ROOM") == 0 ||
                 StringComparer.InvariantCultureIgnoreCase.Compare(calendarUsertypeNode.InnerText, "RESOURCE") == 0 ))
          {
            return new Uri(resourceIdNode.InnerText);
          }
        }
      }
      return null;
    }

    private Task<XmlDocumentWithNamespaceManager> ResourcePropSearchReport(Uri principalUri, string displayName)
    { 
      return _webDavClient.ExecuteWebDavRequestAndReadResponse(
      principalUri,
      "REPORT",
      0,
      null,
      null,
      "application/xml",
      $@"<?xml version='1.0'?>
                        <D:principal-property-search xmlns:D=""DAV:"" xmlns:C=""urn:ietf:params:xml:ns:caldav"">
                            <D:property-search>
                                <D:prop>
                                    <D:displayname/>
                                </D:prop>
                                <D:match match-type=""equals"">{displayName}</D:match>
                            </D:property-search>
                            <D:prop>
                                <D:displayname/>
                                <D:resource-id/>
                                <C:calendar-user-type/>
                            </D:prop> 
                            <D:apply-to-principal-collection-set/>
                        </D:principal-property-search>
                 "
      );
  }
{'fraction_non_alphanumeric': 0.09236882653474981, 'fraction_numerical': 0.0030816640986132513, 'mean_word_length': 2.81441385709867, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 2, '>': 57, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7384639', 'n_tokens_mistral': 3283, 'n_tokens_neox': 3137, 'n_words': 748}