def responseForHref(request, responses, href, resource, propertiesForResource, propertyreq): if propertiesForResource is not None: properties_by_status = waitForDeferred( propertiesForResource(request, propertyreq, resource)) yield properties_by_status properties_by_status = properties_by_status.getResult() propstats = [] for status in properties_by_status: properties = properties_by_status[status] if properties: xml_status = element.Status.fromResponseCode(status) xml_container = element.PropertyContainer(*properties) xml_propstat = element.PropertyStatus(xml_container, xml_status) propstats.append(xml_propstat) if propstats: responses.append(element.PropertyStatusResponse(href, *propstats)) else: responses.append( element.StatusResponse( href, element.Status.fromResponseCode(responsecode.OK), ))
def test_calendar_query_bogus_timezone_id(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZoneID.fromString("bogus"), ) result = yield self.calendar_query( query, got_xml=None, expected_code=responsecode.FORBIDDEN) self.assertTrue("valid-timezone" in result)
def test_PROPPATCH_liveprop(self): """ PROPPATCH on a live property """ prop = davxml.GETETag.fromString("some-etag-string") patch = davxml.PropertyUpdate(davxml.Set(davxml.PropertyContainer(prop))) return self._simple_PROPPATCH(patch, prop, responsecode.FORBIDDEN, "edit of live property")
def test_PROPPATCH_exists_not(self): """ PROPPATCH remove a non-existant property """ prop = davxml.Timeout() # Timeout isn't a valid property, so it won't exist. patch = davxml.PropertyUpdate(davxml.Remove(davxml.PropertyContainer(prop))) return self._simple_PROPPATCH(patch, prop, responsecode.OK, "remove of non-existant property")
def principalSearchPropertySet(self): props = [] for _ignore_field, _ignore_converter, description, xmlClass in self._fieldList: props.append( davxml.PrincipalSearchProperty( davxml.PropertyContainer(xmlClass()), davxml.Description(davxml.PCDATAElement(description), **{"xml:lang": "en"}), )) return davxml.PrincipalSearchPropertySet(*props)
def _defer(properties_by_status): propstats = [] for status in properties_by_status: properties = properties_by_status[status] if properties: xml_status = element.Status.fromResponseCode(status) xml_container = element.PropertyContainer(*properties) xml_propstat = element.PropertyStatus(xml_container, xml_status) propstats.append(xml_propstat) # Always need to have at least one propstat present (required by Prefer header behavior) if len(propstats) == 0: propstats.append(element.PropertyStatus( element.PropertyContainer(), element.Status.fromResponseCode(responsecode.OK) )) if propstats: responses.append(element.PropertyStatusResponse(href, *propstats))
def test_calendar_query_timezone(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) return self.calendar_query(query, got_xml)
def test_calendar_query_wrong_timezone_elements(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ TimezoneCache.create() self.addCleanup(TimezoneCache.clear) tzid1 = "Etc/GMT+1" tz1 = Component(None, pycalendar=readVTZ(tzid1)) calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData(), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), caldavxml.TimeZone.fromCalendar(tz1), ) query.children += (caldavxml.TimeZoneID.fromString(tzid1), ) result = yield self.calendar_query( query, got_xml=None, expected_code=responsecode.BAD_REQUEST) self.assertTrue("Only one of" in result)
def add(self, what, property): """ Add a response. @param what: a status code or a L{Failure} for the given path. @param property: the property whose status is being reported. """ if type(what) is int: code = what error = None message = responsecode.RESPONSES[code] elif isinstance(what, Failure): code = statusForFailure(what) error = errorForFailure(what) message = messageForFailure(what) else: raise AssertionError("Unknown data type: %r" % (what, )) if len(property.children) > 0: # Re-instantiate as empty element. property = element.WebDAVUnknownElement.withName( property.namespace, property.name) if code > 400: # Error codes only log.error("Error during {method} for {property}: {msg}", method=self.method, property=property, msg=message) children = [] children.append(element.PropertyContainer(property)) children.append(element.Status.fromResponseCode(code)) if error is not None: children.append(error) if message is not None: children.append(element.ResponseDescription(message)) self.propstats.append(element.PropertyStatus(*children))
def test_make_calendar_with_props(self): """ Make calendar with properties (CalDAV-access-09, section 5.3.1.2) """ uri = "/calendars/users/user01/calendar_prop/" path = os.path.join(self.docroot, uri[1:]) if os.path.exists(path): rmdir(path) @inlineCallbacks def do_test(response): response = IResponse(response) if response.code != responsecode.CREATED: self.fail("MKCALENDAR failed: %s" % (response.code, )) resource = (yield request.locateResource(uri)) if not resource.isCalendarCollection(): self.fail("MKCALENDAR made non-calendar collection") for qname, value in ( (davxml.DisplayName.qname(), "Lisa's Events"), (caldavxml.CalendarDescription.qname(), "Calendar restricted to events."), ): stored = yield resource.readProperty(qname, None) stored = str(stored) if stored != value: self.fail( "MKCALENDAR failed to set property %s: %s != %s" % (qname, stored, value)) supported_components = yield resource.readProperty( caldavxml.SupportedCalendarComponentSet, None) supported_components = supported_components.children if len(supported_components) != 1: self.fail( "MKCALENDAR failed to set property %s: len(%s) != 1" % (caldavxml.SupportedCalendarComponentSet.qname(), supported_components)) if supported_components[0] != caldavxml.CalendarComponent( name="VEVENT"): self.fail("MKCALENDAR failed to set property %s: %s != %s" % (caldavxml.SupportedCalendarComponentSet.qname(), supported_components[0].toxml(), caldavxml.CalendarComponent(name="VEVENT").toxml())) tz = (yield resource.readProperty(caldavxml.CalendarTimeZone, None)) tz = tz.calendar() self.failUnless(tz.resourceType() == "VTIMEZONE") self.failUnless( tuple(tz.subcomponents())[0].propertyValue("TZID") == "US-Eastern") mk = caldavxml.MakeCalendar( davxml.Set( davxml.PropertyContainer( davxml.DisplayName("Lisa's Events"), caldavxml.CalendarDescription( "Calendar restricted to events."), # FIXME: lang=en caldavxml.SupportedCalendarComponentSet( caldavxml.CalendarComponent(name="VEVENT")), caldavxml.CalendarTimeZone("""BEGIN:VCALENDAR PRODID:-//Example Corp.//CalDAV Client//EN VERSION:2.0 BEGIN:VTIMEZONE TZID:US-Eastern LAST-MODIFIED:19870101T000000Z BEGIN:STANDARD DTSTART:19671029T020000 RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 TZOFFSETFROM:-0400 TZOFFSETTO:-0500 TZNAME:Eastern Standard Time (US & Canada) END:STANDARD BEGIN:DAYLIGHT DTSTART:19870405T020000 RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 TZOFFSETFROM:-0500 TZOFFSETTO:-0400 TZNAME:Eastern Daylight Time (US & Canada) END:DAYLIGHT END:VTIMEZONE END:VCALENDAR """)))) request = SimpleStoreRequest(self, "MKCALENDAR", uri, authPrincipal=self.authPrincipal) request.stream = MemoryStream(mk.toxml()) return self.send(request, do_test)
def simple_event_multiget(self, cal_uri, okuids, baduids, data=None, no_init=False, withData=True): cal_uri = joinURL("/calendars/users/wsanchez", cal_uri) props = (davxml.GETETag(), ) if withData: props += (caldavxml.CalendarData(), ) children = [] children.append(davxml.PropertyContainer(*props)) okhrefs = [joinURL(cal_uri, x + ".ics") for x in okuids] badhrefs = [joinURL(cal_uri, x + ".ics") for x in baduids] for href in okhrefs + badhrefs: children.append(davxml.HRef.fromString(href)) query = caldavxml.CalendarMultiGet(*children) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) for response in doc.root_element.childrenOfType( davxml.PropertyStatusResponse): href = str(response.childOfType(davxml.HRef)) for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) if status.code != responsecode.OK: self.fail( "REPORT failed (status %s) to locate properties: %r" % (status.code, href)) properties = propstat.childOfType( davxml.PropertyContainer).children for property in properties: qname = property.qname() if qname == (davxml.dav_namespace, "getetag"): continue if qname != (caldavxml.caldav_namespace, "calendar-data"): self.fail( "Response included unexpected property %r" % (property, )) result_calendar = property.calendar() if result_calendar is None: self.fail( "Invalid response CalDAV:calendar-data: %r" % (property, )) uid = result_calendar.resourceUID() if uid in okuids: okuids.remove(uid) else: self.fail("Got calendar for unexpected UID %r" % (uid, )) if data: original_calendar = ical.Component.fromString( data[uid]) else: original_filename = file( os.path.join(self.holidays_dir, uid + ".ics")) original_calendar = ical.Component.fromStream( original_filename) self.assertEqual(result_calendar, original_calendar) for response in doc.root_element.childrenOfType( davxml.StatusResponse): href = str(response.childOfType(davxml.HRef)) propstatus = response.childOfType(davxml.PropertyStatus) if propstatus is not None: status = propstatus.childOfType(davxml.Status) else: status = response.childOfType(davxml.Status) if status.code != responsecode.OK: if href in okhrefs: self.fail( "REPORT failed (status %s) to locate properties: %r" % (status.code, href)) else: if href in badhrefs: badhrefs.remove(href) continue else: self.fail("Got unexpected href %r" % (href, )) if withData and (len(okuids) + len(badhrefs)): self.fail("Some components were not returned: %r, %r" % (okuids, badhrefs)) return self.calendar_query(cal_uri, query, got_xml, data, no_init)
def simple_vcard_query(self, vcard_uri, vcard_filter, uids, withData=True, limit=None): vcard_uri = joinURL("/addressbooks/users/wsanchez", vcard_uri) props = ( davxml.GETETag(), ) if withData: props += ( carddavxml.AddressData(), ) query = carddavxml.AddressBookQuery( davxml.PropertyContainer(*props), carddavxml.Filter( vcard_filter, ), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail("REPORT response XML root element is not multistatus: %r" % (doc.root_element,)) count = 0 for response in doc.root_element.childrenOfType(davxml.PropertyStatusResponse): for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) if status.code == responsecode.INSUFFICIENT_STORAGE_SPACE and limit is not None: continue if status.code != responsecode.OK: self.fail("REPORT failed (status %s) to locate properties: %r" % (status.code, propstat)) elif limit is not None: count += 1 continue properties = propstat.childOfType(davxml.PropertyContainer).children for property in properties: qname = property.qname() if qname == (davxml.dav_namespace, "getetag"): continue if qname != (carddavxml.carddav_namespace, "address-data"): self.fail("Response included unexpected property %r" % (property,)) result_addressbook = property.address() if result_addressbook is None: self.fail("Invalid response CardDAV:address-data: %r" % (property,)) uid = result_addressbook.resourceUID() if uid in uids: uids.remove(uid) else: self.fail("Got addressbook for unexpected UID %r" % (uid,)) original_filename = file(os.path.join(self.vcards_dir, uid + ".vcf")) original_addressbook = vcard.Component.fromStream(original_filename) self.assertEqual(result_addressbook, original_addressbook) if limit is not None and count != limit: self.fail("Wrong number of limited results: %d" % (count,)) return self.addressbook_query(vcard_uri, query, got_xml)
def mkcalendar_cb(response): response = IResponse(response) if response.code != responsecode.CREATED: self.fail("MKCALENDAR failed: %s" % (response.code,)) def propfind_cb(response): response = IResponse(response) if response.code != responsecode.MULTI_STATUS: self.fail("Incorrect response to PROPFIND: %s" % (response.code,)) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail("PROPFIND response XML root element is not multistatus: %r" % (doc.root_element,)) response = doc.root_element.childOfType(davxml.Response) href = response.childOfType(davxml.HRef) self.failUnless(str(href) == calendar_uri) for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) if status.code != responsecode.OK: self.fail("Unable to read requested properties (%s): %r" % (status, propstat.childOfType(davxml.PropertyContainer).toxml())) container = propstat.childOfType(davxml.PropertyContainer) # # Check CalDAV:supported-calendar-component-set # supported_components = container.childOfType(caldavxml.SupportedCalendarComponentSet) if not supported_components: self.fail("Expected CalDAV:supported-calendar-component-set element; but got none.") supported = set(("VEVENT",)) for component in supported_components.children: if component.type in supported: supported.remove(component.type) if supported: self.fail("Expected supported calendar component types: %s" % (tuple(supported),)) # # Check CalDAV:supported-calendar-data # supported_calendar = container.childOfType(caldavxml.SupportedCalendarData) if not supported_calendar: self.fail("Expected CalDAV:supported-calendar-data element; but got none.") for calendar in supported_calendar.children: if calendar.content_type not in ("text/calendar", "application/calendar+json"): self.fail("Expected a text/calendar calendar-data type restriction") if calendar.version != "2.0": self.fail("Expected a version 2.0 calendar-data restriction") # # Check DAV:supported-report-set # supported_reports = container.childOfType(davxml.SupportedReportSet) if not supported_reports: self.fail("Expected DAV:supported-report-set element; but got none.") cal_query = False cal_multiget = False cal_freebusy = False for supported in supported_reports.childrenOfType(davxml.SupportedReport): report = supported.childOfType(davxml.Report) if report.childOfType(caldavxml.CalendarQuery) is not None: cal_query = True if report.childOfType(caldavxml.CalendarMultiGet) is not None: cal_multiget = True if report.childOfType(caldavxml.FreeBusyQuery) is not None: cal_freebusy = True if not cal_query: self.fail("Expected CalDAV:CalendarQuery element; but got none.") if not cal_multiget: self.fail("Expected CalDAV:CalendarMultiGet element; but got none.") if not cal_freebusy: self.fail("Expected CalDAV:FreeBusyQuery element; but got none.") return davXMLFromStream(response.stream).addCallback(got_xml) query = davxml.PropertyFind( davxml.PropertyContainer( caldavxml.SupportedCalendarData(), caldavxml.SupportedCalendarComponentSet(), davxml.SupportedReportSet(), ), ) request = SimpleStoreRequest( self, "PROPFIND", calendar_uri, headers=http_headers.Headers({"Depth": "0"}), authPrincipal=self.authPrincipal, ) request.stream = MemoryStream(query.toxml()) return self.send(request, propfind_cb)
def test_calendar_query_time_range(self): """ Partial retrieval of events by time range. (CalDAV-access-09, section 7.6.1) """ calendar_properties = ( davxml.GETETag(), caldavxml.CalendarData( caldavxml.CalendarComponent( caldavxml.AllProperties(), caldavxml.CalendarComponent( caldavxml.Property(name="X-ABC-GUID"), caldavxml.Property(name="UID"), caldavxml.Property(name="DTSTART"), caldavxml.Property(name="DTEND"), caldavxml.Property(name="DURATION"), caldavxml.Property(name="EXDATE"), caldavxml.Property(name="EXRULE"), caldavxml.Property(name="RDATE"), caldavxml.Property(name="RRULE"), caldavxml.Property(name="LOCATION"), caldavxml.Property(name="SUMMARY"), name="VEVENT", ), caldavxml.CalendarComponent( caldavxml.AllProperties(), caldavxml.AllComponents(), name="VTIMEZONE", ), name="VCALENDAR", ), ), ) query_timerange = caldavxml.TimeRange( start="%04d1001T000000Z" % (DateTime.getToday().getYear(), ), end="%04d1101T000000Z" % (DateTime.getToday().getYear(), ), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*calendar_properties), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( query_timerange, name="VEVENT", ), name="VCALENDAR", ), ), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) for response in doc.root_element.childrenOfType( davxml.PropertyStatusResponse): properties_to_find = [p.qname() for p in calendar_properties] for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) properties = propstat.childOfType( davxml.PropertyContainer).children if status.code != responsecode.OK: self.fail( "REPORT failed (status %s) to locate properties: %r" % (status.code, properties)) for property in properties: qname = property.qname() if qname in properties_to_find: properties_to_find.remove(qname) else: self.fail( "REPORT found property we didn't ask for: %r" % (property, )) if isinstance(property, caldavxml.CalendarData): cal = property.calendar() instances = cal.expandTimeRanges( query_timerange.end) vevents = [ x for x in cal.subcomponents() if x.name() == "VEVENT" ] if not TimeRange(query_timerange).matchinstance( vevents[0], instances): self.fail( "REPORT property %r returned calendar %s outside of request time range %r" % (property, property.calendar, query_timerange)) return self.calendar_query(query, got_xml)
def simple_event_query(self, event_filter, uids, withData=True): props = (davxml.GETETag(), ) if withData: props += (caldavxml.CalendarData(), ) query = caldavxml.CalendarQuery( davxml.PropertyContainer(*props), caldavxml.Filter( caldavxml.ComponentFilter( caldavxml.ComponentFilter( event_filter, name="VEVENT", ), name="VCALENDAR", ), ), ) def got_xml(doc): if not isinstance(doc.root_element, davxml.MultiStatus): self.fail( "REPORT response XML root element is not multistatus: %r" % (doc.root_element, )) for response in doc.root_element.childrenOfType( davxml.PropertyStatusResponse): for propstat in response.childrenOfType(davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) if status.code != responsecode.OK: self.fail( "REPORT failed (status %s) to locate properties: %r" % (status.code, propstat)) properties = propstat.childOfType( davxml.PropertyContainer).children for property in properties: qname = property.qname() if qname == (davxml.dav_namespace, "getetag"): continue if qname != (caldavxml.caldav_namespace, "calendar-data"): self.fail( "Response included unexpected property %r" % (property, )) result_calendar = property.calendar() if result_calendar is None: self.fail( "Invalid response CalDAV:calendar-data: %r" % (property, )) uid = result_calendar.resourceUID() if uid in uids: uids.remove(uid) else: self.fail("Got calendar for unexpected UID %r" % (uid, )) original_filename = file( os.path.join(self.holidays_dir, uid + ".ics")) original_calendar = ical.Component.fromStream( original_filename) self.assertEqual(result_calendar, original_calendar) return self.calendar_query(query, got_xml)
def report_DAV__expand_property(self, request, expand_property): """ Generate an expand-property REPORT. (RFC 3253, section 3.8) TODO: for simplicity we will only support one level of expansion. """ # Verify root element if not isinstance(expand_property, element.ExpandProperty): raise ValueError( "%s expected as root element, not %s." % (element.ExpandProperty.sname(), expand_property.sname())) # Only handle Depth: 0 depth = request.headers.getHeader("depth", "0") if depth != "0": log.error("Non-zero depth is not allowed: %s" % (depth, )) raise HTTPError( StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth, ))) # # Get top level properties to expand and make sure we only have one level # properties = {} for property in expand_property.children: namespace = property.attributes.get("namespace", dav_namespace) name = property.attributes.get("name", "") # Make sure children have no children props_to_find = [] for child in property.children: if child.children: log.error( "expand-property REPORT only supports single level expansion" ) raise HTTPError( StatusResponse( responsecode.NOT_IMPLEMENTED, "expand-property REPORT only supports single level expansion" )) child_namespace = child.attributes.get("namespace", dav_namespace) child_name = child.attributes.get("name", "") props_to_find.append((child_namespace, child_name)) properties[(namespace, name)] = props_to_find # # Generate the expanded responses status for each top-level property # properties_by_status = { responsecode.OK: [], responsecode.NOT_FOUND: [], } filteredaces = None lastParent = None for qname in properties.iterkeys(): try: prop = (yield self.readProperty(qname, request)) # Form the PROPFIND-style DAV:prop element we need later props_to_return = element.PropertyContainer(*properties[qname]) # Now dereference any HRefs responses = [] for href in prop.children: if isinstance(href, element.HRef): # Locate the Href resource and its parent resource_uri = str(href) child = (yield request.locateResource(resource_uri)) if not child or not child.exists(): responses.append( element.StatusResponse( href, element.Status.fromResponseCode( responsecode.NOT_FOUND))) continue parent = (yield request.locateResource( parentForURL(resource_uri))) # Check privileges on parent - must have at least DAV:read try: yield parent.checkPrivileges(request, (element.Read(), )) except AccessDeniedError: responses.append( element.StatusResponse( href, element.Status.fromResponseCode( responsecode.FORBIDDEN))) continue # Cache the last parent's inherited aces for checkPrivileges optimization if lastParent != parent: lastParent = parent # Do some optimisation of access control calculation by determining any inherited ACLs outside of # the child resource loop and supply those to the checkPrivileges on each child. filteredaces = ( yield parent.inheritedACEsforChildren(request)) # Check privileges - must have at least DAV:read try: yield child.checkPrivileges( request, (element.Read(), ), inherited_aces=filteredaces) except AccessDeniedError: responses.append( element.StatusResponse( href, element.Status.fromResponseCode( responsecode.FORBIDDEN))) continue # Now retrieve all the requested properties on the HRef resource yield prop_common.responseForHref( request, responses, href, child, prop_common.propertyListForResource, props_to_return, ) prop.children = responses properties_by_status[responsecode.OK].append(prop) except: f = Failure() log.error( "Error reading property {qname} for resource {req}: {failure}", qname=qname, req=request.uri, failure=f.value) status = statusForFailure(f, "getting property: %s" % (qname, )) if status not in properties_by_status: properties_by_status[status] = [] properties_by_status[status].append(propertyName(qname)) # Build the overall response propstats = [ element.PropertyStatus( element.PropertyContainer(*properties_by_status[pstatus]), element.Status.fromResponseCode(pstatus)) for pstatus in properties_by_status if properties_by_status[pstatus] ] returnValue( MultiStatusResponse( (element.PropertyStatusResponse(element.HRef(request.uri), *propstats), )))
def test_PROPPATCH_basic(self): """ PROPPATCH """ # FIXME: # Do PROPFIND to make sure it's still there # Test nonexistant resource # Test None namespace in property def check_patch_response(response): response = IResponse(response) if response.code != responsecode.MULTI_STATUS: self.fail("Incorrect response code for PROPFIND (%s != %s)" % (response.code, responsecode.MULTI_STATUS)) content_type = response.headers.getHeader("content-type") if content_type not in (http_headers.MimeType("text", "xml"), http_headers.MimeType( "application", "xml")): self.fail( "Incorrect content-type for PROPPATCH response (%r not in %r)" % (content_type, (http_headers.MimeType("text", "xml"), http_headers.MimeType("application", "xml")))) return davXMLFromStream( response.stream).addCallback(check_patch_xml) def check_patch_xml(doc): multistatus = doc.root_element if not isinstance(multistatus, davxml.MultiStatus): self.fail( "PROPFIND response XML root element is not multistatus: %r" % (multistatus, )) # Requested a property change one resource, so there should be exactly one response response = multistatus.childOfType(davxml.Response) # Should have a response description (its contents are arbitrary) response.childOfType(davxml.ResponseDescription) # Requested property change was on / self.failUnless( response.childOfType(davxml.HRef) == "/", "Incorrect response URI: %s != /" % (response.childOfType(davxml.HRef), )) # Requested one property change, so there should be exactly one property status propstat = response.childOfType(davxml.PropertyStatus) # And the contained property should be a SpiffyProperty self.failIf( propstat.childOfType( davxml.PropertyContainer).childOfType(SpiffyProperty) is None, "Not a SpiffyProperty in PROPPATCH property status: %s" % (propstat.toxml())) # And the status should be 200 self.failUnless( propstat.childOfType(davxml.Status).code == responsecode.OK, "Incorrect status code for PROPPATCH of property %s: %s != %s" % (propstat.childOfType(davxml.PropertyContainer).toxml(), propstat.childOfType(davxml.Status).code, responsecode.OK)) patch = davxml.PropertyUpdate( davxml.Set( davxml.PropertyContainer( SpiffyProperty.fromString("This is a spiffy resource.")))) request = SimpleRequest(self.site, "PROPPATCH", "/") request.stream = MemoryStream(patch.toxml()) return self.send(request, check_patch_response)
properties_by_status[ responsecode.NOT_FOUND].append( propertyName(property)) elif not returnMinimal: properties_by_status[responsecode.NOT_FOUND].append( propertyName(property)) propstats = [] for status in properties_by_status: properties = properties_by_status[status] if not properties: continue xml_status = davxml.Status.fromResponseCode(status) xml_container = davxml.PropertyContainer(*properties) xml_propstat = davxml.PropertyStatus(xml_container, xml_status) propstats.append(xml_propstat) # Always need to have at least one propstat present (required by Prefer header behavior) if len(propstats) == 0: propstats.append( davxml.PropertyStatus( davxml.PropertyContainer(), davxml.Status.fromResponseCode(responsecode.OK))) xml_resource = davxml.HRef(uri) xml_response = davxml.PropertyStatusResponse(xml_resource, *propstats) xml_responses.append(xml_response)
def test_PROPFIND_basic(self): """ PROPFIND request """ def check_result(response): response = IResponse(response) if response.code != responsecode.MULTI_STATUS: self.fail("Incorrect response code for PROPFIND (%s != %s)" % (response.code, responsecode.MULTI_STATUS)) content_type = response.headers.getHeader("content-type") if content_type not in (http_headers.MimeType("text", "xml"), http_headers.MimeType( "application", "xml")): self.fail( "Incorrect content-type for PROPFIND response (%r not in %r)" % (content_type, (http_headers.MimeType("text", "xml"), http_headers.MimeType("application", "xml")))) return davXMLFromStream(response.stream).addCallback(check_xml) def check_xml(doc): multistatus = doc.root_element if not isinstance(multistatus, davxml.MultiStatus): self.fail( "PROPFIND response XML root element is not multistatus: %r" % (multistatus, )) for response in multistatus.childrenOfType( davxml.PropertyStatusResponse): if response.childOfType(davxml.HRef) == "/": for propstat in response.childrenOfType( davxml.PropertyStatus): status = propstat.childOfType(davxml.Status) properties = propstat.childOfType( davxml.PropertyContainer).children if status.code != responsecode.OK: self.fail( "PROPFIND failed (status %s) to locate live properties: %s" % (status.code, properties)) properties_to_find = [ p.qname() for p in self.liveProperties() ] for property in properties: qname = property.qname() if qname in properties_to_find: properties_to_find.remove(qname) else: self.fail( "PROPFIND found property we didn't ask for: %r" % (property, )) if properties_to_find: self.fail( "PROPFIND failed to find properties: %r" % (properties_to_find, )) break else: self.fail("No response for URI /") query = davxml.PropertyFind( davxml.PropertyContainer(*self.liveProperties())) request = SimpleRequest(self.site, "PROPFIND", "/") depth = "1" if depth is not None: request.headers.setHeader("depth", depth) request.stream = MemoryStream(query.toxml()) return self.send(request, check_result)