def _parse_ldap_criteria(ldap_filter, startidx=0, endidx=-1):
    # type: (str, int, int) -> LDAPCriteria
    """
    Parses an LDAP sub filter (criterion)

    :param ldap_filter: An LDAP filter string
    :param startidx: Sub-filter start index
    :param endidx: Sub-filter end index
    :return: The LDAP sub-filter
    :raise ValueError: Invalid sub-filter
    """
    comparators = "=<>~"
    if startidx < 0:
        raise ValueError(
            "Invalid string range start={0}, end={1}".format(startidx, endidx)
        )

    # Get the comparator
    escaped = False
    idx = startidx
    for char in ldap_filter[startidx:endidx]:
        if not escaped:
            if char == ESCAPE_CHARACTER:
                # Next character escaped
                escaped = True
            elif char in comparators:
                # Comparator found
                break
        else:
            # Escaped character ignored
            escaped = False
        idx += 1
    else:
        # Comparator never found
        raise ValueError(
            "Comparator not found in '{0}'".format(ldap_filter[startidx:endidx])
        )

    # The attribute name can be extracted directly
    attribute_name = ldap_filter[startidx:idx].strip()
    if not attribute_name:
        # Attribute name is missing
        raise ValueError(
            "Attribute name is missing in '{0}'".format(
                ldap_filter[startidx:endidx]
            )
        )

    comparator = _compute_comparator(ldap_filter, idx)
    if comparator is None:
        # Unknown comparator
        raise ValueError(
            "Unknown comparator in '{0}' - {1}\nFilter : {2}".format(
                ldap_filter[startidx:endidx], ldap_filter[idx], ldap_filter
            )
        )

    # Find the end of the comparator
    while ldap_filter[idx] in comparators:
        idx += 1

    # Skip spaces
    idx = _skip_spaces(ldap_filter, idx)

    # Extract the value
    value = ldap_filter[idx:endidx].strip()

    # Use the appropriate comparator if a joker is found in the filter value
    if value == "*":
        # Presence comparator
        comparator = _comparator_presence
    elif "*" in value:
        # Joker
        if comparator == _comparator_eq:
            comparator = _comparator_star
        elif comparator == _comparator_approximate:
            comparator = _comparator_approximate_star

    return LDAPCriteria(
        unescape_LDAP(attribute_name), unescape_LDAP(value), comparator
    )
Parses an LDAP sub filter (criterion)

    :param ldap_filter: An LDAP filter string
    :param startidx: Sub-filter start index
    :param endidx: Sub-filter end index
    :return: The LDAP sub-filter
    :raise ValueError: Invalid sub-filter