def multiget_common(self, request, multiget, collection_type):
    """
    Generate a multiget REPORT.
    """

    # Make sure target resource is of the right type
    if not self.isCollection():
        parent = (yield self.locateParent(request, request.uri))

        if collection_type == COLLECTION_TYPE_CALENDAR:
            if not parent.isPseudoCalendarCollection():
                log.error("calendar-multiget report is not allowed on a resource outside of a calendar collection %s" % (self,))
                raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be calendar resource"))
        elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
            if not parent.isAddressBookCollection():
                log.error("addressbook-multiget report is not allowed on a resource outside of an address book collection %s" % (self,))
                raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be address book resource"))

    responses = []

    propertyreq = multiget.property
    resources = multiget.resources

    if not hasattr(request, "extendedLogItems"):
        request.extendedLogItems = {}
    request.extendedLogItems["rcount"] = len(resources)

    hasData = False
    if propertyreq.qname() == ("DAV:", "allprop"):
        propertiesForResource = report_common.allPropertiesForResource

    elif propertyreq.qname() == ("DAV:", "propname"):
        propertiesForResource = report_common.propertyNamesForResource

    elif propertyreq.qname() == ("DAV:", "prop"):
        propertiesForResource = report_common.propertyListForResource

        if collection_type == COLLECTION_TYPE_CALENDAR:
            # Verify that any calendar-data element matches what we can handle
            result, message, hasData = report_common.validPropertyListCalendarDataTypeVersion(propertyreq)
            precondition = (caldav_namespace, "supported-calendar-data")
        elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
            # Verify that any address-data element matches what we can handle
            result, message, hasData = report_common.validPropertyListAddressDataTypeVersion(propertyreq)
            precondition = (carddav_namespace, "supported-address-data")
        else:
            result = True
        if not result:
            log.error(message)
            raise HTTPError(ErrorResponse(
                responsecode.FORBIDDEN,
                precondition,
                "Invalid object data element",
            ))
    else:
        raise AssertionError("We shouldn't be here")

    # Check size of results is within limit when data property requested
    if hasData and len(resources) > config.MaxMultigetWithDataHrefs:
        log.error("Too many results in multiget report returning data: %d" % len(resources))
        raise HTTPError(ErrorResponse(
            responsecode.FORBIDDEN,
            davxml.NumberOfMatchesWithinLimits(),
            "Too many resources",
        ))

    """
    Three possibilities exist:

        1. The request-uri is a calendar collection, in which case all the hrefs
        MUST be one-level below that collection and must be calendar object resources.

        2. The request-uri is a regular collection, in which case all the hrefs
        MUST be children of that (at any depth) but MUST also be calendar object
        resources (i.e. immediate child of a calendar collection).

        3. The request-uri is a resource, in which case there MUST be
        a single href equal to the request-uri, and MUST be a calendar
        object resource.
    """

    disabled = False
    if self.isPseudoCalendarCollection():
        requestURIis = "calendar"

        # 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 self.inheritedACEsforChildren(request))

        # Check for disabled access
        if filteredaces is None:
            disabled = True

        # Check private events access status
        isowner = (yield self.isOwner(request))

    elif self.isAddressBookCollection():
        requestURIis = "addressbook"

        # 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 self.inheritedACEsforChildren(request))

        # Check for disabled access
        if filteredaces is None:
            disabled = True
        isowner = None

    elif self.isCollection():
        requestURIis = "collection"
        filteredaces = None
        lastParent = None
        isowner = None
    else:
        requestURIis = "resource"
        filteredaces = None
        isowner = None

    if not disabled:

        @inlineCallbacks
        def doResponse():

            # Special for addressbooks
            if collection_type == COLLECTION_TYPE_ADDRESSBOOK:
                if self.isDirectoryBackedAddressBookCollection() and self.directory.liveQuery:
                    result = (yield doDirectoryAddressBookResponse())
                    returnValue(result)

            # Verify that requested resources are immediate children of the request-URI
            valid_names = []
            for href in resources:
                resource_uri = str(href)
                name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                if not self._isChildURI(request, resource_uri):
                    responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.BAD_REQUEST)))
                else:
                    valid_names.append(name)
            if not valid_names:
                returnValue(None)

            # Now determine which valid resources are readable and which are not
            ok_resources = []
            bad_resources = []
            missing_resources = []
            unavailable_resources = []
            yield self.findChildrenFaster(
                "1",
                request,
                lambda x, y: ok_resources.append((x, y)),
                lambda x, y: bad_resources.append((x, y)),
                lambda x: missing_resources.append(x),
                lambda x: unavailable_resources.append(x),
                valid_names,
                (davxml.Read(),),
                inherited_aces=filteredaces
            )

            # Get properties for all valid readable resources
            for resource, href in ok_resources:
                try:
                    yield report_common.responseForHref(
                        request, responses, davxml.HRef.fromString(href),
                        resource, propertiesForResource, propertyreq,
                        isowner=isowner
                    )
                except ValueError:
                    log.error("Invalid calendar resource during multiget: %s" %
                              (href,))
                    responses.append(davxml.StatusResponse(
                        davxml.HRef.fromString(href),
                        davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                except ConcurrentModification:
                    # This can happen because of a race-condition between the
                    # time we determine which resources exist and the deletion
                    # of one of these resources in another request.  In this
                    # case, return a 404 for the now missing resource rather
                    # than raise an error for the entire report.
                    log.error("Missing resource during multiget: %s" % (href,))
                    responses.append(davxml.StatusResponse(
                        davxml.HRef.fromString(href),
                        davxml.Status.fromResponseCode(responsecode.NOT_FOUND)
                    ))

            # Indicate error for all valid non-readable resources
            for ignore_resource, href in bad_resources:
                responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))

            # Indicate error for all missing/unavailable resources
            for href in missing_resources:
                responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
            for href in unavailable_resources:
                responses.append(davxml.StatusResponse(davxml.HRef.fromString(href), davxml.Status.fromResponseCode(responsecode.SERVICE_UNAVAILABLE)))

        @inlineCallbacks
        def doDirectoryAddressBookResponse():

            directoryAddressBookLock = None
            try:
                # Verify that requested resources are immediate children of the request-URI
                # and get vCardFilters ;similar to "normal" case below but do not call getChild()
                vCardFilters = []
                valid_hrefs = []
                for href in resources:
                    resource_uri = str(href)
                    resource_name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                    if self._isChildURI(request, resource_uri) and resource_name.endswith(".vcf") and len(resource_name) > 4:
                        valid_hrefs.append(href)
                        vCardFilters.append(carddavxml.PropertyFilter(
                                                carddavxml.TextMatch.fromString(resource_name[:-4]),
                                                name="UID", # attributes
                                            ))
                    elif not self.directory.cacheQuery:
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))

                # exit if not valid
                if not vCardFilters or not valid_hrefs:
                    returnValue(None)

                addressBookFilter = carddavxml.Filter(*vCardFilters)
                addressBookFilter = addressbookqueryfilter.Filter(addressBookFilter)
                if self.directory.cacheQuery:
                    # add vcards to directory address book and run "normal case" below
                    limit = config.DirectoryAddressBook.MaxQueryResults
                    directoryAddressBookLock, limited = (yield  self.directory.cacheVCardsForAddressBookQuery(addressBookFilter, propertyreq, limit))
                    if limited:
                        log.error("Too many results in multiget report: %d" % len(resources))
                        raise HTTPError(ErrorResponse(
                            responsecode.FORBIDDEN,
                            (dav_namespace, "number-of-matches-within-limits"),
                            "Too many results",
                        ))
                else:
                    #get vCards and filter
                    limit = config.DirectoryAddressBook.MaxQueryResults
                    vCardRecords, limited = (yield self.directory.vCardRecordsForAddressBookQuery(addressBookFilter, propertyreq, limit))
                    if limited:
                        log.error("Too many results in multiget report: %d" % len(resources))
                        raise HTTPError(ErrorResponse(
                            responsecode.FORBIDDEN,
                            (dav_namespace, "number-of-matches-within-limits"),
                            "Too many results",
                        ))

                    for href in valid_hrefs:
                        matchingRecord = None
                        for vCardRecord in vCardRecords:
                            if href == vCardRecord.hRef(): # might need to compare urls instead - also case sens ok?
                                matchingRecord = vCardRecord
                                break

                        if matchingRecord:
                            yield report_common.responseForHref(request, responses, href, matchingRecord, propertiesForResource, propertyreq, vcard=matchingRecord.vCard())
                        else:
                            responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
            finally:
                if directoryAddressBookLock:
                    yield directoryAddressBookLock.release()

        if requestURIis == "calendar" or requestURIis == "addressbook":
            yield doResponse()
        else:
            for href in resources:

                resource_uri = str(href)

                # Do href checks
                if requestURIis == "calendar":
                    pass
                elif requestURIis == "addressbook":
                    pass

                # TODO: we can optimize this one in a similar manner to the calendar case
                elif requestURIis == "collection":
                    name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                    if not self._isChildURI(request, resource_uri, False):
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
                        continue

                    child = (yield request.locateResource(resource_uri))

                    if not child or not child.exists():
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
                        continue

                    parent = (yield child.locateParent(request, resource_uri))

                    if collection_type == COLLECTION_TYPE_CALENDAR:
                        if not parent.isCalendarCollection() or not (yield parent.index().resourceExists(name)):
                            responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                            continue
                    elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
                            responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                            continue

                    # Check privileges on parent - must have at least DAV:read
                    try:
                        yield parent.checkPrivileges(request, (davxml.Read(),))
                    except AccessDeniedError:
                        responses.append(davxml.StatusResponse(href, davxml.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 private events access status
                        isowner = (yield parent.isOwner(request))
                else:
                    name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                    if (resource_uri != request.uri) or not self.exists():
                        responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.NOT_FOUND)))
                        continue

                    parent = (yield self.locateParent(request, resource_uri))

                    if collection_type == COLLECTION_TYPE_CALENDAR:
                        if not parent.isPseudoCalendarCollection() or not (yield parent.index().resourceExists(name)):
                            responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                            continue
                    elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
                        if not parent.isAddressBookCollection() or not (yield parent.index().resourceExists(name)):
                            responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                            continue
                    child = self

                    # 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 private events access status
                    isowner = (yield parent.isOwner(request))

                # Check privileges - must have at least DAV:read
                try:
                    yield child.checkPrivileges(request, (davxml.Read(),), inherited_aces=filteredaces)
                except AccessDeniedError:
                    responses.append(davxml.StatusResponse(href, davxml.Status.fromResponseCode(responsecode.FORBIDDEN)))
                    continue

                yield report_common.responseForHref(request, responses, href, child, propertiesForResource, propertyreq, isowner=isowner)

    returnValue(MultiStatusResponse(responses))
def report_urn_ietf_params_xml_ns_carddav_addressbook_query(
        self, request, addressbook_query):
    """
    Generate an addressbook-query REPORT.
    (CardDAV, section 8.6)
    """
    # Verify root element
    if addressbook_query.qname() != (carddav_namespace, "addressbook-query"):
        raise ValueError(
            "{CardDAV:}addressbook-query expected as root element, not {elementName}."
            .format(elementName=addressbook_query.sname()))

    if not self.isCollection():
        parent = (yield self.locateParent(request, request.uri))
        if not parent.isAddressBookCollection():
            log.error(
                "addressbook-query report is not allowed on a resource outside of an address book collection {parent}",
                parent=self)
            raise HTTPError(
                StatusResponse(
                    responsecode.FORBIDDEN,
                    "Must be address book collection or address book resource")
            )

    responses = []

    xmlfilter = addressbook_query.filter
    filter = Filter(xmlfilter)
    query = addressbook_query.props
    limit = addressbook_query.limit

    assert query is not None

    if query.qname() == ("DAV:", "allprop"):
        propertiesForResource = report_common.allPropertiesForResource
        generate_address_data = False

    elif query.qname() == ("DAV:", "propname"):
        propertiesForResource = report_common.propertyNamesForResource
        generate_address_data = False

    elif query.qname() == ("DAV:", "prop"):
        propertiesForResource = report_common.propertyListForResource

        # Verify that any address-data element matches what we can handle
        result, message, generate_address_data = report_common.validPropertyListAddressDataTypeVersion(
            query)
        if not result:
            log.error(message)
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    (carddav_namespace, "supported-address-data"),
                    "Invalid address-data",
                ))

    else:
        raise AssertionError("We shouldn't be here")

    # Verify that the filter element is valid
    if (filter is None) or not filter.valid():
        log.error("Invalid filter element: %r" % (filter, ))
        raise HTTPError(
            ErrorResponse(
                responsecode.FORBIDDEN,
                (carddav_namespace, "valid-filter"),
                "Invalid filter element",
            ))

    matchcount = [
        0,
    ]
    max_number_of_results = [
        config.MaxQueryWithDataResults if generate_address_data else None,
    ]
    limited = [
        False,
    ]

    if limit:
        clientLimit = int(str(limit.childOfType(NResults)))
        if max_number_of_results[
                0] is None or clientLimit < max_number_of_results[0]:
            max_number_of_results[0] = clientLimit

    @inlineCallbacks
    def doQuery(addrresource, uri):
        """
        Run a query on the specified address book collection
        accumulating the query responses.
        @param addrresource: the L{CalDAVResource} for an address book collection.
        @param uri: the uri for the address book collecton resource.
        """
        def checkMaxResults():
            matchcount[0] += 1
            if max_number_of_results[0] is not None and matchcount[
                    0] > max_number_of_results[0]:
                raise NumberOfMatchesWithinLimits(max_number_of_results[0])

        @inlineCallbacks
        def queryAddressBookObjectResource(resource,
                                           uri,
                                           name,
                                           vcard,
                                           query_ok=False):
            """
            Run a query on the specified vcard.
            @param resource: the L{CalDAVResource} for the vcard.
            @param uri: the uri of the resource.
            @param name: the name of the resource.
            @param vcard: the L{Component} vcard read from the resource.
            """

            if query_ok or filter.match(vcard):
                # Check size of results is within limit
                checkMaxResults()

                if name:
                    href = davxml.HRef.fromString(joinURL(uri, name))
                else:
                    href = davxml.HRef.fromString(uri)

                try:
                    yield report_common.responseForHref(request,
                                                        responses,
                                                        href,
                                                        resource,
                                                        propertiesForResource,
                                                        query,
                                                        vcard=vcard)
                except ConcurrentModification:
                    # This can happen because of a race-condition between the
                    # time we determine which resources exist and the deletion
                    # of one of these resources in another request.  In this
                    # case, we ignore the now missing resource rather
                    # than raise an error for the entire report.
                    log.error("Missing resource during sync: {href}",
                              href=href)

        @inlineCallbacks
        def queryDirectoryBackedAddressBook(directoryBackedAddressBook,
                                            addressBookFilter):
            """
            """
            results, limited[
                0] = yield directoryBackedAddressBook.doAddressBookDirectoryQuery(
                    addressBookFilter, query, max_number_of_results[0])
            for vCardResult in results:

                # match against original filter if different from addressBookFilter
                if addressBookFilter is filter or filter.match(
                    (yield vCardResult.vCard())):

                    # Check size of results is within limit
                    checkMaxResults()

                    try:
                        yield report_common.responseForHref(
                            request,
                            responses,
                            vCardResult.hRef(),
                            vCardResult,
                            propertiesForResource,
                            query,
                            vcard=(yield vCardResult.vCard()))
                    except ConcurrentModification:
                        # This can happen because of a race-condition between the
                        # time we determine which resources exist and the deletion
                        # of one of these resources in another request.  In this
                        # case, we ignore the now missing resource rather
                        # than raise an error for the entire report.
                        log.error("Missing resource during sync: {href}",
                                  href=vCardResult.hRef())

        if not addrresource.isAddressBookCollection():

            #do UID lookup on last part of uri
            resource_name = urllib.unquote(uri[uri.rfind("/") + 1:])
            if resource_name.endswith(".vcf") and len(resource_name) > 4:

                # see if parent is directory backed address book
                parent = (yield addrresource.locateParent(request, uri))

        # Check whether supplied resource is an address book or an address book object resource
        if addrresource.isAddressBookCollection():

            if addrresource.isDirectoryBackedAddressBookCollection():
                yield maybeDeferred(queryDirectoryBackedAddressBook,
                                    addrresource, filter)

            else:

                # 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
                                addrresource.inheritedACEsforChildren(request))

                # Check for disabled access
                if filteredaces is not None:
                    index_query_ok = True
                    try:
                        # Get list of children that match the search and have read access
                        names = [
                            name for name, ignore_uid in (
                                yield addrresource.search(filter))
                        ]  #@UnusedVariable
                    except IndexedSearchException:
                        names = yield addrresource.listChildren()
                        index_query_ok = False
                    if not names:
                        return

                    # Now determine which valid resources are readable and which are not
                    ok_resources = []
                    yield addrresource.findChildrenFaster(
                        "1",
                        request,
                        lambda x, y: ok_resources.append((x, y)),
                        None,
                        None,
                        None,
                        names, (davxml.Read(), ),
                        inherited_aces=filteredaces)
                    for child, child_uri in ok_resources:
                        child_uri_name = child_uri[child_uri.rfind("/") + 1:]

                        if generate_address_data or not index_query_ok:
                            vcard = yield child.vCard()
                            assert vcard is not None, "vCard {name} is missing from address book collection {collection!r}".format(
                                name=child_uri_name, collection=self)
                        else:
                            vcard = None

                        yield queryAddressBookObjectResource(
                            child,
                            uri,
                            child_uri_name,
                            vcard,
                            query_ok=index_query_ok)

        else:

            handled = False
            resource_name = urllib.unquote(uri[uri.rfind("/") + 1:])
            if resource_name.endswith(".vcf") and len(resource_name) > 4:

                # see if parent is directory backed address book
                parent = (yield addrresource.locateParent(request, uri))

                if parent.isDirectoryBackedAddressBookCollection():

                    vCardFilter = carddavxml.Filter(*[
                        carddavxml.PropertyFilter(
                            carddavxml.TextMatch.fromString(
                                resource_name[:-4]),
                            name="UID",  # attributes
                        ),
                    ])
                    vCardFilter = Filter(vCardFilter)

                    yield maybeDeferred(queryDirectoryBackedAddressBook,
                                        parent, vCardFilter)
                    handled = True

            if not handled:
                vcard = yield addrresource.vCard()
                yield queryAddressBookObjectResource(addrresource, uri, None,
                                                     vcard)

        if limited[0]:
            raise NumberOfMatchesWithinLimits(matchcount[0])

    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToAddressBookCollections(
            self, request, request.uri, depth, doQuery, (davxml.Read(), ))
    except NumberOfMatchesWithinLimits, e:
        self.log.info(
            "Too many matching components in addressbook-query report. Limited to {limit} items",
            limit=e.maxLimit())
        responses.append(
            davxml.StatusResponse(
                davxml.HRef.fromString(request.uri),
                davxml.Status.fromResponseCode(
                    responsecode.INSUFFICIENT_STORAGE_SPACE),
                davxml.Error(davxml.NumberOfMatchesWithinLimits()),
                davxml.ResponseDescription(
                    "Results limited to {limit} items".format(
                        limit=e.maxLimit())),
            ))
def report_urn_ietf_params_xml_ns_carddav_addressbook_query(self, request, addressbook_query):
    """
    Generate an addressbook-query REPORT.
    (CardDAV, section 8.6)
    """
    # Verify root element
    if addressbook_query.qname() != (carddav_namespace, "addressbook-query"):
        raise ValueError("{CardDAV:}addressbook-query expected as root element, not %s." % (addressbook_query.sname(),))

    if not self.isCollection():
        parent = (yield self.locateParent(request, request.uri))
        if not parent.isAddressBookCollection():
            log.err("addressbook-query report is not allowed on a resource outside of an address book collection %s" % (self,))
            raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Must be address book collection or address book resource"))

    responses = []

    xmlfilter = addressbook_query.filter
    filter = addressbookqueryfilter.Filter(xmlfilter)
    query  = addressbook_query.props
    limit = addressbook_query.limit

    assert query is not None
    
    if query.qname() == ("DAV:", "allprop"):
        propertiesForResource = report_common.allPropertiesForResource
        generate_address_data = False

    elif query.qname() == ("DAV:", "propname"):
        propertiesForResource = report_common.propertyNamesForResource
        generate_address_data = False

    elif query.qname() == ("DAV:", "prop"):
        propertiesForResource = report_common.propertyListForResource
       
        # Verify that any address-data element matches what we can handle
        result, message, generate_address_data = report_common.validPropertyListAddressDataTypeVersion(query)
        if not result:
            log.err(message)
            raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "supported-address-data")))
        
    else:
        raise AssertionError("We shouldn't be here")

    # Verify that the filter element is valid
    if (filter is None) or not filter.valid():
        log.err("Invalid filter element: %r" % (filter,))
        raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (carddav_namespace, "valid-filter")))

    matchcount = [0,]
    max_number_of_results = [config.MaxQueryWithDataResults if generate_address_data else None,]
    limited = [False,]
    
    if limit:
        clientLimit = int(str(limit.childOfType(NResults)))
        if max_number_of_results[0] is None or clientLimit < max_number_of_results[0]:
            max_number_of_results[0] = clientLimit

    @inlineCallbacks
    def doQuery(addrresource, uri):
        """
        Run a query on the specified address book collection
        accumulating the query responses.
        @param addrresource: the L{CalDAVFile} for an address book collection.
        @param uri: the uri for the address book collecton resource.
        """
        
        def checkMaxResults():
            matchcount[0] += 1
            if max_number_of_results[0] is not None and matchcount[0] > max_number_of_results[0]:
                raise NumberOfMatchesWithinLimits(max_number_of_results[0])
           
        
        def queryAddressBookObjectResource(resource, uri, name, vcard, query_ok = False):
            """
            Run a query on the specified vcard.
            @param resource: the L{CalDAVFile} for the vcard.
            @param uri: the uri of the resource.
            @param name: the name of the resource.
            @param vcard: the L{Component} vcard read from the resource.
            """
            
            if query_ok or filter.match(vcard):
                # Check size of results is within limit
                checkMaxResults()

                if name:
                    href = davxml.HRef.fromString(joinURL(uri, name))
                else:
                    href = davxml.HRef.fromString(uri)
            
                return report_common.responseForHref(request, responses, href, resource, propertiesForResource, query, vcard=vcard)
            else:
                return succeed(None)
            
                                
            
        @inlineCallbacks
        def queryDirectoryBackedAddressBook(directoryBackedAddressBook, addressBookFilter):
            """
            """
            records, limited[0] = (yield directoryBackedAddressBook.directory.vCardRecordsForAddressBookQuery( addressBookFilter, query, max_number_of_results[0] ))
            for vCardRecord in records:
                
                # match against original filter
                if filter.match(vCardRecord.vCard()):
 
                    # Check size of results is within limit
                    checkMaxResults()
                   
                    yield report_common.responseForHref(request, responses, vCardRecord.hRef(), vCardRecord, propertiesForResource, query, vcard=vCardRecord.vCard())
 
 
            
        directoryAddressBookLock = None
        try:                

            if addrresource.isDirectoryBackedAddressBookCollection() and addrresource.directory.cacheQuery:
                
                directory = addrresource.directory
                if directory.liveQuery:
                    # if liveQuery and cacheQuery, get vCards into the directory address book on disk
                    directoryAddressBookLock, limited[0] = (yield  directory.cacheVCardsForAddressBookQuery( filter, query, max_number_of_results[0] ) )
 
                elif directory.maxDSQueryRecords and directory.maxDSQueryRecords < max_number_of_results[0]:
                    max_number_of_results[0] = directory.maxDSQueryRecords
                   
                
            elif not addrresource.isAddressBookCollection():
 
                #do UID lookup on last part of uri
                resource_name = urllib.unquote(uri[uri.rfind("/") + 1:])
                if resource_name.endswith(".vcf") and len(resource_name) > 4:
    
                    # see if parent is directory backed address book
                    parent = (yield  addrresource.locateParent( request, uri ) )
    
                    if parent.isDirectoryBackedAddressBookCollection() and parent.directory.cacheQuery:
                        
                        directory = parent.directory
                        if directory.liveQuery:
                            vCardFilter = carddavxml.Filter( *[carddavxml.PropertyFilter(
                                                        carddavxml.TextMatch.fromString(resource_name[:-4]), 
                                                        name="UID", # attributes
                                                        ),] )
                            vCardFilter = addressbookqueryfilter.Filter(vCardFilter)
                            
                            directoryAddressBookLock, limited[0] = (yield  directory.cacheVCardsForAddressBookQuery( vCardFilter, query, max_number_of_results[0] ) )

                        elif directory.maxDSQueryRecords and directory.maxDSQueryRecords < max_number_of_results[0]:
                            max_number_of_results[0] = directory.maxDSQueryRecords
   
    
        
            # Check whether supplied resource is an address book or an address book object resource
            if addrresource.isAddressBookCollection():
    
                if addrresource.isDirectoryBackedAddressBookCollection() and addrresource.directory.liveQuery and not addrresource.directory.cacheQuery:
                    yield  maybeDeferred( queryDirectoryBackedAddressBook, addrresource, filter )
                
                else:

                    # 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 addrresource.inheritedACEsforChildren(request))
                
                    # Check for disabled access
                    if filteredaces is not None:
                        # See whether the filter is valid for an index only query
                        index_query_ok = addrresource.index().searchValid(filter)
                    
                        # Get list of children that match the search and have read access
                        names = [name for name, ignore_uid in addrresource.index().search(filter)] #@UnusedVariable
                        if not names:
                            return
                          
                        # Now determine which valid resources are readable and which are not
                        ok_resources = []
                        yield addrresource.findChildrenFaster(
                            "1",
                            request,
                            lambda x, y: ok_resources.append((x, y)),
                            None,
                            names,
                            (davxml.Read(),),
                            inherited_aces=filteredaces
                        )
                        
                        for child, child_uri in ok_resources:
                            child_uri_name = child_uri[child_uri.rfind("/") + 1:]
                            child_path_name = urllib.unquote(child_uri_name)
                            
                            if generate_address_data or not index_query_ok:
                                vcard = addrresource.vCard(child_path_name)
                                assert vcard is not None, "vCard %s is missing from address book collection %r" % (child_uri_name, self)
                            else:
                                vcard = None
                            
                            yield queryAddressBookObjectResource(child, uri, child_uri_name, vcard, query_ok = index_query_ok)
                        
            else:
                
                handled = False;
                resource_name = urllib.unquote(uri[uri.rfind("/") + 1:])
                if resource_name.endswith(".vcf") and len(resource_name) > 4:
                    
                    # see if parent is directory backed address book
                    parent = (yield  addrresource.locateParent( request, uri ) )
    
                    if parent.isDirectoryBackedAddressBookCollection() and parent.directory.liveQuery and not parent.directory.cacheQuery:
 
                        vCardFilter = carddavxml.Filter( *[carddavxml.PropertyFilter(
                                                    carddavxml.TextMatch.fromString(resource_name[:-4]), 
                                                    name="UID", # attributes
                                                    ),] )
                        vCardFilter = addressbookqueryfilter.Filter(vCardFilter)
                        
                        yield  maybeDeferred( queryDirectoryBackedAddressBook, parent, vCardFilter )
                        handled = True

                if not handled:
                    vcard = addrresource.vCard()
                    yield queryAddressBookObjectResource(addrresource, uri, None, vcard)
        
            if limited[0]:
                raise NumberOfMatchesWithinLimits(matchcount[0])

        finally:
            if directoryAddressBookLock:
                yield directoryAddressBookLock.release()
                
    
    # Run report taking depth into account
    try:
        depth = request.headers.getHeader("depth", "0")
        yield report_common.applyToAddressBookCollections(self, request, request.uri, depth, doQuery, (davxml.Read(),))
    except NumberOfMatchesWithinLimits, e:
        self.log_info("Too many matching components in addressbook-query report. Limited to %d items" % e.maxLimit())
        responses.append(davxml.StatusResponse(
                        davxml.HRef.fromString(request.uri),
                        davxml.Status.fromResponseCode(responsecode.INSUFFICIENT_STORAGE_SPACE),
                        davxml.Error(davxml.NumberOfMatchesWithinLimits()),
                        #davxml.ResponseDescription("Results limited by %s at %d" % resultsWereLimited),
                        davxml.ResponseDescription("Results limited to %d items" % e.maxLimit()),
                    ))
Exemplo n.º 4
0
def multiget_common(self, request, multiget, collection_type):
    """
    Generate a multiget REPORT.
    """

    # Make sure target resource is of the right type
    if not self.isCollection():
        parent = (yield self.locateParent(request, request.uri))

        if collection_type == COLLECTION_TYPE_CALENDAR:
            if not parent.isPseudoCalendarCollection():
                log.error(
                    "calendar-multiget report is not allowed on a resource outside of a calendar collection {res}",
                    res=self)
                raise HTTPError(
                    StatusResponse(responsecode.FORBIDDEN,
                                   "Must be calendar resource"))
        elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
            if not parent.isAddressBookCollection():
                log.error(
                    "addressbook-multiget report is not allowed on a resource outside of an address book collection {res}",
                    res=self)
                raise HTTPError(
                    StatusResponse(responsecode.FORBIDDEN,
                                   "Must be address book resource"))

    responses = []

    propertyreq = multiget.property
    resources = multiget.resources

    if not hasattr(request, "extendedLogItems"):
        request.extendedLogItems = {}
    request.extendedLogItems["rcount"] = len(resources)

    hasData = False
    if propertyreq.qname() == ("DAV:", "allprop"):
        propertiesForResource = report_common.allPropertiesForResource

    elif propertyreq.qname() == ("DAV:", "propname"):
        propertiesForResource = report_common.propertyNamesForResource

    elif propertyreq.qname() == ("DAV:", "prop"):
        propertiesForResource = report_common.propertyListForResource

        if collection_type == COLLECTION_TYPE_CALENDAR:
            # Verify that any calendar-data element matches what we can handle
            result, message, hasData = report_common.validPropertyListCalendarDataTypeVersion(
                propertyreq)
            precondition = (caldav_namespace, "supported-calendar-data")
        elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
            # Verify that any address-data element matches what we can handle
            result, message, hasData = report_common.validPropertyListAddressDataTypeVersion(
                propertyreq)
            precondition = (carddav_namespace, "supported-address-data")
        else:
            result = True
        if not result:
            log.error(message)
            raise HTTPError(
                ErrorResponse(
                    responsecode.FORBIDDEN,
                    precondition,
                    "Invalid object data element",
                ))
    else:
        raise AssertionError("We shouldn't be here")

    # Check size of results is within limit when data property requested
    if hasData and len(resources) > config.MaxMultigetWithDataHrefs:
        log.error("Too many resources in multiget report: {count}",
                  count=len(resources))
        raise HTTPError(
            ErrorResponse(
                responsecode.FORBIDDEN,
                davxml.NumberOfMatchesWithinLimits(),
                "Too many resources",
            ))
    """
    Three possibilities exist:

        1. The request-uri is a calendar collection, in which case all the hrefs
        MUST be one-level below that collection and must be calendar object resources.

        2. The request-uri is a regular collection, in which case all the hrefs
        MUST be children of that (at any depth) but MUST also be calendar object
        resources (i.e. immediate child of a calendar collection).

        3. The request-uri is a resource, in which case there MUST be
        a single href equal to the request-uri, and MUST be a calendar
        object resource.
    """

    disabled = False
    if self.isPseudoCalendarCollection():
        requestURIis = "calendar"

        # 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 self.inheritedACEsforChildren(request))

        # Check for disabled access
        if filteredaces is None:
            disabled = True

        # Check private events access status
        isowner = (yield self.isOwner(request))

    elif self.isAddressBookCollection():
        requestURIis = "addressbook"

        # 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 self.inheritedACEsforChildren(request))

        # Check for disabled access
        if filteredaces is None:
            disabled = True
        isowner = None

    elif self.isCollection():
        requestURIis = "collection"
        filteredaces = None
        lastParent = None
        isowner = None
    else:
        requestURIis = "resource"
        filteredaces = None
        isowner = None

    if not disabled:

        @inlineCallbacks
        def doResponse():

            # Special for addressbooks
            if collection_type == COLLECTION_TYPE_ADDRESSBOOK:
                if self.isDirectoryBackedAddressBookCollection():
                    result = (yield doDirectoryAddressBookResponse())
                    returnValue(result)

            # Verify that requested resources are immediate children of the request-URI
            valid_names = []
            for href in resources:
                resource_uri = str(href)
                name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                if not self._isChildURI(request, resource_uri):
                    responses.append(
                        davxml.StatusResponse(
                            href,
                            davxml.Status.fromResponseCode(
                                responsecode.BAD_REQUEST)))
                else:
                    valid_names.append(name)
            if not valid_names:
                returnValue(None)

            # Now determine which valid resources are readable and which are not
            ok_resources = []
            bad_resources = []
            missing_resources = []
            unavailable_resources = []
            yield self.findChildrenFaster(
                "1",
                request,
                lambda x, y: ok_resources.append((x, y)),
                lambda x, y: bad_resources.append((x, y)),
                lambda x: missing_resources.append(x),
                lambda x: unavailable_resources.append(x),
                valid_names, (davxml.Read(), ),
                inherited_aces=filteredaces)

            # Get properties for all valid readable resources
            for resource, href in ok_resources:
                try:
                    yield report_common.responseForHref(
                        request,
                        responses,
                        davxml.HRef.fromString(href),
                        resource,
                        propertiesForResource,
                        propertyreq,
                        isowner=isowner)
                except ValueError:
                    log.error(
                        "Invalid calendar resource during multiget: {href}",
                        href=href)
                    responses.append(
                        davxml.StatusResponse(
                            davxml.HRef.fromString(href),
                            davxml.Status.fromResponseCode(
                                responsecode.FORBIDDEN)))
                except ConcurrentModification:
                    # This can happen because of a race-condition between the
                    # time we determine which resources exist and the deletion
                    # of one of these resources in another request.  In this
                    # case, return a 404 for the now missing resource rather
                    # than raise an error for the entire report.
                    log.error("Missing resource during multiget: {href}",
                              href=href)
                    responses.append(
                        davxml.StatusResponse(
                            davxml.HRef.fromString(href),
                            davxml.Status.fromResponseCode(
                                responsecode.NOT_FOUND)))

            # Indicate error for all valid non-readable resources
            for ignore_resource, href in bad_resources:
                responses.append(
                    davxml.StatusResponse(
                        davxml.HRef.fromString(href),
                        davxml.Status.fromResponseCode(
                            responsecode.FORBIDDEN)))

            # Indicate error for all missing/unavailable resources
            for href in missing_resources:
                responses.append(
                    davxml.StatusResponse(
                        davxml.HRef.fromString(href),
                        davxml.Status.fromResponseCode(
                            responsecode.NOT_FOUND)))
            for href in unavailable_resources:
                responses.append(
                    davxml.StatusResponse(
                        davxml.HRef.fromString(href),
                        davxml.Status.fromResponseCode(
                            responsecode.SERVICE_UNAVAILABLE)))

        @inlineCallbacks
        def doDirectoryAddressBookResponse():

            directoryAddressBookLock = None
            try:
                # Verify that requested resources are immediate children of the request-URI
                # and get vCardFilters ;similar to "normal" case below but do not call getChild()
                vCardFilters = []
                valid_hrefs = []
                for href in resources:
                    resource_uri = str(href)
                    resource_name = unquote(
                        resource_uri[resource_uri.rfind("/") + 1:])
                    if self._isChildURI(
                            request, resource_uri) and resource_name.endswith(
                                ".vcf") and len(resource_name) > 4:
                        valid_hrefs.append(href)
                        textMatchElement = carddavxml.TextMatch.fromString(
                            resource_name[:-4])
                        textMatchElement.attributes[
                            "match-type"] = "equals"  # do equals compare. Default is "contains"
                        vCardFilters.append(
                            carddavxml.PropertyFilter(
                                textMatchElement,
                                name="UID",  # attributes
                            ))
                    else:
                        responses.append(
                            davxml.StatusResponse(
                                href,
                                davxml.Status.fromResponseCode(
                                    responsecode.NOT_FOUND)))

                # exit if not valid
                if not vCardFilters or not valid_hrefs:
                    returnValue(None)

                addressBookFilter = carddavxml.Filter(*vCardFilters)
                addressBookFilter = Filter(addressBookFilter)

                # get vCards and filter
                limit = config.DirectoryAddressBook.MaxQueryResults
                results, limited = (yield self.doAddressBookDirectoryQuery(
                    addressBookFilter, propertyreq, limit, defaultKind=None))
                if limited:
                    log.error("Too many results in multiget report: {count}",
                              count=len(resources))
                    raise HTTPError(
                        ErrorResponse(
                            responsecode.FORBIDDEN,
                            (dav_namespace, "number-of-matches-within-limits"),
                            "Too many results",
                        ))

                for href in valid_hrefs:
                    matchingResource = None
                    for vCardResource in results:
                        if href == vCardResource.hRef(
                        ):  # might need to compare urls instead - also case sens ok?
                            matchingResource = vCardResource
                            break

                    if matchingResource:
                        yield report_common.responseForHref(
                            request,
                            responses,
                            href,
                            matchingResource,
                            propertiesForResource,
                            propertyreq,
                            vcard=matchingResource.vCard())
                    else:
                        responses.append(
                            davxml.StatusResponse(
                                href,
                                davxml.Status.fromResponseCode(
                                    responsecode.NOT_FOUND)))
            finally:
                if directoryAddressBookLock:
                    yield directoryAddressBookLock.release()

        if requestURIis == "calendar" or requestURIis == "addressbook":
            yield doResponse()
        else:
            for href in resources:

                resource_uri = str(href)

                # Do href checks
                if requestURIis == "calendar":
                    pass
                elif requestURIis == "addressbook":
                    pass

                # TODO: we can optimize this one in a similar manner to the calendar case
                elif requestURIis == "collection":
                    name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                    if not self._isChildURI(request, resource_uri, False):
                        responses.append(
                            davxml.StatusResponse(
                                href,
                                davxml.Status.fromResponseCode(
                                    responsecode.NOT_FOUND)))
                        continue

                    child = (yield request.locateResource(resource_uri))

                    if not child or not child.exists():
                        responses.append(
                            davxml.StatusResponse(
                                href,
                                davxml.Status.fromResponseCode(
                                    responsecode.NOT_FOUND)))
                        continue

                    parent = (yield child.locateParent(request, resource_uri))

                    if collection_type == COLLECTION_TYPE_CALENDAR:
                        if not parent.isCalendarCollection() or not (
                                yield parent.resourceExists(name)):
                            responses.append(
                                davxml.StatusResponse(
                                    href,
                                    davxml.Status.fromResponseCode(
                                        responsecode.FORBIDDEN)))
                            continue
                    elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
                        if not parent.isAddressBookCollection() or not (
                                yield parent.resourceExists(name)):
                            responses.append(
                                davxml.StatusResponse(
                                    href,
                                    davxml.Status.fromResponseCode(
                                        responsecode.FORBIDDEN)))
                            continue

                    # Check privileges on parent - must have at least DAV:read
                    try:
                        yield parent.checkPrivileges(request,
                                                     (davxml.Read(), ))
                    except AccessDeniedError:
                        responses.append(
                            davxml.StatusResponse(
                                href,
                                davxml.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 private events access status
                        isowner = (yield parent.isOwner(request))
                else:
                    name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
                    if (resource_uri != request.uri) or not self.exists():
                        responses.append(
                            davxml.StatusResponse(
                                href,
                                davxml.Status.fromResponseCode(
                                    responsecode.NOT_FOUND)))
                        continue

                    parent = (yield self.locateParent(request, resource_uri))

                    if collection_type == COLLECTION_TYPE_CALENDAR:
                        if not parent.isPseudoCalendarCollection() or not (
                                yield parent.resourceExists(name)):
                            responses.append(
                                davxml.StatusResponse(
                                    href,
                                    davxml.Status.fromResponseCode(
                                        responsecode.FORBIDDEN)))
                            continue
                    elif collection_type == COLLECTION_TYPE_ADDRESSBOOK:
                        if not parent.isAddressBookCollection() or not (
                                yield parent.resourceExists(name)):
                            responses.append(
                                davxml.StatusResponse(
                                    href,
                                    davxml.Status.fromResponseCode(
                                        responsecode.FORBIDDEN)))
                            continue
                    child = self

                    # 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 private events access status
                    isowner = (yield parent.isOwner(request))

                # Check privileges - must have at least DAV:read
                try:
                    yield child.checkPrivileges(request, (davxml.Read(), ),
                                                inherited_aces=filteredaces)
                except AccessDeniedError:
                    responses.append(
                        davxml.StatusResponse(
                            href,
                            davxml.Status.fromResponseCode(
                                responsecode.FORBIDDEN)))
                    continue

                yield report_common.responseForHref(request,
                                                    responses,
                                                    href,
                                                    child,
                                                    propertiesForResource,
                                                    propertyreq,
                                                    isowner=isowner)

    returnValue(MultiStatusResponse(responses))