
    i	H                       d Z ddlZddlZ eh d          Z G 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%d&d'i d(d)d*d+d,d+d-d+d.d/d0d1d2d3d4d5d6d7d8d9d:d;d<d=d>d?d@dAdBdCdDdEdFdGdHdIiZdJdKdLdMdNdOidNdOidNdOidPdQdRdNdPidNdOidNdOidNdOidPdQdRg dSdTZdJdKdLdMdNdOidNdOidNdOidNdPidPdQdRg dSdUZe                                Z	e	Z
dNdVidNdOidWdXdRdNdOidNdOidNdOidJdKdLdMg dYdZZd[d\idNdPidNdOidNdOidWd]dRd^dWd_dNdOidNdOidNdPid0gd`daZedbdcdddedfdgdhd\didjdkdlz  Zi dmd[dnidodWdpdRdqdWdrdRdsdNdOidtdNdOidudNdOidvdNdOidwdNdOidxdNdPidydNdWidzdNdOid{dNdOid|dNdPid}dNdOid~dNdWiddNdOiddOdg diZi ddddRd~dNdPidddddddgdddidmd[didxdNdPiddNdPidd`ddNdOiddNdViddNdOid{d`dzdNdWiddOddRdydNdWidtdNdOidNdOidNdOidNdPidZd`dWd`dWdWddRdNdOidNdOid`dgddid
ZdWdNdPidNdOidNdOidNdOidNdOidNdPidNdOidNdPiddgd`dZedddRdNdOidz  ZdNdPidNdOidNdPidNdPidNdPiddidWd]dRg dZdNdOidNdWidNdOidNdOidNdWidNdOidNdOig ddZd`d`d`dNdPidNdOidNdOidNdOidNdOidNdPidNdWidNdWidNdOidNdPidPddRddidZi dmddddxdNdPidodNdOidzdNdOiddNdOiddNdOid{dOddRddWddRd~dNdPid}dNdOiddNdWidtdNdOiddNdOiddNdPiddNdPidg ddddidydNdPiiZdjddLddNdOidNdOidNdidNdPidNdOidNdOidNdOidNdOidNdPidNdOidgdZdNdOidNdOidNdOidNdOidNdPidNdOidNdPiddgd`d	Zi dxdNdOidvdNdOidsdNdPiddNdOiddNdViddNdPiddNdViddNdPid~dNdPiddNdOiddNdOiddNdOiddNdOidddiddNdPiddgZdbddddbdNdPid`dWddRdNdWid`dNdOiddHgd	ZddddNdVidNdOidNdWidNdOidNdWidNdWidNdOidNdPidNdPidNdPidddddBgdZdjddddbdcdddedddWdddNdOidNdOidNdPidNdPidNdOidNdPidNdig 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                   J   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d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'd(d)d%d*id+d(d,dd-id.dd/id0dd1id2i d3d4d%d5id6d7dd8id9dd:id;dd<ii d=d>d%d&id6d?dd@idAddBidCddDidEdFd%d&id6dGdHd%d&id6dIdJd%d&id6dKddLidMdNd%d&id6dOddPidQddRidSddTidUddVidWddXidYddZd%d&id[d\dd]id^d_d`gdai dbdcd%d&id6ddddeidfdgd%d&id6dhddiidjddkidlddmidndod%d&id6dpddqidrddsidtdduidvddwidxddyidzdd{id|dd}id~ddidddidddii dddgdadddiddd%d&id6dddidddidddidddgdadddidddidddidddidddidddidddgdadddidddidddiddiddii i ddid"dddddd"ddddddZddZddZddZd Zd Z	d Z
d ZeddfdZ ed&dh          ZddZddZededefdĄ            Zedń             ZedefdƄ            ZddǄ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_keyszget-current-user-principalr   z|Support for RFC5397, current principal extension.  Most CalDAV servers have this, but it is an extension to the DAV standardz'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   
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   a  RFC4791 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).  (RFC4791 also says that the server MAY support MKCOL in section 8.5.2.  I do read it as MKCOL may be used for creating calendars - which is weird, since section 8.5.2 is titled "external attachments".  We should consider testing this as well))defaultr   create-calendar.autor	   zBAccessing a calendar which does not exist automatically creates it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 it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).zsave-load.todoz4it's possible to save and load tasks to the calendarz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)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.searchzgcalendar MUST support searching for objects using the REPORT method, as specified in 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 issue)r   r   r"   zsearch.time-rangezfSearch for time or date ranges should work.  This is specified in RFC4791, section 7.4 and 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.z9https://datatracker.ietf.org/doc/html/rfc4791#section-9.9)r   links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 restriction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.is-not-definedzaSupports searching for objects where properties is-not-defined according to 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 CATEGORIESz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 DTEND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.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)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 sectionssearch.text.category.substringzqSubstring search for category should work according to the RFC.  I.e., search for mil should match family,financesearch.text.by-uida4  The server supports searching for objects by UID property. When unsupported, calendar.get_object_by_uid(uid) will not work.  This may be removed in the feature - the checker-script is not checking the right thing (check TODO-comments), probably search by uid is no special case for any server implementations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.z9https://datatracker.ietf.org/doc/html/rfc4791#section-7.4)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 eventsz3search.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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)zfreebusy-queryzfreebusy queries come in two flavors, one query can be done towards a CalDAV server as defined in RFC4791, another query can be done through the scheduling framework, RFC 6638.  Only RFC4791 is tested for as todayfreebusy-query.rfc4791aZ  Server supports free/busy-query REPORT as specified in RFC4791 section 7.10. The REPORT allows clients to query for free/busy time information for a time range. Servers without this support will typically return an error (often 500 Internal Server Error or 501 Not Implemented). Note: RFC6638 defines a different freebusy mechanism for schedulingz: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)zprincipal-search.by-namezServer 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 implementedprincipal-search.by-name.selfzServer 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)rO   cleanup)principal-search.list-allzwrong-password-checksavezsave.duplicate-uid!save.duplicate-uid.cross-calendartest-calendarz!test-calendar.compatibility-testsNc                 L   t          |t                    r\t          j        |j                  | _        |j        | _        t          |d          rt          j        |j                  ng | _        dS |du | _        i | _        g | _        |r|                     |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hasattrrW   copyFeatureSet)selffeature_set_dicts     U/root/projects/butler/venv/lib/python3.11/site-packages/caldav/compatibility_hints.py__init__zFeatureSet.__init__(  s    . &
33 	$(M2B2S$T$TD!/?/[D,HOP`bnHoHowdi(8(CDDDuwDOF
 ,<t+C( " 	B 05AAAAA	B 	B    Tc                    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dii}nJ |                     |d           |                     |          }|                    dd	          }||                             d|                    d
d                    }d S )Nr!   Tr   Fr	   r   rX   r   server-featurer"   )rZ   dictstrr`   find_featureget)ra   featurevaluefcfeat_def	feat_typesups          rc   set_featurezFeatureSet.set_featureP  s    eT"" 	5!BBs## 		Iu-.BBd]]Iv./BBe^^I}56BB]Iy12BBLB///$$W--LL)9::	kooii)H)HIIre   c                    |D ]/}|dk    r||         | _         	 |                     |          }n9# t          t          f$ r% t	          j        d| dt          d           i }Y nw xY w||         }|| j        vr
i | j        |<   | j        |         }t          |t                    r
|rdnd|d<   t          |t                    r d|vr|                     ||           ||d<   t          |t                    r7d|v r|                     |d         |           |                    |           /J |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!   )rW   rj   AssertionErrorKeyErrorwarningswarnUserWarningr]   rZ   boolri   _validate_support_levelrh   updaterY   )ra   feature_setrY   rl   feature_inform   server_nodes          rc   r`   zFeatureSet.copyFeatureSetd  s   " 	 	G+%%"-g"6	"#0099"H- " " "d d d d 	     ""  (Ed33313%g./8K%&& 
38)KmI&&E3'' I[,H,H,,UG<<<).I&&E4(( %%00y1A7KKK""5)))) 	MMOOOOO	 	s   13A'&A'c                     |t           vrLt          j        d| d| dd                    t	          t                                t
          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,    rv   N)VALID_SUPPORT_LEVELSrz   r{   joinsortedr|   )ra   levelfeature_names      rc   r~   z"FeatureSet._validate_support_level  s    ,,,MKL K Ku K K!%62F+G+G!H!HK K	      -,re   c                     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)rZ   rh   rk   )ra   feature_dicts     rc   _collapse_keyzFeatureSet._collapse_key  s[     ,-- 	  Y''X&&Z((
 	
re   c                    t          | j                                                  }t                      }|D ]6}d|v r0|                    |d|                    d                              7t          |          }|                    d            |D ],}|                     |          }t          |d                   r| 	                    |t          d          }t          |d                   dk    s|d}||                     |          nd}|d         D ]M}	| j                            | d|	           }
|
d	} n'|                     |
          }||
}|}C||k    rd	} nN|sT|| j        vr
i | j        |<   |d         D ]!}	| j                            | d|	            "|                     ||i           .dS )
z~
        If all subfeatures are the same, it should be collapsed into the parent

        Messy and complex logic :-(
        .Nc                 2    |                      d           | fS Nr   )r   )xs    rc   <lambda>z%FeatureSet.collapse.<locals>.<lambda>  s    qwws||mQ%7 re   )keysubfeaturesF)return_typereturn_defaults   T)listr]   keyssetaddrfindsortrj   lenis_supportedrh   r   rk   popr`   )ra   featuresparentsrl   parentparent_infofoodont_collapsefoo_keysubbarbar_keys               rc   rY   zFeatureSet.collapse  s(    -224455%% 	: 	:Gg~~G$7W]]3%7%7$78999w--77888 	; 	;F++F33K;}-.. ;''DRW'XX{=122Q66#/$)M9<d00555dG*=9 " ""37768I8IC8I8IJJ;,0M!E"&"4"4S"9"9;"%C&-GG$//,0M!E 0 ) ;!)>>><>D1&9#.}#= I IC 1556G6G#6G6GHHHH++VSM:::3	; 	;re   c                    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    rd
diS |dk    rddiS |dv ri S t	          d|          )Nr"   r   rg   r!   r   r   r   Fr   	behaviournormalr   r   T)r   r   zUnknown feature type: )rZ   ri   rj   rk   
ValueError)ra   r   feature_types      rc   _defaultzFeatureSet._default  s    lC(( 	;,,\::L$$	**#''0@AA+++((---u&&111 (,,111''@@@JFlFFGGGre   Fc                    |                      |          }|}	 || j        v r#|                     | j        |         |||          S ||k    r|n|                      |          }d|vr|                     ||||          }||S d|vrO|sdS d|vr|                     ||||          }||S |                     |                     |          |||          S |d|                    d                   })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   )rj   r]   _convert_node_derive_from_subfeaturesr   r   )	ra   rl   r   r   accept_fragiler   feature_current_infoderiveds	            rc   r   zFeatureSet.is_supported  s:    ((11	64000))$*?*I<Ydftuuu+3w+>+><<DDUDUV^D_D_L,,77,P[]kll&"N(""&  4 L00";;HlT_aoppG*&))$--*E*E|U`bpqqq 4!4!4 45H-	6re   r   c                     d|vs|d         sdS d}g |d         D ]}| d| }	                       |          }d|v r$n#  Y nxY w|dz  }| j        v ru j        |         }	|	                    d|	                    d|	                    d	|	                    d
                                        }
|
r                    |
           sdS t	           fdD                       }t          fdD                       }t                    |k    }|r|r	d         }nd}n|r|r	d         }n|rd}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   *   K   | ]}|j         v V  d S N)_POSITIVE_STATUSES).0sra   s     rc   	<genexpr>z6FeatureSet._derive_from_subfeatures.<locals>.<genexpr>1  s+      UUA1 77UUUUUUre   c              3   0   K   | ]}|d          k    V  dS )r   N )r   r   subfeature_statusess     rc   r   z6FeatureSet._derive_from_subfeatures.<locals>.<genexpr>2  s.      PPqq/22PPPPPPre   r   )rj   r]   rk   appendanyallr   r   )ra   rl   r   r   r   total_relevantr   subfeature_keysubfeature_infosub_dictstatushas_positiveall_sameis_completederived_statusderived_noder   s   `               @rc   r   z#FeatureSet._derive_from_subfeatures  s     ,,L4O,4  . 	7 	7C '//#//N"&"3"3N"C"C// 0aN!6660@!ihU`bjbnbnoybzbzH{H{1|1|}} 7'..v666 # 	4UUUUATUUUUUPPPP<OPPPPP-...@ 	 +!4Q!7 "+ 		X 		03NN 	&NN 4 ">2!!,k>ZZZs   AAc           	         |t           k    r=|                    d|                    d|                    d                              S |t          k    r|S |t          k    r|                    dd          }|dk    rdS |r|dk    rd}|                    dd	          d	k    r|dk    S |                    d           o+|                    d           o|                    d
           S J )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   rg   r   )ri   rk   rh   r}   )ra   noder   r   r   r!   s         rc   r   zFeatureSet._convert_nodeJ  s    #88Itxx$((;:O:O'P'PQQQD  KD  hhy&11G'!!t !'Y"6"6 (899=MMM&((  88H---hdhh{6K6K2KhTXT\T\]gThThPhhLre   rl   returnc                    || j         v sJ d| j         |         vr|| j         |         d<   d|v rMd| j         |         vr>|                     |d|                    d                             | j         |         d<   d| j         |         vrD|                                 }|                    d          D ]
}||         }|| 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)
        rO   r   r   Nr   )FEATURESrj   r   feature_treesplit)clsrl   treer   s       rc   rj   zFeatureSet.find_featureg  s     #,&&&&g...,3CL!&)'>>hcl7.CCC.1.>.>wGZVYHZHZGZ?[.\.\CL!(+W 555##%%D]]3''  Aw37CL!-0|G$$re   c                 j    |D ]/}|}|                     d          }|D ]}||vri ||<   ||         }0|S r   )r   )r   targetsourcefeatr   pathparts          rc   _dots_to_treezFeatureSet._dots_to_tree~  s_     	" 	"DD::c??D " "t##!#DJDz" re   c                     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)r_   r   r   r   )r   s    rc   r   zFeatureSet.feature_tree  sK    " 3(( 	%$$#+S\:::  re   c                     i }|r|                                   | j        D ]B}| j        |         }|r||                     |          k    r+|                                ||<   C|S r   )rY   r]   r   r[   )ra   compactretr   rl   s        rc   dotted_feature_set_listz"FeatureSet.dotted_feature_set_list  st     	MMOOO& 	$ 	$A+A.G 7dmmA&6&666\\^^CFF
re   r   )T)F)__name__
__module____qualname____doc__r   rd   rr   r`   r~   r   rY   r   r}   r   	frozensetr   r   r   classmethodri   rh   rj   r   r   r   r   re   rc   r   r   '   sU
        .g N	
g 	 a">qS 	
 	
g" 	%  Z'[#g& 	2( c4d 4d'g, 	$ _>M_!U	  -g> 	( Fv
 
?gL 	!% b#
 #
MgT 	"F, }

 
Ug\ 	"M3_!
 !
]gd 	*],
egj 	  I
kgp 	)  G+
qgv 	wgx 	 b"I/
 
yg@ 	S
AgF 	M+bcGgH 	&  8x  (yIg gJ 	,  >Q  _h  jp  ^q  .r  .rKg^ 	0-  Br  2s_g` 	=*`aagb 	%}6v&wcgd 	+  =O  ]f  hn  \o  -p  -pegf 	3  Er  @I  KQ  R  5S  5Sggh 	(  :m  {D  FL  zM  *N  *Nigj 	m-STkgn 	+  =x  FO  QW  EX  -Y  -Yogp 	  i!
qgv 	&  n(
wg| 	#  P%
}gB 	  E
CgH 	$  ~&
IgN 	( S!6*
 
Og\ 	  DE]g` 	% FQR'
 '
ag g gh 	!2]ktv|j}"~"~igj 	+]  =t  -ukgl 	"3^luw}k~##mgn 	,m  >v  .wogp 	$m5b%cqgr 	"M  4K  $Lsgt 	 ~!6*"
 "
ug| 	)  P+
}gB 	&  C(
CgH 	C
IgN 	%  P'
OgT 	'  W)
UgZ 	   n"
[g` 	  E!Fagd 	)  O+
egj 	  R
kgp 	>
qg g gv 	/ ]QR1
 1
wg~ 	486
gD 	< E!6*>
 >
EgL 	5/7
MgR 	>  V@
SgX 	)d+
Yg` 	& jST(
 (
agh 	+,-
ign 	,,.
ogt 	0r2
ugz 	  |
{g@ 	  H
AgF 	=  +B  CGgH 	! xRS#
 #
IgP 	  ]
QgV 	#  F%
Wg\ 	(  m*
]g gd   D&
   ~!
    Q.

 & J$3Gnpp
 
 & `$3@~  A  A.
 .
Eg g gHR%B %B %B %BPJ J J J(       D  
 
 
$'; '; ';RH H H( 15d[` !6 !6 !6 !6F #FG#455D[ D[ D[ D[L   : %3 %4 % % % [%,   [ !T ! ! ! [!,	 	 	 	 	 	re   r   zno_current-user-principalzCurrent user principal not supported by the server (flag is ignored by the tests as for now - pass the principal URL as the testing URL and it will work, albeit with one warningno_schedulingzRFC6833 is not supportedno_scheduling_mailboxzEParts of RFC6833 is supported, but not the existence of inbox/mailbox'no_scheduling_calendar_user_address_setzKParts of RFC6833 is supported, but not getting the calendar users addressesno_default_calendarzdThe given user starts without an assigned default calendar (or without pre-defined calendars at all)no_freebusy_rfc6638z9Server does not support a freebusy-request as per RFC6638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?)no_delete_eventzRZimbra does not support deleting an event, probably because event_by_url is broken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_datesearch_nostart_future_tasks_deliveredzFuture tasks are yielded when doing a date search with some end timestamp and without start timestamp and the task contains both dtstart and due, but not duration (xandikos 0.2.12)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.9"vtodo_no_dtstart_infinite_durationzdate search will find todo-items without dtstart if due is after the date search interval.  This is in breach of rfc4791section 9.9!vtodo_no_dtstart_search_weirdnesszZimbra is weird"vtodo_no_duration_search_weirdnessvtodo_with_due_weirdness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unique_calendar_idsz5For every test, generate a new and unique calendar id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text_search_is_case_insensitivez@Probably not supporting the collation used by the caldav librarydate_search_ignores_durationzDate search with search interval overlapping event interval works on events with dtstart and dtend, but not on events with dtstart and due!date_todo_search_ignores_durationz)Same as above, but specifically for tasks#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?no_supported_components_supportz1The supported components prop query does not workno_relshipsz[The calendar server does not support child/parent relationships between calendar components'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 itno_search_openendedz"An open-ended search will not work	localhostr'   /)r   r   r   r!   r	   r   z500 internal server error)r!   r   )r  r   r   )r   rB   rG   r4   r8   r1   r=   r?   rM   rL   rt   )r   r1   rH   rJ   rM   rL   rt   r   r   z!inconsistent results between runs)r   r  r   r   )r9   r;   rD   rH   rJ   rM   r   rt   r   z/remote.php/davz*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!   F)r   r1   rH   rJ   r%   r&   rC   rN   rM   rt   rF   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 immediatelyr/   zM404 most of the time - but sometimes 200.  Weird, should be investigated morerT   rJ   r$   r,   r*   r-   rK   r9   r:   r>   rH   r1   r8   rM   rt   )r   r   r  r   r   r2   r   zXServer returns everything when searching for events and nothing when searching for todosr   r   g      ?)r   r   rU   rP   zwipe-calendarz	/ucaldav/r+   r)   rL   r4   rB   z6cannot reliably test due to broken comp-type filtering)rI   rH   rM   z&works for CLASS but not for CATEGORIES)
rM   rK   r%   r1   r9   r;   r8   rJ   rt   rU   )r(   r1   rH   rJ   rC   r.   rM   rN   rR   rt   rF   r   zmkcol-required)r    r#   )r1   rJ   r8   rM   rT   rU   r%   rt   )r   r  r   r   r   )r(   r1   rJ   r8   rK   rM   rR   rt   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 reason)r>   r7   r5   r-   r9   r;   r<   r8   r1   rB   rK   rG   rL   rM   rU   zcalendar.robur.coopz/principals/)r   r   z9a text search ignores the filter and returns all elementsr@   zZProbably not supported, but my caldav-server-checker tool has issues with it at the momentrI   rC   )r
  r   r  r  zposteo.de:8443)r   r   r   r   )r   r    r-   rT   r1   rH   rJ   rC   rF   rK   rM   rt   )	rH   rJ   rC   rN   rM   r.   r1   rt   rF   r6   r7   r5   r;   rA   ru   <   )r   r   r   z&broken for all-day (VALUE=DATE) events)	r   r#   rM   r8   rE   rC   rJ   r)   rt      z/webdav/zpurelymail.com)r   r   )r   r#   r1   r9   r8   r6   r4   rJ   rM   rN   rR   r   rt   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  r   r  )r   r   r1   rG   r;   rK   rM   rN   rL   r    rt   )r   r[   rz   r   r   r   incompatibility_descriptionxandikos_v0_2_12xandikos_v0_3xandikos_mainxandikosradicale	nextcloudecloudzimbrabedeworksynologybaikal
baikal_oldcyrusdavicalsogoroburposteodavisccsstalwart
purelymailgmxr   re   rc   <module>r.     s      !y " " "   2B
 B
 B
 B
 B
 B
 B
 B
pv 	@v &v Sv .Yv 	8v" G%v( F+v. C1v4 	(7v> VAvD `GvL 	,OvV 1PYv\ F\_vb .	#evj 6 	Cmvp %	sv vz )	}vD (GvJ )MvP SvV " 	\Yv\ C_vb 	Gevj %mvp  	asvv &Nyv| # 	YvB (7EvH ) 	mKvN   	kQvT &?WvZ i]v` . 	Ucv vf 0iv v x $/&cRR-6,F$-}#=(-8+7Fabb"+\!:'7'0-&@"M2*6E`aa    @ $/&cRR"+]!; *3M(B.7-G"L1*6E`aa   8 ""$$  (0#,m"<EN  ^A  :B  :B)2M(B.7-G"M2#.&cRR   8 	% #,\!:)2M(B.7-GAC C [' ' 3<]1K&/%?"L1'(&++ 	2 

  y  &!   
(+
W-+
9;qrr+
 	  AP  Q  Q+

 ()])C+
 ,i-G+
 &	='A+
 $i%?+
 'M(B+
 )\2+
 9i(+
 i7+
 I}-+
 Y5+
  ')](D!+
"  )Y!7#+
$ 	=9%+
& '+
*    ++
 +
Z7H  <V  W  W7)\!:7 'C887 7 &87 [17 )\27  /L0I!7" ,U#7& 	=9'72 6378 =97> 5?7@ 9A7F + M- -G7N 9O7V ,=.W7^ 	=* 	=) 	<i7 7 7t !*)2Aijj#,m"< )=9-2>?&8  #"+\!:)2M(B.7-G2;]1K)2M(B"L1&/%?"+\!: 	 ',# * #*9IJJ&6  
 #,\!:.7-G )<8"L1*3\)B&8AC C 	 	@ %m5#,i!9/8--I!*M;i("M2"+]!;	 	 	 0 ").(-#\2'7=# 	=% 	= 
 	<" 	9- 	9 	=$  ),7 { 
 '8O) )\	'" 	
 9l4	 M3	 y-9	 	=:	 	=9	 <wyy	 y  @\  ^  ^	  9l!<	 ')](D	 (9i)A	 ,y--I	  0)]1K!	" L1#	$ y,7%	&    '	2 &83	4 9l+5	 	> " 
 "=1#]3 ,5i)A #,\!: *3M(B.7-G2;]1K'0-&@l+"M2A$
 $
n *3M(B.7-G2;]1K&/%?"L1)2M(B"+\!:
 ',	 	()]3#i%? ()\)B /M0J 	62 ()\)B y&1 'L(A  )\!:  !9m"<!" 	=9#$ 9m4%& M2'* &8+. y,7/0 "1F  
 !"L1$ ?HV~22 3<Y1G .3 /8-G 	5	/ F #*C88 '/ #,]!;
 (3 )=9 )95()4.7-G"L1&/%>"+\!:"  	 	*;& &
T ".    .7  Hj  "k  "k$-}#= $-m"<
 l+"L1&/%?  ),7
 "9.  I+ +re   