o
    .j~l                    @   sf  d Z ddlZddlZeh dZG dd dZi dddd	d
dddddddddddddddddddddd d!d"d#d$d%Zd&d'd(d)d*d+d,id-Zd+d.id+d,id/d0d1d+d,id+d.id+d,id'd(d)d*d+d,iddgd2	Zd3d4id+d&id+d,id5d/d6d1d7d/d8d9d:id+d,id+d,id+d&id;d5d5d<Z	e	d=d>d?d@dAdBdCd4dDdEdFdGB Z
i dHd3dIidJd/dKd1dLd+d,idMd+d,idNd+d,idOd+d,idPd+d&idQd+d/idRd+d,idSd+d,idTd5dUd+d&idVd+d,idWd+d/idXd+d,idYd,dZd5d+d,id+d;ig d[d\Zi d]d^d_d`dad+dbidcd+d.iddd+d.ided+d.idfd+d,idZd+d.idgd9d:idHd3dhidPd+d&idid+d&idjd+d,idXd+d,idkd=dld5dSd5dmd5d+d/id;dnd1d+d&id5d+d&id+dbid;dod1dd
gdpZd5d/d5d/d/dqd1d+d,id+d,idgd9d:id5d5drZd5d/d+d&id+d,id+d,id5d+d,id+d&id+d,id+d&iddgd5dsZedtdud1d+d,idvB Zd+d&id5d+d&id+d&id+d&id9d:id/d6d1d+d,idwZd+d,id5d+d/id+d,id+d/id+d,id+d,ig dxdyZi dZd5dfd5dUd5dzd5d{d5dPd+d&idRd+d,id|d+d,id}d+d,idXd+d,idWd+d&id~d+d/idQd+d/idd+d,idkd+d&idYd&dd1dgd9d:iZi dHddddPd+d&idJd+d,idRd+d,idld+d,idXd+d,idSd,dd1dWd+d&idVd+d,idd+d/idd+d,idYd+d&idkd+d&idd+d,idd"gdd+d,idgd9d:id+d&id+d,idZdEdd)dd+d,id+d,id+dbid+d&id5d+d,id+d,id+d,id+d&id+d,id+d,idZd5d+d,id+d,id5d+d,id+d&id+d,id+d&iddgd5d
Zi ddd+d&idfd=dad=dd+d,idPd+d,idNd+d,idLd+d&idid+d,idWd+d&id|d+d,idd+d.idzd+d&idld+d.id{d+d&idd+d&idXd+d,idmd+d,id+d,id9d:id+d&idgdZd=d_ddd=d+d&id5d/dd1d+d/id+d.id=d=d=dgdZd^dd`d+d.id+d,id+d/id+d,id+d/id+d/id+d&id+d&id+d&idddd+d,id+d,idZdEdddd=d>d?d@ddd/ddd+d,id+d,id+d&id+d&id+d,id+d&id+dbid+d.id+d,id+d,id+d,idgdZi dMd+d,idNd+d,idd+d.idd+d&idPd+d,idzd+d,id{d+d,idXd+d,idd+d;idWd+d&idSd+d,idUd+d,id|d+d,id}d+d,id~d+d,idd+d,idd+d,id+d,id+d&id+d,id+d,id+d,id+d&id+d;id+dbid&d;d=d,dZdS )aI  
This file serves as a database of different compatibility issues we've
encountered while working on the caldav library, and descriptions on
how the well-known servers behave.

TODO: it should probably be split with the "feature definitions",
"server implementation details" and "feature database logic" in three separate files.
    N>   fullquirkbrokenfragileunknown
ungracefulunsupportedc                   @   s  e Zd ZdZi dddidddddd	d
ddddidddgddddddddgddddidddddd d!d"d#d$d%d&d'd(id#d)d*d+dd,d-d.id/d0d1gd2d3d-d4id5d6d7dd8id9d:d;gdd<dd=id>i d?d@d-dAidBi dCddDidEddFidGdHd-d.idBdIdJd-d.idBdKddLidMddNidOddPidQdRd-d.idBdSdTd-d.idBdUdVd-d.idBdWddXidYdZd-d.idBd[dd\id]dd^id_dd`idaddbidcddd-d.idegdfi dgdhd-d.idBdidjdkgddlddmidndod-d.idBdpdqdrdsgddtdudsgddvdwd-d.idBdxddyidzd{d-d.idsgdfd|d}dsgdd~ddsgdddd-d.idsgdfddd-d.idsgdfddd-d.idBdddidddidddsgdi ddd-d.idBddd-d.idgdfdddgddddgddddgddddiddg ddddddgddddgdddddgddddidddidddrgddddiddd-d.idBdddidddii dddidddgddddidddidddidddgddddidddgdddddgd-d.idʜdddgddddgddddgd-d.idʜddd-d.iddgdfdddgddddgddddgddddiddiddiddiddii i ddid*ddddd#d*ddddd#d	ZdddZdddZdddZdd Zdd Z	dd Z
dd Zeddfdd Zed.dhZdddZdddZededefdd	Zed
d ZedefddZdddZdS (  
FeatureSeta  Work in progress ... TODO: write a better class description.

    This class holds the description of different behaviour observed in
    a class constant.

    An object of this class describes the feature set of a server.

    TODO: use enums?  TODO: describe the different types  TODO: think more through the different types, consolidate?
      type -> "client-feature", "client-hints", "server-peculiarity", "tests-behaviour", "server-observation", "server-feature" (last is default)
      support -> "full" (default), "unsupported", "fragile", "quirk", "broken", "ungraceful"

    unsupported means that attempts to use the feature will be silently ignored (this may actually be the worst option, as it may cause data loss).  quirk means that the feature is suppored, but special handling needs to be done towards the server.  fragile means that it sometimes works and sometimes not - either it's arbitrary, or we didn't spend enough time doing research into the patterns.  My idea behind broken was that the server should do completely unexpected things.  Probably a lot of things classified as "unsupported" today should rather be classified as "broken".  Some AI-generated code is using"broken".  TODO: look through and clean up.  "ungraceful" means the server will throw some error (this may indeed be the most graceful, as the client may catch the error and handle it in the best possible way).

    types:
     * client-feature means the client is supposed to do special things (like, rate-limiting).  While the need for rate-limiting may be set by the server, it may not be possible to reliably establish it by probling the server, and the value may differ for different clients.
     * server-peculiarity - weird behaviour detected at the server side, behaviour that is too odd to be described as "missing support for a feature".  Example: there is some cache working, causing a delay from some object is sent to the server and until it can be retrieved.  The difference between an "unsupported server-feature" and a "server-peculiarity" may be a bit floating - like, arguably "instant updates" may be considered a feature.
     * tests-behaviour - configuration for the tests.  Like, it's OK to wipe everyhting from the test calendar, location of test calendar, rate-limiting that only should apply to test runs, etc.
     * server-observation - not features, but other facts found about the server
     * server-feature - some feature (preferably rooted with a pointer to some specific section of the RFC)
       * "support" -> "quirk" if we have a server-peculiarity where it's needed with special care to get the request through.

    IMPORTANT NOTE: The dotted format sort of represents a hierarchy - say, one may have foo.bar and foo.zoo.  If foo has an explicit default given in the FEATURES below, it will be considered an independent feature, otherwise it will be considered to only exist to group bar and zoo together.  This matters if bar and zoo is not supported.  Without an explicit default below, the default for foo will also be "unsupported".
    zauto-connecttypeclient-hintsauto-connect.urlzInstruction for how to access DAV.  I.e. `/remote.php/dav` - see also https://github.com/python-caldav/caldav/issues/463.  To be used in the get_davclient method if the URL only contains a domainz The path to append to the domainzUDomain name may be given through the features - useful for well-known cloud solutionsz7The scheme to prepend to the domain.  Defaults to httpsbasepathdomainscheme)descriptionr
   
extra_keysurlzget-current-user-principalzSupport for RFC5397, current principal extension.  Most CalDAV servers have this, but it is an extension to the DAV standard.  Possibly observed missing on mail.ru, DavMail gateway and it is possible to configure the support in some sabre-based serversz-https://datatracker.ietf.org/doc/html/rfc5397)r   linksz'get-current-user-principal.has-calendarserver-observationa  Principal has one or more calendars.  Some servers and providers comes with a pre-defined calendar for each user, for other servers a calendar has to be explicitly created (supported means there exists a calendar - it may be because the calendar was already provisioned together with the principal, or it may be because a calendar was created manually, the checks can't see the difference))r
   r   get-supported-componentsa1  Server returns the supported-calendar-component-set property (RFC 4791 section 5.2.3).  The property is optional: when absent the RFC mandates that all component types are accepted, so 'unsupported' here is not a protocol violation, but the client cannot determine the actual supported set without trying.z;https://datatracker.ietf.org/doc/html/rfc4791#section-5.2.3z.create-calendar.with-supported-component-typesr   ae  Server honours the supported-calendar-component-set restriction set at MKCALENDAR time.  When 'full', the server both advertises (or enforces) the restriction; when 'unsupported', the restriction is silently ignored (wrong-type objects can be saved to the calendar).  When 'ungraceful', the MKCALENDAR request itself fails when a component set is specified.
rate-limitclient-featurezclient (or test code) must sleep a bit between requests.  Pro-active rate limiting is done through interval and count, server-flagged rate-limiting is controlled through default_sleep/max_sleepz Rate limiting window, in secondsz2Max number of requests to send within the intervalz@Max sleep when hitting a 429 or 503 with retry-after, in secondsz2Sleep for this long when hitting a 429, in seconds)intervalcount	max_sleepdefault_sleep)r
   r   r   search-cacheserver-peculiarityzThe server delivers search results from a cache which is not immediately updated when an object is changed.  Hence recent changes may not be reflected in search resultsdelayz[after this number of seconds, we may be reasonably sure that the search results are updatedztests-cleanup-calendartests-behaviourzDeleting a calendar does not delete the objects, or perhaps create/delete of calendars does not work at all.  For each test run, every calendar resource object should be deleted for every test runcreate-calendarsupportr   aI  RFC4791 section 5.3.1 says that "support for MKCALENDAR on the server is only RECOMMENDED and not REQUIRED because some calendar stores only support one calendar per user (or principal), and those are typically pre-created for each account".  Hence a conformant server may opt to not support creating calendars, this is often seen for cloud services (some services allows extra calendars to be made, but not through the CalDAV protocol).  (RFC5689 extended MKCOL may also be used to create calendar collections as an alternative to MKCALENDAR.  We should consider testing this as well)z;https://datatracker.ietf.org/doc/html/rfc4791#section-5.3.1z-https://datatracker.ietf.org/doc/html/rfc5689)defaultr   r   create-calendar.autor   zBAccessing a calendar which does not exist automatically creates it)r#   r   create-calendar.set-displaynamez@It's possible to set the displayname on a calendar upon creationdelete-calendara  RFC4791 says nothing about deletion of calendars, so the server implementation is free to choose weather this should be supported or not.  Section 3.2.3.2 in RFC 6638 says that if a calendar is deleted, all the calendarobjectresources on the calendar should also be deleted - but it's a bit unclear if this only applies to scheduling objects or not.  Some calendar servers moves the object to a trashcan rather than deleting itz=https://datatracker.ietf.org/doc/html/rfc6638#section-3.2.3.2delete-calendar.free-namespaceziThe delete operations clears the namespace, so that another calendar with the same ID/name can be createdhttphttp.multiplexingaD  chulka/baikal:nginx is having Problems with using HTTP/2 with multiplexing, ref https://github.com/python-caldav/caldav/issues/564.  I haven't (yet) been able to reproduce this locally, so no check for this yet.  Due to caution and friendly advice from the niquests team, the default now is to NOT support http multiplexing.r   )r   r#   z	save-loadz6it's possible to save and load objects to the calendarzsave-load.eventz5it's possible to save and load events to the calendarzsave-load.event.recurrencesz~it's possible to save and load recurring events to the calendar - events with an RRULE property set, including recurrence setsz!save-load.event.recurrences.countzQThe server will receive and store a recurring event with a count set in the RRULE%save-load.event.recurrences.exceptionan  When a VCALENDAR containing a master VEVENT (with RRULE) and exception VEVENT(s) (with RECURRENCE-ID) is stored, the server keeps them together as a single calendar object resource. When unsupported, the server splits exception VEVENTs into separate calendar objects, making client-side expansion unreliable (the master expands without knowing about its exceptions).save-load.todoz4it's possible to save and load tasks to the calendarsave-load.todo.recurrencesz>it's possible to save and load recurring tasks to the calendar save-load.todo.recurrences.countzPThe server will receive and store a recurring task with a count set in the RRULE(save-load.todo.recurrences.thisandfuturezkCompleting a recurring task with rrule_mode='thisandfuture' works (modifies RRULE and saves back to server)save-load.todo.mixed-calendarzqThe same calendar may contain both events and tasks (Zimbra only allows tasks to be placed on special task lists)save-load.journalz$The server will even accept journals save-load.journal.mixed-calendarzyThe same calendar may contain events, tasks and journals (some servers require journals on a dedicated VJOURNAL calendar)zsave-load.get-by-urlzGET requests to calendar object resource URLs work correctly. When unsupported, the server returns 404 on GET even for valid object URLs. The client works around this by falling back to UID-based lookup.zsave-load.reuse-deleted-uida  After deleting an event, the server allows creating a new event with the same UID. When 'broken', the server keeps deleted events in a trashbin with a soft-delete flag, causing unique constraint violations on UID reuse. See https://github.com/nextcloud/server/issues/30096zsave-load.event.timezonezThe server accepts events with non-UTC timezone information. When unsupported or broken, the server may reject events with timezone data (e.g., return 403 Forbidden). Related to GitHub issue https://github.com/python-caldav/caldav/issues/372.zsave-load.icalendarzIs it possible to save icalendar data to the calendar?  (Most likely yes - but we need a parent to collect all icalendar compatibility problems that aren't specific to one kind of object resource typessave-load.icalendar.related-tozThe server preserves RELATED-TO properties (RFC5545 section 3.8.4.5) when saving and loading calendar objects. When 'unsupported', the server may typically silently strip all RELATED-TO linesz=https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.4.5)r   r#   r   zsave-load.mutablea[  A saved calendar object resource can be modified and PUT back to the server; the server accepts the update and returns the modified data on the next GET/REPORT. When 'unsupported', the server treats calendar objects as immutable after initial creation (e.g. Google Calendar's legacy CalDAV API). Replaces the old 'no_overwrite' compatibility flag.searchzgcalendar MUST support searching for objects using the REPORT method, as specified in RFC4791, section 7z7https://datatracker.ietf.org/doc/html/rfc4791#section-7search.comp-type.optionala`  In all the search examples in the RFC, comptype is given during a search, the client specifies if it's event or tasks or journals that is wanted.  However, as I read the RFC this is not required.  If omitted, the server should deliver all objects.  Many servers will not return anything if the COMPTYPE filter is not set.  Other servers will return 404search.comp-typezServer correctly filters calendar-query results by component type. When 'broken', server may misclassify component types (e.g., returning TODOs when VEVENTs are requested). The library will perform client-side filtering to work around this issuezsearch.time-rangezfSearch for time or date ranges should work.  This is specified in RFC4791, section 7.4 and section 9.9z9https://datatracker.ietf.org/doc/html/rfc4791#section-7.4z9https://datatracker.ietf.org/doc/html/rfc4791#section-9.9zsearch.time-range.accuratea  Time-range searches should only return events/todos that actually fall within the requested time range. Some servers incorrectly return recurring events whose recurrences fall outside (after) the search interval, or events with no recurrences in the requested time range at all. RFC4791 section 9.9 specifies that a VEVENT component overlaps a time range if the condition (start < search_end AND end > search_start) is true.search.time-range.todoz)basic time range searches for tasks works search.time-range.todo.old-dateszutime range searches for tasks with old dates (e.g. year 2000) work - some servers enforce a min-date-time restrictionzsearch.time-range.todo.strictzBounded VTODO time-range searches do not return tasks whose time span falls entirely outside the searched range (no false positives).search.time-range.openzOpen-ended time-range searches (with only one bound) work correctly. RFC4791 section 9.9: the CALDAV:time-range 'start' and 'end' attributes are optional; if absent, assume -infinity and +infinity respectively. At least one attribute must be present.search.time-range.open.enda2  Searches with only a start bound (end assumed +infinity) correctly return components whose time span overlaps the start. RFC4791 section 9.9: for a VTODO with DTSTART+DUE and absent end bound, the overlap condition is (start < DUE) OR (start <= DTSTART). When 'unsupported', such queries return no results.search.time-range.open.starta  Searches with only an end bound (start assumed -infinity) correctly exclude components whose DTSTART is after the end bound. RFC4791 section 9.9: a VTODO with DTSTART+DUE should not overlap if its DTSTART > search_end. When 'broken', the server incorrectly returns future tasks.%search.time-range.open.start.durationa  Time-range searches correctly handle components that specify their interval via DTSTART+DURATION (without DTEND/DUE). RFC4791 section 9.9: a VEVENT with DURATION (end > 0s) overlaps [start, end] if (start < DTSTART+DURATION) AND (end > DTSTART); a VTODO with DTSTART+DURATION overlaps if (start <= DTSTART+DURATION) AND ((end > DTSTART) OR (end >= DTSTART+DURATION)). Tested for both VTODO and VEVENT; if support is asymmetric across component types the feature is marked 'broken' with a behaviour note.search.time-range.eventz)basic time range searches for event works!search.time-range.event.old-dateszvtime range searches for events with old dates (e.g. year 2000) work - some servers enforce a min-date-time restrictionzsearch.time-range.journalz+basic time range searches for journal workssearch.time-range.alarmzTime range searches for alarms work. The server supports searching for events based on when their alarms trigger, as specified in RFC4791 section 9.9search.unlimited-time-rangeu7  A REPORT without a time-range filter should return all matching objects regardless of when they occur. Some servers (e.g. OX App Suite) use a sliding window for REPORT requests without a time range, returning only objects within approximately ±1 year of now and potentially missing older or far-future objects.search.is-not-definedzaSupports searching for objects where properties is-not-defined according to rfc4791 section 9.7.4z;https://datatracker.ietf.org/doc/html/rfc4791#section-9.7.4zsearch.is-not-defined.categoryzSupports searching for objects where the CATEGORIES property is not defined (RFC4791 section 9.7.4). Some servers support is-not-defined for other properties (e.g. CLASS) but silently return wrong results or nothing when applied to CATEGORIESsearch.is-not-defined.dtendzSupports searching for objects where the DTEND property is not defined (RFC4791 section 9.7.4). Some servers support is-not-defined for some properties but not DTENDzsearch.is-not-defined.classzSupports searching for objects where the CLASS property is not defined (RFC4791 section 9.7.4). Some servers support is-not-defined for CLASS but not for other properties like CATEGORIESsearch.textz&Search for text attributes should worksearch.text.case-sensitivea  In RFC4791, section-9.7.5, a text-match may pass a collation, and i;ascii-casemap MUST be the default, this is not checked (yet - TODO) by the caldav-server-checker project.  Section 7.5 describes that the servers also are REQUIRED to support i;octet.  The definitions of those collations are given in RFC4790, i;octet is a case-sensitive byte-by-byte comparition (fastest).  search.text.case-sensitive is supported if passing the i;octet collation to search causes the search to be case-sensitive.);https://datatracker.ietf.org/doc/html/rfc4791#section-9.7.5z9https://datatracker.ietf.org/doc/html/rfc4791#section-7.5z-https://datatracker.ietf.org/doc/html/rfc4790search.text.case-insensitivea  The i;ascii-casemap requires ascii-characters to be case-insensitive, while non-ascii characters are compared byte-by-byte (case-sensitive).  Proper unicode case-insensitive searches may be supported by the server, but it's not a requirement in the RFC.  As for now, we consider case-insensitive searches to be supported if the i;ascii-casemap collation does what it's supposed to do..  In the future we may consider adding a search.text.case-insensitive.unicode. (i;unicode-casemap is defined in RFC5051)rD   z-https://datatracker.ietf.org/doc/html/rfc5051zsearch.text.substringa  According to RFC4791 the search done should be a substring search.  The search.text.substring feature is set if the calendar server does this (as opposed to only return full matches).  Substring matches does not always make sense, but it's mandated by the RFC.  When a server does a substring match on some properties but an exact match on others, the support should be marked as fragile.  Except for categories, which are handled in search.text.category.substringsearch.text.categoryzSearch for category should work.  This is not explicitly specified in RFC4791, but covered in section 9.7.5.  No examples targets categories explicitly, but there are some text match examples in section 7.8.6 and following sectionsz;https://datatracker.ietf.org/doc/html/rfc4791#section-7.8.6zsearch.text.category.substringzqSubstring search for category should work according to the RFC.  I.e., search for mil should match family,financesearch.recurrencesz!Support for recurrences in search$search.recurrences.includes-implicitzRFC 4791, section 7.4 says that the server MUST expand recurring components to determine whether any recurrence instances overlap the specified time range.  Considered supported i.e. if a search for 2005 yields a yearly event happening first time in 2004.)search.recurrences.includes-implicit.todoztasks can also be recurring1search.recurrences.includes-implicit.todo.pendingzga future recurrence of a pending task should always be pending and appear in searches for pending tasks*search.recurrences.includes-implicit.eventzsupport for events3search.recurrences.includes-implicit.infinite-scopezxNeedless to say, search on any future date range, no matter how far out in the future, should yield the recurring objectsearch.combined-is-logical-andzGMultiple search filters should yield only those that passes all filterssearch.recurrences.expandedaL  According to RFC 4791, the server MUST expand recurrence objects if asked for it - but many server doesn't do that.  Some servers don't do expand at all, others deliver broken data, typically missing RECURRENCE-ID.  The python caldav client library (from 2.0) does the expand-operation client-side no matter if it's supported or notz;https://datatracker.ietf.org/doc/html/rfc4791#section-9.6.5 search.recurrences.expanded.todozexpanding tasks!search.recurrences.expanded.eventzexanding events%search.recurrences.expanded.exceptionzUServer expand should work correctly also if a recurrence set with exceptions is given
sync-tokena^  RFC6578 sync-collection reports are supported. Server provides sync tokens that can be used to efficiently retrieve only changed objects since last sync. Support can be 'full', 'fragile' (occasionally returns more content than expected), or 'unsupported'. Behaviour 'time-based' indicates second-precision tokens requiring sleep(1) between operationsz-https://datatracker.ietf.org/doc/html/rfc6578zsync-token.deletezServer correctly handles sync-collection reports after objects have been deleted from the calendar (solved in Nextcloud in https://github.com/nextcloud/server/pull/44130)
schedulingz~Server supports CalDAV Scheduling (RFC6638). Detected via the presence of 'calendar-auto-schedule' in the DAV response header.z-https://datatracker.ietf.org/doc/html/rfc6638scheduling.mailboxzServer provides schedule-inbox and schedule-outbox collections for the principal (RFC6638 sections 2.1-2.2). When unsupported, calls to schedule_inbox() or schedule_outbox() raise NotFoundError.z9https://datatracker.ietf.org/doc/html/rfc6638#section-2.1z9https://datatracker.ietf.org/doc/html/rfc6638#section-2.2)r   r   r#   $scheduling.calendar-user-address-setzServer provides the calendar-user-address-set property on the principal (RFC6638 section 2.4.1), used to identify a user's email/URI for scheduling purposes. When unsupported, calendar_user_address_set() raises NotFoundError.z;https://datatracker.ietf.org/doc/html/rfc6638#section-2.4.1!scheduling.mailbox.inbox-deliveryzServer delivers incoming scheduling REQUEST messages to the attendee's schedule-inbox (RFC6638 section 4.1). See also scheduling.auto-schedule for whether the server additionally auto-processes invitations into the attendee's calendar.z9https://datatracker.ietf.org/doc/html/rfc6638#section-4.1scheduling.auto-schedulea  Server automatically processes incoming iTIP REQUEST messages and adds the event directly to the attendee's calendar without requiring explicit acceptance from the inbox (RFC6638 SCHEDULE-AGENT=SERVER behaviour). When False/unsupported, the attendee must process inbox items manually. Note: only detectable from the caldav-server-tester with a cross-user probe (extra_principals configured).scheduling.schedule-taga9  Server returns a Schedule-Tag response header on GET of a scheduling object resource (a calendar object with an ORGANIZER property) and exposes the schedule-tag DAV property via PROPFIND (RFC6638 sections 3.2-3.3). Clients use the Schedule-Tag for conditional PUT requests to detect concurrent scheduling changes.z9https://datatracker.ietf.org/doc/html/rfc6638#section-3.2z9https://datatracker.ietf.org/doc/html/rfc6638#section-3.3'scheduling.schedule-tag.stable-partstatzServer keeps the Schedule-Tag stable when an attendee performs a PARTSTAT-only update (RFC6638 section 3.2 requirement). Non-compliant servers change the tag even when only PARTSTAT is updated, breaking conditional-PUT logic for other attendees.scheduling.freebusy-queryzServer supports the RFC6638 freebusy query: the organizer POSTs a VFREEBUSY REQUEST to the schedule outbox and the server returns free/busy information for the listed attendees.z7https://datatracker.ietf.org/doc/html/rfc6638#section-5freebusy-querya  Server supports the RFC4791 free/busy-query REPORT (section 7.10): a REPORT sent directly to a calendar collection to retrieve free/busy time for a range. See also scheduling.freebusy-query for the RFC6638 variant which POSTs a VFREEBUSY to the schedule outbox.z:https://datatracker.ietf.org/doc/html/rfc4791#section-7.10principal-searchzServer supports searching for principals (CalDAV users). Principal search may be restricted for privacy/security reasons on many servers.  (not to be confused with get-current-user-principal)zServer supports searching for principals by display name. Testing this properly requires setting up another user with a known name, so this check is not yet implementedzServer allows searching for own principal by display name. Some servers block this for privacy reasons even when general principal search workszfServer allows listing all principals without a name filter. Often blocked for privacy/security reasonszServer rejects requests with wrong password by returning an authorization error. Some servers may not properly reject wrong passwords in certain configurations.a  Server allows events with the same UID to exist in different calendars and treats them as separate entities. Support can be 'full' (allowed), 'ungraceful' (rejected with error), or 'unsupported' (silently ignored or moved). Behaviour 'silently-ignored' means the duplicate is not saved but no error is thrown. Behaviour 'moved-instead-of-copied' means the event is moved from the original calendar to the new calendar (Zimbra behavior)a,  if the server does not allow creating new calendars, then use the calendar with the given name for running tests (NOT SUPPORTED YET!), wipe the calendar between each test run (alternative for calendars not supporting the creation of new calendars is a very expensive delete objects one-by-one by uid)zcalendar namez%thorough|pre|post|light|wipe-calendar)namecleanup-regimezif the server does not allow creating new calendars, then use the calendar with the given name for running the compatibility testsz<Set to True to clean up the calendar after compatibility run)r]   cleanup)	zprincipal-search.by-nameprincipal-search.by-name.selfprincipal-search.list-allzwrong-password-checksavezsave.duplicate-uid!save.duplicate-uid.cross-calendartest-calendarz!test-calendar.compatibility-testsNc                 C   sv   t |tr#t|j| _|j| _t|drt|j| _dS g | _dS |du | _i | _g | _|r9| j|dd dS dS )ax  
        TODO: describe the feature_set better.

        Should be a dict on the same style as self.FEATURES, but different.

        Shortcuts accepted in the dict, like:

        {
            "recurrences.search-includes-implicit-recurrences.infinite-scope":
                "unsupported" }

        is equivalent with

        {
           "recurrences": {
               "features": {
                   "search-includes-inplicit-recurrences": {
                       "infinite-scope":
                           "support": "unsupported" }}}}

        (TODO: is this sane?  Am I reinventing a configuration language?)
        
_old_flagsNFcollapse)	
isinstancer	   copydeepcopy_server_featuresbackward_compatibility_modehasattrre   copyFeatureSet)selffeature_set_dict rq   O/home/thesage/.local/lib/python3.10/site-packages/caldav/compatibility_hints.py__init__  s   

zFeatureSet.__init__Tc                 C   s   t |tr
||i}n/t |tr|d|ii}n#|du r!|ddii}n|du r,|ddii}n|d u r7|ddii}nt| j|dd d S )Nr"   Tr   Fr   r   rf   )rh   dictstrAssertionErrorrn   )ro   featurevaluefcrq   rq   rr   set_feature  s   


zFeatureSet.set_featurec              
   C   s
  |D ]x}|dkr|| | _ qz| |}W n ttfy,   tjd| dtdd Y nw || }|| jvr;i | j|< | j| }t|t	rN|rIdnd|d< qt|t
rbd|vrb| || ||d< qt|tryd|v rs| |d | || qt|r|   d S d S )	N	old_flagszUnknown feature 'za' in configuration. This might be a typo. Check caldav/compatibility_hints.py for valid features.   
stacklevelr   r   r"   )re   find_featurerv   KeyErrorwarningswarnUserWarningrk   rh   boolru   _validate_support_levelrt   updaterg   )ro   feature_setrg   rw   feature_inforx   server_noderq   rq   rr   rn     s<   








zFeatureSet.copyFeatureSetc              
   C   s<   |t vrtjd| d| ddtt  tdd dS dS )z4Validate that a support level is valid, warn if not.z	Feature 'z' has invalid support level 'z'. Valid levels: z,    r}   N)VALID_SUPPORT_LEVELSr   r   joinsortedr   )ro   levelfeature_namerq   rq   rr   r     s   
z"FeatureSet._validate_support_levelc                 C   s*   t |ts|S |d|d|dfS )ah  
        Extract the key part of a feature dictionary for comparison during collapse.

        For collapse purposes, we compare the 'support' level (or 'enable', 'behaviour', 'observed')
        but ignore differences in detailed behaviour messages, as those are often implementation-specific
        error messages that shouldn't prevent collapsing.
        r"   enableobserved)rh   rt   get)ro   feature_dictrq   rq   rr   _collapse_key  s   
zFeatureSet._collapse_keyc                 C   sf  t | j }t }|D ]}d|v r||d|d  qt |}|jdd d |D ]}| |}t|d r| j	|t
dd}t|d d	ksN|durd}|durY| |nd}|d D ]+}	| j| d|	 }
|
du rtd
} n| |
}|du r|
}|}q_||krd
} nq_|s|| jvri | j|< |d D ]}	| j| d|	  q| ||i q-dS )z~
        If all subfeatures are the same, it should be collapsed into the parent

        Messy and complex logic :-(
        .Nc                 S   s   |  d | fS Nr   )r   )xrq   rq   rr   <lambda>+  s    z%FeatureSet.collapse.<locals>.<lambda>)keysubfeaturesF)return_typereturn_defaults   T)listrk   keyssetaddrfindsortr   lenis_supportedrt   r   r   poprn   )ro   featuresparentsrw   parentparent_infofoodont_collapsefoo_keysubbarbar_keyrq   rq   rr   rg     sH   



zFeatureSet.collapsec                 C   s   t |tr
| |}d|v r|d S |dd}|dkr ddiS |dkr(ddiS |d	kr0d
diS |dkr8ddiS |dv r>i S td|)Nr#   r
   server-featurer"   r   r   r   Fr   	behaviournormalr   r   T)r    r   zUnknown feature type: )rh   ru   r   r   
ValueError)ro   r   feature_typerq   rq   rr   _defaultG  s    

zFeatureSet._defaultFc           	      C   s   |  |}|}	 || jv r| | j| |||S ||kr|n|  |}d|vr5| ||||}|dur5|S d|vrZ|s=dS d|vrO| ||||}|durO|S | | ||||S |d|d }q)a
  Work in progress

        TODO: write a better docstring

        The dotted features is essentially a tree.  If feature foo
        is unsupported it basically means that feature foo.bar is also
        unsupported.  Hence the extra logic visiting "nodes".
        Tr#   Nr   )r   rk   _convert_node_derive_from_subfeaturesr   r   )	ro   rw   r   r   accept_fragiler   feature_current_infoderivedrq   rq   rr   r   [  s(   
	
zFeatureSet.is_supportedr   c                    sB  d|vs|d s
dS d}g |d D ]F}| d| }z  |}d|v r'W qW n	 ty1   Y nw |d7 }| jv rX j| }	|	d|	d|	d	|	d
}
|
rX|
 qs]dS t fddD }tfddD }t|k}|r|rd }nd}n|r|rd }n|rd}ndS d|i} ||||S )u5  
        Derive parent feature status from explicitly set subfeatures.

        Logic:
        - Only consider subfeatures WITHOUT explicit defaults (those are independent features)
        - If ANY relevant subfeature has a positive status (full/quirk) → derive as that status
          (any support means the parent has some support)
        - If ALL relevant subfeatures are set AND all have the same negative status → use that status
        - If only a PARTIAL set of subfeatures is configured with all negative statuses →
          return None (incomplete information, fall through to default)
        - Mixed statuses (some positive, some negative) → "unknown"

        Returns None if no relevant subfeatures are explicitly set or if
        derivation is inconclusive due to partial information.
        r   Nr   r   r#   r   r"   r   r   r   c                 3   s    | ]}| j v V  qd S N)_POSITIVE_STATUSES.0s)ro   rq   rr   	<genexpr>  s    z6FeatureSet._derive_from_subfeatures.<locals>.<genexpr>c                 3   s    | ]	}| d  kV  qdS )r   Nrq   r   )subfeature_statusesrq   rr   r     s    r   )	r   	Exceptionrk   r   appendanyallr   r   )ro   rw   r   r   r   total_relevantr   subfeature_keysubfeature_infosub_dictstatushas_positiveall_sameis_completederived_statusderived_noderq   )ro   r   rr   r     sJ   


"


z#FeatureSet._derive_from_subfeaturesc              	   C   s   |t u r|d|d|dS |tu r|S |tu rM|dd}|dkr'dS |r/|dkr/d}|dd	d	kr;|dkS |d oL|d oL|d
 S t)a?  
        Return the information in a "node" given the wished return_type

        (The dotted feature format was an afterthought, the first
        iteration of this code the feature tree was actually a
        hierarchical dict, hence the naming of the method.  I
        considered it too complicated though)
        r"   r   r   r   r   Tr   r
   r   r   )ru   r   rt   r   rv   )ro   noder   r   r   r"   rq   rq   rr   r     s   	$zFeatureSet._convert_noderw   returnc                 C   s   || j v sJ d| j | vr|| j | d< d|v r1d| j | vr1| |d|d | j | d< d| j | vrO|  }|dD ]}|| }qA|| j | d< | j | S )a$  
        Feature should be a string like feature.subfeature.subsubfeature.

        Looks through the FEATURES list and returns the relevant section.

        Will raise an Error if feature is not found

        (this is very simple now - used to be a hierarchy dict to be traversed)
        r]   r   r   Nr   )FEATURESr   r   feature_treesplit)clsrw   treer   rq   rq   rr   r     s   "

zFeatureSet.find_featurec                 C   s>   |D ]}|}| d}|D ]}||vri ||< || }qq|S r   )r   )r   targetsourcefeatr   pathpartrq   rq   rr   _dots_to_tree  s   

zFeatureSet._dots_to_treec                 C   s,   t | dr| jS i | _| | j| j | jS )a   TODO: is this in use at all?  Can it be deprecated already?

        TODO: the description may be outdated as I decided to refactor
        things from "overly complex" to "just sufficiently complex".
        Or maybe it's still a bit too complex.

        A "path" may have several "subpaths" in self.FEATURES
        (i.e. feat.subfeat.A, feat.subfeat.B, feat.subfeat.C)

        This method will return `{'feat': { 'subfeat': {'A': {}, ...}}}`
        making it possible to traverse the feature tree

        _feature_tree)rm   r   r   r   )r   rq   rq   rr   r     s
   
zFeatureSet.feature_treec                 C   sJ   i }|r|    | jD ]}| j| }|r|| |krq| ||< q|S r   )rg   rk   r   ri   )ro   compactretr   rw   rq   rq   rr   dotted_feature_set_list  s   

z"FeatureSet.dotted_feature_set_listr   )T)F)__name__
__module____qualname____doc__r   rs   rz   rn   r   r   rg   r   r   r   	frozensetr   r   r   classmethodru   rt   r   r   r   r   rq   rq   rq   rr   r	   '   sd   "+26>BEILMQTUV`abcdefhilorsx|     
               $  )  .  /  0  1  5  9  >  B  F  J  M  U  \  `  g  j  m  q  t  x  {  ~           
                        #    '    -    4    <    @    D    J    N    k(!
)#F
r	   calendar_orderz8Server supports (nonstandard) calendar ordering propertycalendar_colorz5Server supports (nonstandard) calendar color propertyduplicates_not_allowedzRDuplication of an event in the same calendar not allowed (even with different uid)event_by_url_is_brokenzHA GET towards a valid calendar object resource URL will yield 404 (wtf?)propfind_allprop_failurez{The propfind test fails ... it asserts DAV:allprop response contains the text 'resourcetype', possibly this assert is wrong*vtodo_datesearch_nodtstart_task_is_skippedzBdate searches for todo-items will not find tasks without a dtstart?vtodo_datesearch_nodtstart_task_is_skipped_in_closed_date_rangezNonly open-ended date searches for todo-items will find tasks without a dtstart'vtodo_datesearch_notime_task_is_skippedzXdate searches for todo-items will (only) find tasks that has either a dtstart or due setvtodo_no_due_infinite_durationzdate search will find todo-items without due if dtstart is before the date search interval.  This is in breach of rfc4791section 9.9zvtodo-cannot-be-uncompletedzIf a VTODO object has been set with STATUS:COMPLETE, it's not possible to delete the COMPLTEDED attribute and change back to STATUS:IN-ACTIONsticky_eventszqEvents should be deleted before the calendar is deleted, and/or deleting a calendar may not have immediate effectno_overwritezevents cannot be editeddav_not_supportedzwhen asked, the server may claim it doesn't support the DAV protocol.  Observed by one baikal server, should be investigated more (TODO) and robur#fastmail_buggy_noexpand_date_searchzThe 'blissful anniversary' recurrent example event is returned when asked for a no-expand date search for some timestamps covering a completely different datenon_existing_raises_otherzRobur raises AuthorizationError when trying to access a non-existing resource (while 404 is expected).  Probably so one shouldn't probe a public name space?'robur_rrule_freq_yearly_expands_monthlyzRobur expands a yearly event into a monthly event.  I believe I've reported this one upstream at some point, but can't find back to itr   	localhostr(   /)r   r   r   r"   r   )r\   r   rS   r   r   z!inconsistent results between runs)r"   r   )	r@   rC   rJ   rO   rQ   r\   r   rS   r{   r   z/remote.php/davFz*Deleting a recently created calendar failszdeleting a calendar moves it to a trashbin, thrashbin has to be manually 'emptied' from the web-ui before the namespace is freed up)r   r"   r^   zwipe-calendarr   )r   r4   rO   rL   r&   r'   rd   rI   r`   r\   r;   rM   rX   T   r   r   x   zIt's needed to manually empty trashbin frequently when running tests.  Since this operation takes some time and/or there are some caches, it's needed to run tests slowly, even when hammering the 'empty thrashbin' frequently)r   r   r   r   r   r   zecloud.globalhttpsr   )r   r   r   z/dav/r&   z4may move to trashbin instead of deleting immediatelyrc   r%   r/   r-   r0   rR   r@   rB   rL   rF   rO   r4   r>   r\   rX   )r   r   r   )r2   r:   r{   r   r   r|   )r   r   rW   r   rU   rZ   rT   rV   rd   z	/ucaldav/r.   r*   r[   r6   rG   zXServer returns everything when searching for events and nothing when searching for todoszOfirst RELATED-TO line is preserved but subsequent RELATED-TO lines are stripped)rR   r5   r4   rA   r\   rS   r2   r{   z&works for CLASS but not for CATEGORIES)r\   rR   r&   r4   r@   rC   r>   r{   rd   rX   rV   )rX   r)   r4   rO   rI   rL   r1   r\   r`   ra   r{   rM   r   zmkcol-required)r!   r$   )r4   rL   r>   r\   rc   rd   r&   rY   )r   r   r   r   )r)   rX   r4   r>   rR   r\   ra   r{   r=   r7   rC   rE   rH   rN   a  Search by name failed: ReportError at '501 Not Implemented - <?xml version="1.0" encoding="ISO-8859-1"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<body><h3>An error occurred during object publishing</h3><p>did not find the specified REPORT</p></body>
</html>
', reason no reasonzcalendar.robur.coopz/principals/)r   r   z9a text search ignores the filter and returns all elementsrP   rI   rS   r{   r2   )rR   r   zposteo.de:8443)r   r   r   )r   r!   r0   rc   r4   rL   rO   rI   rM   rR   r\   rS   )
rX   rO   rI   rL   r`   r\   r1   r4   r{   rM   rY   r<   r8   )r\   rd   r[   r{   <   )r   r   r   z&broken for all-day (VALUE=DATE) events)r   r$   r\   r>   rK   rI   r*   r8   rV   rW   r{      z/webdav/zpurelymail.com)r   r   )r   r$   r4   r@   r>   r<   r6   r\   r`   ra   r   rS   r   zcaldav.gmx.netz/begenda/dav/{username}/   )r   r   r   r   r   z`unexpected results from date-search without comp-type - but only sometimes - TODO: research more)r"   r   )r   r   r4   rN   rC   rR   r\   r`   r[   r!   rS   rT   rU   r9   r{   r+   r,   r?   rJ   )rA   r[   r\   r`   ra   rc   r2   rS   rZ   r:   r9   r;   )r   ri   r   r   r   r	   incompatibility_descriptionxandikosradicale	nextcloudecloudzimbrabedeworksynologybaikal
baikal_oldcyrusdavicalsogoroburposteodavisccsstalwart
purelymailgmxoxrq   rq   rq   rr   <module>   s        #&*-036=

	
	
2


.$	#$)P	

6
(!$4
