You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by rh...@apache.org on 2018/11/07 12:30:11 UTC

svn commit: r1846002 [14/44] - in /subversion/branches/ra-git: ./ build/ build/ac-macros/ build/generator/ build/generator/swig/ build/generator/templates/ build/generator/util/ build/win32/ contrib/client-side/ contrib/client-side/svn_load_dirs/ contr...

Modified: subversion/branches/ra-git/subversion/libsvn_repos/authz.c
URL: http://svn.apache.org/viewvc/subversion/branches/ra-git/subversion/libsvn_repos/authz.c?rev=1846002&r1=1846001&r2=1846002&view=diff
==============================================================================
--- subversion/branches/ra-git/subversion/libsvn_repos/authz.c (original)
+++ subversion/branches/ra-git/subversion/libsvn_repos/authz.c Wed Nov  7 12:30:06 2018
@@ -25,6 +25,7 @@
 
 #include <apr_pools.h>
 #include <apr_file_io.h>
+#include <apr_fnmatch.h>
 
 #include "svn_hash.h"
 #include "svn_pools.h"
@@ -34,946 +35,1592 @@
 #include "svn_repos.h"
 #include "svn_config.h"
 #include "svn_ctype.h"
+#include "private/svn_atomic.h"
 #include "private/svn_fspath.h"
 #include "private/svn_repos_private.h"
+#include "private/svn_sorts_private.h"
+#include "private/svn_subr_private.h"
 #include "repos.h"
+#include "authz.h"
+#include "config_file.h"
 
 
-/*** Structures. ***/
+/*** Access rights. ***/
 
-/* Information for the config enumerators called during authz
-   lookup. */
-struct authz_lookup_baton {
-  /* The authz configuration. */
-  svn_config_t *config;
-
-  /* The user to authorize. */
-  const char *user;
-
-  /* Explicitly granted rights. */
-  svn_repos_authz_access_t allow;
-  /* Explicitly denied rights. */
-  svn_repos_authz_access_t deny;
-
-  /* The rights required by the caller of the lookup. */
-  svn_repos_authz_access_t required_access;
-
-  /* The following are used exclusively in recursive lookups. */
-
-  /* The path in the repository (an fspath) to authorize. */
-  const char *repos_path;
-  /* repos_path prefixed by the repository name and a colon. */
-  const char *qualified_repos_path;
-
-  /* Whether, at the end of a recursive lookup, access is granted. */
-  svn_boolean_t access;
-};
+/* This structure describes the access rights given to a specific user by
+ * a path rule (actually the rule set specified for a path).  I.e. there is
+ * one instance of this per path rule.
+ */
+typedef struct path_access_t
+{
+  /* Sequence number of the path rule that this struct was derived from.
+   * If multiple rules apply to the same path (only possible with wildcard
+   * matching), the one with the highest SEQUENCE_NUMBER wins, i.e. the latest
+   * one defined in the authz file.
+   *
+   * A value of 0 denotes the default rule at the repository root denying
+   * access to everybody.  User-defined path rules start with ID 1.
+   */
+  int sequence_number;
 
-/* Information for the config enumeration functions called during the
-   validation process. */
-struct authz_validate_baton {
-  svn_config_t *config; /* The configuration file being validated. */
-  svn_error_t *err;     /* The error being thrown out of the
-                           enumerator, if any. */
-};
+  /* Access rights of the respective user as defined by the rule set. */
+  authz_access_t rights;
+} path_access_t;
+
+/* Use this to indicate that no sequence ID has been assigned.
+ * It will automatically be inferior to (less than) any other sequence ID. */
+#define NO_SEQUENCE_NUMBER (-1)
+
+/* Convenience structure combining the node-local access rights with the
+ * min and max rights granted within the sub-tree. */
+typedef struct limited_rights_t
+{
+  /* Access granted to the current user.  If the SEQUENCE_NUMBER member is
+   * NO_SEQUENCE_NUMBER, there has been no specific path rule for this PATH
+   * but only for some sub-path(s).  There is always a rule at the root node.
+   */
+  path_access_t access;
 
-/* Currently this structure is just a wrapper around a svn_config_t.
-   Please update authz_pool if you modify this structure. */
-struct svn_authz_t
-{
-  svn_config_t *cfg;
-};
+  /* Minimal access rights that the user has on this or any other node in 
+   * the sub-tree.  This does not take inherited rights into account. */
+  authz_access_t min_rights;
 
+  /* Maximal access rights that the user has on this or any other node in 
+   * the sub-tree.  This does not take inherited rights into account. */
+  authz_access_t max_rights;
 
-
-/*** Checking access. ***/
+} limited_rights_t;
 
-/* Determine whether the REQUIRED access is granted given what authz
- * to ALLOW or DENY.  Return TRUE if the REQUIRED access is
- * granted.
- *
- * Access is granted either when no required access is explicitly
- * denied (implicit grant), or when the required access is explicitly
- * granted, overriding any denials.
- */
+/* Return TRUE, if RIGHTS has local rights defined in the ACCESS member. */
 static svn_boolean_t
-authz_access_is_granted(svn_repos_authz_access_t allow,
-                        svn_repos_authz_access_t deny,
-                        svn_repos_authz_access_t required)
+has_local_rule(const limited_rights_t *rights)
 {
-  svn_repos_authz_access_t stripped_req =
-    required & (svn_authz_read | svn_authz_write);
-
-  if ((deny & required) == svn_authz_none)
-    return TRUE;
-  else if ((allow & required) == stripped_req)
-    return TRUE;
-  else
-    return FALSE;
+  return rights->access.sequence_number != NO_SEQUENCE_NUMBER;
 }
 
-
-/* Decide whether the REQUIRED access has been conclusively
- * determined.  Return TRUE if the given ALLOW/DENY authz are
- * conclusive regarding the REQUIRED authz.
- *
- * Conclusive determination occurs when any of the REQUIRED authz are
- * granted or denied by ALLOW/DENY.
- */
-static svn_boolean_t
-authz_access_is_determined(svn_repos_authz_access_t allow,
-                           svn_repos_authz_access_t deny,
-                           svn_repos_authz_access_t required)
+/* Aggregate the ACCESS spec of TARGET and RIGHTS into TARGET.  I.e. if both
+ * are specified, pick one in accordance to the precedence rules. */
+static void
+combine_access(limited_rights_t *target,
+               const limited_rights_t *rights)
 {
-  if ((deny & required) || (allow & required))
-    return TRUE;
-  else
-    return FALSE;
+  /* This implies the check for NO_SEQUENCE_NUMBER, i.e no rights being
+   * specified. */
+  if (target->access.sequence_number < rights->access.sequence_number)
+    target->access = rights->access;
 }
 
-/* Return TRUE is USER equals ALIAS. The alias definitions are in the
-   "aliases" sections of CFG. Use POOL for temporary allocations during
-   the lookup. */
-static svn_boolean_t
-authz_alias_is_user(svn_config_t *cfg,
-                    const char *alias,
-                    const char *user,
-                    apr_pool_t *pool)
+/* Aggregate the min / max access rights of TARGET and RIGHTS into TARGET. */
+static void
+combine_right_limits(limited_rights_t *target,
+                     const limited_rights_t *rights)
 {
-  const char *value;
-
-  svn_config_get(cfg, &value, "aliases", alias, NULL);
-  if (!value)
-    return FALSE;
+  target->max_rights |= rights->max_rights;
+  target->min_rights &= rights->min_rights;
+}
 
-  if (strcmp(value, user) == 0)
-    return TRUE;
+
 
-  return FALSE;
-}
+/*** Authz cache access. ***/
 
+/* All authz instances currently in use as well as all filtered authz
+ * instances in use will be cached here.
+ * Both caches will be instantiated at most once. */
+static svn_object_pool__t *authz_pool = NULL;
+static svn_object_pool__t *filtered_pool = NULL;
+static svn_atomic_t authz_pool_initialized = FALSE;
 
-/* Return TRUE if USER is in GROUP.  The group definitions are in the
-   "groups" section of CFG.  Use POOL for temporary allocations during
-   the lookup. */
-static svn_boolean_t
-authz_group_contains_user(svn_config_t *cfg,
-                          const char *group,
-                          const char *user,
-                          apr_pool_t *pool)
+/* Implements svn_atomic__err_init_func_t. */
+static svn_error_t *
+synchronized_authz_initialize(void *baton, apr_pool_t *pool)
 {
-  const char *value;
-  apr_array_header_t *list;
-  int i;
+#if APR_HAS_THREADS
+  svn_boolean_t multi_threaded = TRUE;
+#else
+  svn_boolean_t multi_threaded = FALSE;
+#endif
 
-  svn_config_get(cfg, &value, "groups", group, NULL);
+  SVN_ERR(svn_object_pool__create(&authz_pool, multi_threaded, pool));
+  SVN_ERR(svn_object_pool__create(&filtered_pool, multi_threaded, pool));
 
-  list = svn_cstring_split(value, ",", TRUE, pool);
+  return SVN_NO_ERROR;
+}
 
-  for (i = 0; i < list->nelts; i++)
-    {
-      const char *group_user = APR_ARRAY_IDX(list, i, char *);
+svn_error_t *
+svn_repos_authz_initialize(apr_pool_t *pool)
+{
+  /* Protect against multiple calls. */
+  return svn_error_trace(svn_atomic__init_once(&authz_pool_initialized,
+                                               synchronized_authz_initialize,
+                                               NULL, pool));
+}
 
-      /* If the 'user' is a subgroup, recurse into it. */
-      if (*group_user == '@')
-        {
-          if (authz_group_contains_user(cfg, &group_user[1],
-                                        user, pool))
-            return TRUE;
-        }
+/* Return a combination of AUTHZ_KEY and GROUPS_KEY, allocated in RESULT_POOL.
+ * GROUPS_KEY may be NULL.  This is the key for the AUTHZ_POOL.
+ */
+static svn_membuf_t *
+construct_authz_key(const svn_checksum_t *authz_key,
+                    const svn_checksum_t *groups_key,
+                    apr_pool_t *result_pool)
+{
+  svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
+  if (groups_key)
+    {
+      apr_size_t authz_size = svn_checksum_size(authz_key);
+      apr_size_t groups_size = svn_checksum_size(groups_key);
 
-      /* If the 'user' is an alias, verify it. */
-      else if (*group_user == '&')
-        {
-          if (authz_alias_is_user(cfg, &group_user[1],
-                                  user, pool))
-            return TRUE;
-        }
+      svn_membuf__create(result, authz_size + groups_size, result_pool);
+      result->size = authz_size + groups_size; /* exact length is required! */
 
-      /* If the user matches, stop. */
-      else if (strcmp(user, group_user) == 0)
-        return TRUE;
+      memcpy(result->data, authz_key->digest, authz_size);
+      memcpy((char *)result->data + authz_size,
+             groups_key->digest, groups_size);
+    }
+  else
+    {
+      apr_size_t size = svn_checksum_size(authz_key);
+      svn_membuf__create(result, size, result_pool);
+      result->size = size; /* exact length is required! */
+      memcpy(result->data, authz_key->digest, size);
     }
 
-  return FALSE;
+  return result;
 }
 
-
-/* Determines whether an authz rule applies to the current
- * user, given the name part of the rule's name-value pair
- * in RULE_MATCH_STRING and the authz_lookup_baton object
- * B with the username in question.
+/* Return a combination of REPOS_NAME, USER and AUTHZ_ID, allocated in
+ * RESULT_POOL.  USER may be NULL.  This is the key for the FILTERED_POOL.
  */
-static svn_boolean_t
-authz_line_applies_to_user(const char *rule_match_string,
-                           struct authz_lookup_baton *b,
-                           apr_pool_t *pool)
-{
-  /* If the rule has an inversion, recurse and invert the result. */
-  if (rule_match_string[0] == '~')
-    return !authz_line_applies_to_user(&rule_match_string[1], b, pool);
-
-  /* Check for special tokens. */
-  if (strcmp(rule_match_string, "$anonymous") == 0)
-    return (b->user == NULL);
-  if (strcmp(rule_match_string, "$authenticated") == 0)
-    return (b->user != NULL);
-
-  /* Check for a wildcard rule. */
-  if (strcmp(rule_match_string, "*") == 0)
-    return TRUE;
+static svn_membuf_t *
+construct_filtered_key(const char *repos_name,
+                       const char *user,
+                       const svn_membuf_t *authz_id,
+                       apr_pool_t *result_pool)
+{
+  svn_membuf_t *result = apr_pcalloc(result_pool, sizeof(*result));
+  size_t repos_len = strlen(repos_name);
+  size_t user_len = user ? strlen(user) : 1;
+  const char *nullable_user = user ? user : "\0";
+  size_t size = authz_id->size + repos_len + 1 + user_len + 1;
+
+  svn_membuf__create(result, size, result_pool);
+  result->size = size;
+
+  memcpy(result->data, repos_name, repos_len + 1);
+  size = repos_len + 1;
+  memcpy((char *)result->data + size, nullable_user, user_len + 1);
+  size += user_len + 1;
+  memcpy((char *)result->data + size, authz_id->data, authz_id->size);
 
-  /* If we get here, then the rule is:
-   *  - Not an inversion rule.
-   *  - Not an authz token rule.
-   *  - Not a wildcard rule.
-   *
-   * All that's left over is regular user or group specifications.
-   */
-
-  /* If the session is anonymous, then a user/group
-   * rule definitely won't match.
-   */
-  if (b->user == NULL)
-    return FALSE;
-
-  /* Process the rule depending on whether it is
-   * a user, alias or group rule.
-   */
-  if (rule_match_string[0] == '@')
-    return authz_group_contains_user(
-      b->config, &rule_match_string[1], b->user, pool);
-  else if (rule_match_string[0] == '&')
-    return authz_alias_is_user(
-      b->config, &rule_match_string[1], b->user, pool);
-  else
-    return (strcmp(b->user, rule_match_string) == 0);
+  return result;
 }
 
+
+/*** Constructing the prefix tree. ***/
 
-/* Callback to parse one line of an authz file and update the
- * authz_baton accordingly.
+/* Since prefix arrays may have more than one hit, we need to link them
+ * for fast lookup. */
+typedef struct sorted_pattern_t
+{
+  /* The filtered tree node carrying the prefix. */
+  struct node_t *node;
+
+  /* Entry that is a prefix to this one or NULL. */
+  struct sorted_pattern_t *next;
+} sorted_pattern_t;
+
+/* Substructure of node_t.  It contains all sub-node that use patterns
+ * in the next segment level. We keep it separate to save a bit of memory
+ * and to be able to check for pattern presence in a single operation.
  */
-static svn_boolean_t
-authz_parse_line(const char *name, const char *value,
-                 void *baton, apr_pool_t *pool)
+typedef struct node_pattern_t
 {
-  struct authz_lookup_baton *b = baton;
+  /* If not NULL, this represents the "*" follow-segment. */
+  struct node_t *any;
 
-  /* Stop if the rule doesn't apply to this user. */
-  if (!authz_line_applies_to_user(name, b, pool))
-    return TRUE;
+  /* If not NULL, this represents the "**" follow-segment. */
+  struct node_t *any_var;
 
-  /* Set the access grants for the rule. */
-  if (strchr(value, 'r'))
-    b->allow |= svn_authz_read;
+  /* If not NULL, the segments of all sorted_pattern_t in this array are the
+   * prefix part of "prefix*" patterns.  Sorted by segment prefix. */
+  apr_array_header_t *prefixes;
+
+  /* If not NULL, the segments of all sorted_pattern_t in this array are the
+   * reversed suffix part of "*suffix" patterns.  Sorted by reversed
+   * segment suffix. */
+  apr_array_header_t *suffixes;
+
+  /* If not NULL, the segments of all sorted_pattern_t in this array contain
+   * wildcards and don't fit into any of the above categories.
+   * The NEXT members of the elements will not be used. */
+  apr_array_header_t *complex;
+
+  /* This node itself is a "**" segment and must therefore itself be added
+   * to the matching node list for the next level. */
+  svn_boolean_t repeat;
+} node_pattern_t;
+
+/* The pattern tree.  All relevant path rules are being folded into this
+ * prefix tree, with a single, whole segment stored at each node.  The whole
+ * tree applies to a single user only.
+ */
+typedef struct node_t
+{
+  /* The segment as specified in the path rule.  During the lookup tree walk,
+   * this will compared to the respective segment of the path to check. */
+  svn_string_t segment;
+
+  /* Immediate access rights granted by rules on this node and the min /
+   * max rights on any path in this sub-tree. */
+  limited_rights_t rights;
+
+  /* Map of sub-segment(const char *) to respective node (node_t) for all
+   * sub-segments that have rules on themselves or their respective subtrees.
+   * NULL, if there are no rules for sub-paths relevant to the user. */
+  apr_hash_t *sub_nodes;
+
+  /* If not NULL, this contains the pattern-based segment sub-nodes. */
+  node_pattern_t *pattern_sub_nodes;
+} node_t;
+
+/* Create a new tree node for SEGMENT.
+   Note: SEGMENT->pattern is always interned and therefore does not
+   have to be copied into the result pool. */
+static node_t *
+create_node(authz_rule_segment_t *segment,
+            apr_pool_t *result_pool)
+{
+  node_t *result = apr_pcalloc(result_pool, sizeof(*result));
+  if (segment)
+    result->segment = segment->pattern;
   else
-    b->deny |= svn_authz_read;
+    {
+      result->segment.data = "";
+      result->segment.len = 0;
+    }
+  result->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
+  return result;
+}
 
-  if (strchr(value, 'w'))
-    b->allow |= svn_authz_write;
-  else
-    b->deny |= svn_authz_write;
+/* Auto-create a node in *NODE, make it apply to SEGMENT and return it. */
+static node_t *
+ensure_node(node_t **node,
+            authz_rule_segment_t *segment,
+            apr_pool_t *result_pool)
+{
+  if (!*node)
+    *node = create_node(segment, result_pool);
 
-  return TRUE;
+  return *node;
 }
 
-
-/* Return TRUE iff the access rules in SECTION_NAME apply to PATH_SPEC
- * (which is a repository name, colon, and repository fspath, such as
- * "myrepos:/trunk/foo").
+/* compare_func comparing segment names. It takes a sorted_pattern_t* as
+ * VOID_LHS and a const authz_rule_segment_t * as VOID_RHS.
  */
-static svn_boolean_t
-is_applicable_section(const char *path_spec,
-                      const char *section_name)
+static int
+compare_node_rule_segment(const void *void_lhs,
+                          const void *void_rhs)
 {
-  apr_size_t path_spec_len = strlen(path_spec);
+  const sorted_pattern_t *element = void_lhs;
+  const authz_rule_segment_t *segment = void_rhs;
 
-  return ((strncmp(path_spec, section_name, path_spec_len) == 0)
-          && (path_spec[path_spec_len - 1] == '/'
-              || section_name[path_spec_len] == '/'
-              || section_name[path_spec_len] == '\0'));
+  return strcmp(element->node->segment.data, segment->pattern.data);
 }
 
-
-/* Callback to parse a section and update the authz_baton if the
- * section denies access to the subtree the baton describes.
+/* compare_func comparing segment names. It takes a sorted_pattern_t* as
+ * VOID_LHS and a const char * as VOID_RHS.
  */
-static svn_boolean_t
-authz_parse_section(const char *section_name, void *baton, apr_pool_t *pool)
+static int
+compare_node_path_segment(const void *void_lhs,
+                          const void *void_rhs)
 {
-  struct authz_lookup_baton *b = baton;
-  svn_boolean_t conclusive;
+  const sorted_pattern_t *element = void_lhs;
+  const char *segment = void_rhs;
 
-  /* Does the section apply to us? */
-  if (!is_applicable_section(b->qualified_repos_path, section_name)
-      && !is_applicable_section(b->repos_path, section_name))
-    return TRUE;
-
-  /* Work out what this section grants. */
-  b->allow = b->deny = 0;
-  svn_config_enumerate2(b->config, section_name,
-                        authz_parse_line, b, pool);
+  return strcmp(element->node->segment.data, segment);
+}
 
-  /* Has the section explicitly determined an access? */
-  conclusive = authz_access_is_determined(b->allow, b->deny,
-                                          b->required_access);
+/* Make sure a node_t* for SEGMENT exists in *ARRAY and return it.
+ * Auto-create either if they don't exist.  Entries in *ARRAY are
+ * sorted by their segment strings.
+ */
+static node_t *
+ensure_node_in_array(apr_array_header_t **array,
+                     authz_rule_segment_t *segment,
+                     apr_pool_t *result_pool)
+{
+  int idx;
+  sorted_pattern_t entry;
+  sorted_pattern_t *entry_ptr;
 
-  /* Is access granted OR inconclusive? */
-  b->access = authz_access_is_granted(b->allow, b->deny,
-                                      b->required_access)
-    || !conclusive;
+  /* Auto-create the array. */
+  if (!*array)
+    *array = apr_array_make(result_pool, 4, sizeof(sorted_pattern_t));
 
-  /* As long as access isn't conclusively denied, carry on. */
-  return b->access;
-}
+  /* Find the node in ARRAY and the IDX at which it were to be inserted.
+   * Initialize IDX such that we won't attempt a hinted lookup (likely
+   * to fail and therefore pure overhead). */
+  idx = (*array)->nelts;
+  entry_ptr = svn_sort__array_lookup(*array, segment, &idx,
+                                     compare_node_rule_segment);
+  if (entry_ptr)
+    return entry_ptr->node;
 
+  /* There is no such node, yet.
+   * Create one and insert it into the sorted array. */
+  entry.node = create_node(segment, result_pool);
+  entry.next = NULL;
+  svn_sort__array_insert(*array, &entry, idx);
 
-/* Validate access to the given user for the given path.  This
- * function checks rules for exactly the given path, and first tries
- * to access a section specific to the given repository before falling
- * back to pan-repository rules.
- *
- * Update *access_granted to inform the caller of the outcome of the
- * lookup.  Return a boolean indicating whether the access rights were
- * successfully determined.
- */
-static svn_boolean_t
-authz_get_path_access(svn_config_t *cfg, const char *repos_name,
-                      const char *path, const char *user,
-                      svn_repos_authz_access_t required_access,
-                      svn_boolean_t *access_granted,
-                      apr_pool_t *pool)
-{
-  const char *qualified_path;
-  struct authz_lookup_baton baton = { 0 };
-
-  baton.config = cfg;
-  baton.user = user;
-
-  /* Try to locate a repository-specific block first. */
-  qualified_path = apr_pstrcat(pool, repos_name, ":", path, SVN_VA_NULL);
-  svn_config_enumerate2(cfg, qualified_path,
-                        authz_parse_line, &baton, pool);
-
-  *access_granted = authz_access_is_granted(baton.allow, baton.deny,
-                                            required_access);
-
-  /* If the first test has determined access, stop now. */
-  if (authz_access_is_determined(baton.allow, baton.deny,
-                                 required_access))
-    return TRUE;
+  return entry.node;
+}
 
-  /* No repository specific rule, try pan-repository rules. */
-  svn_config_enumerate2(cfg, path, authz_parse_line, &baton, pool);
+/* Auto-create the PATTERN_SUB_NODES sub-structure in *NODE and return it. */
+static node_pattern_t *
+ensure_pattern_sub_nodes(node_t *node,
+                         apr_pool_t *result_pool)
+{
+  if (node->pattern_sub_nodes == NULL)
+    node->pattern_sub_nodes = apr_pcalloc(result_pool,
+                                          sizeof(*node->pattern_sub_nodes));
 
-  *access_granted = authz_access_is_granted(baton.allow, baton.deny,
-                                            required_access);
-  return authz_access_is_determined(baton.allow, baton.deny,
-                                    required_access);
+  return node->pattern_sub_nodes;
 }
 
+/* Combine an ACL rule segment with the corresponding node in our filtered
+ * data model. */
+typedef struct node_segment_pair_t
+{
+  authz_rule_segment_t *segment;
+  node_t *node;
+} node_segment_pair_t;
 
-/* Validate access to the given user for the subtree starting at the
- * given path.  This function walks the whole authz file in search of
- * rules applying to paths in the requested subtree which deny the
- * requested access.
- *
- * As soon as one is found, or else when the whole ACL file has been
- * searched, return the updated authorization status.
- */
-static svn_boolean_t
-authz_get_tree_access(svn_config_t *cfg, const char *repos_name,
-                      const char *path, const char *user,
-                      svn_repos_authz_access_t required_access,
-                      apr_pool_t *pool)
+/* Context object to be used with process_acl. It allows us to re-use
+ * information from previous insertions. */
+typedef struct construction_context_t
 {
-  struct authz_lookup_baton baton = { 0 };
+  /* Array of node_segment_pair_t.  It contains all segments already
+   * processed of the current insertion together with the respective
+   * nodes in our filtered tree.  Before the next lookup, the tree
+   * walk for the common prefix can be skipped. */
+  apr_array_header_t *path;
+} construction_context_t;
 
-  baton.config = cfg;
-  baton.user = user;
-  baton.required_access = required_access;
-  baton.repos_path = path;
-  baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
-                                           ":", path, SVN_VA_NULL);
-  /* Default to access granted if no rules say otherwise. */
-  baton.access = TRUE;
+/* Return a new context object allocated in RESULT_POOL. */
+static construction_context_t *
+create_construction_context(apr_pool_t *result_pool)
+{
+  construction_context_t *result = apr_pcalloc(result_pool, sizeof(*result));
 
-  svn_config_enumerate_sections2(cfg, authz_parse_section,
-                                 &baton, pool);
+  /* Array will be auto-extended but this initial size will make it rarely
+   * ever necessary. */
+  result->path = apr_array_make(result_pool, 32, sizeof(node_segment_pair_t));
 
-  return baton.access;
+  return result;
 }
 
-
-/* Callback to parse sections of the configuration file, looking for
-   any kind of granted access.  Implements the
-   svn_config_section_enumerator2_t interface. */
-static svn_boolean_t
-authz_get_any_access_parser_cb(const char *section_name, void *baton,
-                               apr_pool_t *pool)
+/* Constructor utility:  Below NODE, recursively insert sub-nodes for the
+ * path given as *SEGMENTS of length SEGMENT_COUNT. If matching nodes
+ * already exist, use those instead of creating new ones.  Set the leave
+ * node's access rights spec to PATH_ACCESS.  Update the context info in CTX.
+ */
+static void
+insert_path(construction_context_t *ctx,
+            node_t *node,
+            path_access_t *path_access,
+            int segment_count,
+            authz_rule_segment_t *segment,
+            apr_pool_t *result_pool,
+            apr_pool_t *scratch_pool)
 {
-  struct authz_lookup_baton *b = baton;
+  node_t *sub_node;
+  node_segment_pair_t *node_segment;
 
-  /* Does the section apply to the query? */
-  if (section_name[0] == '/'
-      || strncmp(section_name, b->qualified_repos_path,
-                 strlen(b->qualified_repos_path)) == 0)
-    {
-      b->allow = b->deny = svn_authz_none;
-
-      svn_config_enumerate2(b->config, section_name,
-                            authz_parse_line, baton, pool);
-      b->access = authz_access_is_granted(b->allow, b->deny,
-                                          b->required_access);
-
-      /* Continue as long as we don't find a determined, granted access. */
-      return !(b->access
-               && authz_access_is_determined(b->allow, b->deny,
-                                             b->required_access));
+  /* End of path? */
+  if (segment_count == 0)
+    {
+      /* Set access rights.  Note that there might be multiple rules for
+       * the same path due to non-repo-specific rules vs. repo-specific
+       * ones.  Whichever gets defined last wins.
+       */
+      limited_rights_t rights;
+      rights.access = *path_access;
+      rights.max_rights = path_access->rights;
+      rights.min_rights = path_access->rights;
+      combine_access(&node->rights, &rights);
+      return;
     }
 
-  return TRUE;
-}
+  /* Any wildcards?  They will go into a separate sub-structure. */
+  if (segment->kind != authz_rule_literal)
+    ensure_pattern_sub_nodes(node, result_pool);
 
+  switch (segment->kind)
+    {
+      /* A full wildcard segment? */
+    case authz_rule_any_segment:
+      sub_node = ensure_node(&node->pattern_sub_nodes->any,
+                             segment, result_pool);
+      break;
+
+      /* One or more full wildcard segments? */
+    case authz_rule_any_recursive:
+      sub_node = ensure_node(&node->pattern_sub_nodes->any_var,
+                             segment, result_pool);
+      ensure_pattern_sub_nodes(sub_node, result_pool)->repeat = TRUE;
+      break;
+
+      /* A single wildcard at the end of the segment? */
+    case authz_rule_prefix:
+      sub_node = ensure_node_in_array(&node->pattern_sub_nodes->prefixes,
+                                      segment, result_pool);
+      break;
+
+      /* A single wildcard at the start of segments? */
+    case authz_rule_suffix:
+      sub_node = ensure_node_in_array(&node->pattern_sub_nodes->suffixes,
+                                      segment, result_pool);
+      break;
+
+      /* General pattern? */
+    case authz_rule_fnmatch:
+      sub_node = ensure_node_in_array(&node->pattern_sub_nodes->complex,
+                                      segment, result_pool);
+      break;
+
+      /* Then it must be a literal. */
+    default:
+      SVN_ERR_ASSERT_NO_RETURN(segment->kind == authz_rule_literal);
 
-/* Walk through the authz CFG to check if USER has the REQUIRED_ACCESS
- * to any path within the REPOSITORY.  Return TRUE if so.  Use POOL
- * for temporary allocations. */
-static svn_boolean_t
-authz_get_any_access(svn_config_t *cfg, const char *repos_name,
-                     const char *user,
-                     svn_repos_authz_access_t required_access,
-                     apr_pool_t *pool)
-{
-  struct authz_lookup_baton baton = { 0 };
-
-  baton.config = cfg;
-  baton.user = user;
-  baton.required_access = required_access;
-  baton.access = FALSE; /* Deny access by default. */
-  baton.repos_path = "/";
-  baton.qualified_repos_path = apr_pstrcat(pool, repos_name,
-                                           ":/", SVN_VA_NULL);
-
-  /* We could have used svn_config_enumerate2 for "repos_name:/".
-   * However, this requires access for root explicitly (which the user
-   * may not always have). So we end up enumerating the sections in
-   * the authz CFG and stop on the first match with some access for
-   * this user. */
-  svn_config_enumerate_sections2(cfg, authz_get_any_access_parser_cb,
-                                 &baton, pool);
-
-  /* If walking the configuration was inconclusive, deny access. */
-  if (!authz_access_is_determined(baton.allow,
-                                  baton.deny, baton.required_access))
-    return FALSE;
+      if (!node->sub_nodes)
+        {
+          node->sub_nodes = svn_hash__make(result_pool);
+          sub_node = NULL;
+        }
+      else
+        {
+          sub_node = svn_hash_gets(node->sub_nodes, segment->pattern.data);
+        }
 
-  return baton.access;
-}
+      /* Auto-insert a sub-node for the current segment. */
+      if (!sub_node)
+        {
+          sub_node = create_node(segment, result_pool);
+          apr_hash_set(node->sub_nodes,
+                       sub_node->segment.data,
+                       sub_node->segment.len,
+                       sub_node);
+        }
+    }
 
+  /* Update context. */
+  node_segment = apr_array_push(ctx->path);
+  node_segment->segment = segment;
+  node_segment->node = sub_node;
+
+  /* Continue at the sub-node with the next segment. */
+  insert_path(ctx, sub_node, path_access, segment_count - 1, segment + 1,
+              result_pool, scratch_pool);
+}
 
-
-/*** Validating the authz file. ***/
 
-/* Check for errors in GROUP's definition of CFG.  The errors
- * detected are references to non-existent groups and circular
- * dependencies between groups.  If an error is found, return
- * SVN_ERR_AUTHZ_INVALID_CONFIG.  Use POOL for temporary
- * allocations only.
- *
- * CHECKED_GROUPS should be an empty (it is used for recursive calls).
+/* If the ACL is relevant to the REPOSITORY and user (given as MEMBERSHIPS
+ * plus ANONYMOUS flag), insert the respective nodes into tree starting
+ * at ROOT.  Use the context info of the previous call in CTX to eliminate
+ * repeated lookups.  Allocate new nodes in RESULT_POOL and use SCRATCH_POOL
+ * for temporary allocations.
  */
-static svn_error_t *
-authz_group_walk(svn_config_t *cfg,
-                 const char *group,
-                 apr_hash_t *checked_groups,
-                 apr_pool_t *pool)
+static void
+process_acl(construction_context_t *ctx,
+            const authz_acl_t *acl,
+            node_t *root,
+            const char *repository,
+            const char *user,
+            apr_pool_t *result_pool,
+            apr_pool_t *scratch_pool)
 {
-  const char *value;
-  apr_array_header_t *list;
+  path_access_t path_access;
   int i;
+  node_t *node;
 
-  svn_config_get(cfg, &value, "groups", group, NULL);
-  /* Having a non-existent group in the ACL configuration might be the
-     sign of a typo.  Refuse to perform authz on uncertain rules. */
-  if (!value)
-    return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                             "An authz rule refers to group '%s', "
-                             "which is undefined",
-                             group);
-
-  list = svn_cstring_split(value, ",", TRUE, pool);
-
-  for (i = 0; i < list->nelts; i++)
+  /* Skip ACLs that don't say anything about the current user
+     and/or repository. */
+  if (!svn_authz__get_acl_access(&path_access.rights, acl, user, repository))
+    return;
+
+  /* Insert the rule into the filtered tree. */
+  path_access.sequence_number = acl->sequence_number;
+
+  /* Try to reuse results from previous runs.
+   * Basically, skip the common prefix. */
+  node = root;
+  for (i = 0; i < ctx->path->nelts; ++i)
     {
-      const char *group_user = APR_ARRAY_IDX(list, i, char *);
+      const node_segment_pair_t *step
+        = &APR_ARRAY_IDX(ctx->path, i, const node_segment_pair_t);
 
-      /* If the 'user' is a subgroup, recurse into it. */
-      if (*group_user == '@')
+      /* Exploit the fact that all strings in the authz model are unique /
+       * internized and can be identified by address alone. */
+      if (   !step->node
+          || i >= acl->rule.len
+          || step->segment->kind != acl->rule.path[i].kind
+          || step->segment->pattern.data != acl->rule.path[i].pattern.data)
         {
-          /* A circular dependency between groups is a Bad Thing.  We
-             don't do authz with invalid ACL files. */
-          if (svn_hash_gets(checked_groups, &group_user[1]))
-            return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG,
-                                     NULL,
-                                     "Circular dependency between "
-                                     "groups '%s' and '%s'",
-                                     &group_user[1], group);
-
-          /* Add group to hash of checked groups. */
-          svn_hash_sets(checked_groups, &group_user[1], "");
-
-          /* Recurse on that group. */
-          SVN_ERR(authz_group_walk(cfg, &group_user[1],
-                                   checked_groups, pool));
-
-          /* Remove group from hash of checked groups, so that we don't
-             incorrectly report an error if we see it again as part of
-             another group. */
-          svn_hash_sets(checked_groups, &group_user[1], NULL);
+          ctx->path->nelts = i;
+          break;
         }
-      else if (*group_user == '&')
+      else
         {
-          const char *alias;
-
-          svn_config_get(cfg, &alias, "aliases", &group_user[1], NULL);
-          /* Having a non-existent alias in the ACL configuration might be the
-             sign of a typo.  Refuse to perform authz on uncertain rules. */
-          if (!alias)
-            return svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                     "An authz rule refers to alias '%s', "
-                                     "which is undefined",
-                                     &group_user[1]);
+          node = step->node;
         }
     }
 
-  return SVN_NO_ERROR;
+  /* Insert the path rule into the filtered tree. */
+  insert_path(ctx, node, &path_access,
+              acl->rule.len - i, acl->rule.path + i,
+              result_pool, scratch_pool);
 }
 
-
-/* Callback to perform some simple sanity checks on an authz rule.
- *
- * - If RULE_MATCH_STRING references a group or an alias, verify that
- *   the group or alias definition exists.
- * - If RULE_MATCH_STRING specifies a token (starts with $), verify
- *   that the token name is valid.
- * - If RULE_MATCH_STRING is using inversion, verify that it isn't
- *   doing it more than once within the one rule, and that it isn't
- *   "~*", as that would never match.
- * - Check that VALUE part of the rule specifies only allowed rule
- *   flag characters ('r' and 'w').
- *
- * Return TRUE if the rule has no errors. Use BATON for context and
- * error reporting.
+/* Forward declaration ... */
+static svn_boolean_t
+trim_tree(node_t *node,
+          int latest_any_var,
+          apr_pool_t *scratch_pool);
+
+/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *HASH of
+ * node_t * and remove empty nodes from.  *HASH may be NULL.  If all nodes
+ * could be removed, set *HASH to NULL and return TRUE.  Allocate temporary
+ * data in SCRATCH_POOL.
  */
-static svn_boolean_t authz_validate_rule(const char *rule_match_string,
-                                         const char *value,
-                                         void *baton,
-                                         apr_pool_t *pool)
-{
-  const char *val;
-  const char *match = rule_match_string;
-  struct authz_validate_baton *b = baton;
-
-  /* Make sure the user isn't using double-negatives. */
-  if (match[0] == '~')
+static svn_boolean_t
+trim_subnode_hash(apr_hash_t **hash,
+                  int latest_any_var,
+                  apr_pool_t *scratch_pool)
+{
+  if (*hash)
     {
-      /* Bump the pointer past the inversion for the other checks. */
-      match++;
+      apr_array_header_t *to_remove = apr_array_make(scratch_pool, 0,
+                                                     sizeof(node_t *));
 
-      /* Another inversion is a double negative; we can't not stop. */
-      if (match[0] == '~')
+      apr_hash_index_t *hi;
+      for (hi = apr_hash_first(scratch_pool, *hash);
+           hi;
+           hi = apr_hash_next(hi))
         {
-          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                     "Rule '%s' has more than one "
-                                     "inversion; double negatives are "
-                                     "not permitted.",
-                                     rule_match_string);
-          return FALSE;
+          node_t *node = apr_hash_this_val(hi);
+          if (trim_tree(node, latest_any_var, scratch_pool))
+            APR_ARRAY_PUSH(to_remove, node_t *) = node;
         }
 
-      /* Make sure that the rule isn't "~*", which won't ever match. */
-      if (strcmp(match, "*") == 0)
+      /* Are some nodes left? */
+      if (to_remove->nelts < apr_hash_count(*hash))
         {
-          b->err = svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                    "Authz rules with match string '~*' "
-                                    "are not allowed, because they never "
-                                    "match anyone.");
+          /* Remove empty nodes (if any). */
+          int i;
+          for (i = 0; i < to_remove->nelts; ++i)
+            {
+              node_t *node = APR_ARRAY_IDX(to_remove, i, node_t *);
+              apr_hash_set(*hash, node->segment.data, node->segment.len,
+                           NULL);
+            }
+
           return FALSE;
         }
+
+      /* No nodes left.  A NULL hash is more efficient than an empty one. */
+      *hash = NULL;
     }
 
-  /* If the rule applies to a group, check its existence. */
-  if (match[0] == '@')
-    {
-      const char *group = &match[1];
+  return TRUE;
+}
 
-      svn_config_get(b->config, &val, "groups", group, NULL);
+/* Call trim_tree() with LATEST_ANY_VAR on all elements in the *ARRAY of
+ * node_t * and remove empty nodes from.  *ARRAY may be NULL.  If all nodes
+ * could be removed, set *ARRAY to NULL and return TRUE.  Allocate
+ * temporary data in SCRATCH_POOL.
+ */
+static svn_boolean_t
+trim_subnode_array(apr_array_header_t **array,
+                   int latest_any_var,
+                   apr_pool_t *scratch_pool)
+{
+  if (*array)
+    {
+      int i, dest;
+      for (i = 0, dest = 0; i < (*array)->nelts; ++i)
+        {
+          node_t *node = APR_ARRAY_IDX(*array, i, sorted_pattern_t).node;
+          if (!trim_tree(node, latest_any_var, scratch_pool))
+            {
+              if (i != dest)
+                APR_ARRAY_IDX(*array, dest, sorted_pattern_t)
+                  = APR_ARRAY_IDX(*array, i, sorted_pattern_t);
+              ++dest;
+            }
+        }
 
-      /* Having a non-existent group in the ACL configuration might be
-         the sign of a typo.  Refuse to perform authz on uncertain
-         rules. */
-      if (!val)
+      /* Are some nodes left? */
+      if (dest)
         {
-          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                     "An authz rule refers to group "
-                                     "'%s', which is undefined",
-                                     rule_match_string);
+          /* Trim it to the number of valid entries. */
+          (*array)->nelts = dest;
           return FALSE;
         }
+
+      /* No nodes left.  A NULL array is more efficient than an empty one. */
+      *array = NULL;
     }
 
-  /* If the rule applies to an alias, check its existence. */
-  if (match[0] == '&')
-    {
-      const char *alias = &match[1];
+  return TRUE;
+}
 
-      svn_config_get(b->config, &val, "aliases", alias, NULL);
+/* Remove all rules and sub-nodes from NODE that are fully eclipsed by the
+ * "any-var" rule with sequence number LATEST_ANY_VAR.  Return TRUE, if
+ * there are no rules left in the sub-tree, including NODE.
+ * Allocate temporary data in SCRATCH_POOL.
+ */
+static svn_boolean_t
+trim_tree(node_t *node,
+          int latest_any_var,
+          apr_pool_t *scratch_pool)
+{
+  svn_boolean_t removed_all = TRUE;
 
-      if (!val)
-        {
-          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                     "An authz rule refers to alias "
-                                     "'%s', which is undefined",
-                                     rule_match_string);
-          return FALSE;
-        }
-     }
+  /* For convenience, we allow NODE to be NULL: */
+  if (!node)
+    return TRUE;
 
-  /* If the rule specifies a token, check its validity. */
-  if (match[0] == '$')
+  /* Do we have a later "any_var" rule at this node. */
+  if (   node->pattern_sub_nodes
+      && node->pattern_sub_nodes->any_var
+      &&   node->pattern_sub_nodes->any_var->rights.access.sequence_number
+         > latest_any_var)
     {
-      const char *token_name = &match[1];
+      latest_any_var
+        = node->pattern_sub_nodes->any_var->rights.access.sequence_number;
+    }
 
-      if ((strcmp(token_name, "anonymous") != 0)
-       && (strcmp(token_name, "authenticated") != 0))
-        {
-          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                     "Unrecognized authz token '%s'.",
-                                     rule_match_string);
-          return FALSE;
-        }
+  /* Is there a local rule at this node that is not eclipsed by any_var? */
+  if (has_local_rule(&node->rights))
+    {
+      /* Remove the local rule, if it got eclipsed.
+       * Note that for the latest any_var node, the sequence number is equal. */
+      if (node->rights.access.sequence_number >= latest_any_var)
+        removed_all = FALSE;
+      else
+         node->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
     }
 
-  val = value;
+  /* Process all sub-nodes. */
+  removed_all &= trim_subnode_hash(&node->sub_nodes, latest_any_var,
+                                   scratch_pool);
 
-  while (*val)
+  if (node->pattern_sub_nodes)
     {
-      if (*val != 'r' && *val != 'w' && ! svn_ctype_isspace(*val))
-        {
-          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                     "The character '%c' in rule '%s' is not "
-                                     "allowed in authz rules", *val,
-                                     rule_match_string);
-          return FALSE;
-        }
+      if (trim_tree(node->pattern_sub_nodes->any, latest_any_var,
+                    scratch_pool))
+        node->pattern_sub_nodes->any = NULL;
+      else
+        removed_all = FALSE;
+
+      if (trim_tree(node->pattern_sub_nodes->any_var, latest_any_var,
+                    scratch_pool))
+        node->pattern_sub_nodes->any_var = NULL;
+      else
+        removed_all = FALSE;
 
-      ++val;
+      removed_all &= trim_subnode_array(&node->pattern_sub_nodes->prefixes,
+                                        latest_any_var, scratch_pool);
+      removed_all &= trim_subnode_array(&node->pattern_sub_nodes->suffixes,
+                                        latest_any_var, scratch_pool);
+      removed_all &= trim_subnode_array(&node->pattern_sub_nodes->complex,
+                                        latest_any_var, scratch_pool);
+
+      /* Trim the tree as much as possible to speed up lookup(). */
+      if (removed_all)
+        node->pattern_sub_nodes = NULL;
     }
 
-  return TRUE;
+  return removed_all;
 }
 
-/* Callback to check ALIAS's definition for validity.  Use
-   BATON for context and error reporting. */
-static svn_boolean_t authz_validate_alias(const char *alias,
-                                          const char *value,
-                                          void *baton,
-                                          apr_pool_t *pool)
+/* Forward declaration ... */
+static void
+finalize_tree(node_t *node,
+              limited_rights_t *sum,
+              apr_pool_t *scratch_pool);
+
+/* Call finalize_tree() on all elements in the HASH of node_t *, passing
+ * SUM along. HASH may be NULL. Use SCRATCH_POOL for temporary allocations.
+ */
+static void
+finalize_subnode_hash(apr_hash_t *hash,
+                      limited_rights_t *sum,
+                      apr_pool_t *scratch_pool)
 {
-  /* No checking at the moment, every alias is valid */
-  return TRUE;
+  if (hash)
+    {
+      apr_hash_index_t *hi;
+      for (hi = apr_hash_first(scratch_pool, hash);
+           hi;
+           hi = apr_hash_next(hi))
+        finalize_tree(apr_hash_this_val(hi), sum, scratch_pool);
+    }
 }
 
-
-/* Callback to check GROUP's definition for cyclic dependancies.  Use
-   BATON for context and error reporting. */
-static svn_boolean_t authz_validate_group(const char *group,
-                                          const char *value,
-                                          void *baton,
-                                          apr_pool_t *pool)
+/* Call finalize_up_tree() on all elements in the ARRAY of node_t *,
+ * passing SUM along.  ARRAY may be NULL.  Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+static void
+finalize_subnode_array(apr_array_header_t *array,
+                       limited_rights_t *sum,
+                       apr_pool_t *scratch_pool)
 {
-  struct authz_validate_baton *b = baton;
+  if (array)
+    {
+      int i;
+      for (i = 0; i < array->nelts; ++i)
+        finalize_tree(APR_ARRAY_IDX(array, i, sorted_pattern_t).node, sum,
+                      scratch_pool);
+    }
+}
 
-  b->err = authz_group_walk(b->config, group, apr_hash_make(pool), pool);
-  if (b->err)
-    return FALSE;
+/* Link prefixes within the sorted ARRAY. */
+static void
+link_prefix_patterns(apr_array_header_t *array)
+{
+  int i;
+  if (!array)
+    return;
 
-  return TRUE;
+  for (i = 1; i < array->nelts; ++i)
+    {
+      sorted_pattern_t *prev
+        = &APR_ARRAY_IDX(array, i - 1, sorted_pattern_t);
+      sorted_pattern_t *pattern
+        = &APR_ARRAY_IDX(array, i, sorted_pattern_t);
+
+      /* Does PATTERN potentially have a prefix in ARRAY?
+       * If so, at least the first char must match with the predecessor's
+       * because the array is sorted by that string. */
+      if (prev->node->segment.data[0] != pattern->node->segment.data[0])
+        continue;
+
+      /* Only the predecessor or any of its prefixes can be the closest
+       * prefix to PATTERN. */
+      for ( ; prev; prev = prev->next)
+        if (   prev->node->segment.len < pattern->node->segment.len
+            && !memcmp(prev->node->segment.data,
+                       pattern->node->segment.data,
+                       prev->node->segment.len))
+          {
+            pattern->next = prev;
+            break;
+          }
+    }
 }
 
+/* Recursively finalization the tree node properties for NODE.  Update SUM
+ * (of NODE's parent) by combining it with the recursive access rights info
+ * on NODE.  Use SCRATCH_POOL for temporary allocations.
+ */
+static void
+finalize_tree(node_t *node,
+              limited_rights_t *sum,
+              apr_pool_t *scratch_pool)
+{
+  limited_rights_t *local_sum = &node->rights;
+
+  /* For convenience, we allow NODE to be NULL: */
+  if (!node)
+    return;
 
-/* Callback to check the contents of the configuration section given
-   by NAME.  Use BATON for context and error reporting. */
-static svn_boolean_t authz_validate_section(const char *name,
-                                            void *baton,
-                                            apr_pool_t *pool)
-{
-  struct authz_validate_baton *b = baton;
-
-  /* Use the group checking callback for the "groups" section... */
-  if (strcmp(name, "groups") == 0)
-    svn_config_enumerate2(b->config, name, authz_validate_group,
-                          baton, pool);
-  /* ...and the alias checking callback for "aliases"... */
-  else if (strcmp(name, "aliases") == 0)
-    svn_config_enumerate2(b->config, name, authz_validate_alias,
-                          baton, pool);
-  /* ...but for everything else use the rule checking callback. */
+  /* Sum of rights at NODE - so far. */
+  if (has_local_rule(local_sum))
+    {
+      local_sum->max_rights = local_sum->access.rights;
+      local_sum->min_rights = local_sum->access.rights;
+    }
   else
     {
-      /* Validate the section's name. Skip the optional REPOS_NAME. */
-      const char *fspath = strchr(name, ':');
-      if (fspath)
-        fspath++;
-      else
-        fspath = name;
-      if (! svn_fspath__is_canonical(fspath))
+      local_sum->min_rights = authz_access_write;
+      local_sum->max_rights = authz_access_none;
+    }
+
+  /* Process all sub-nodes. */
+  finalize_subnode_hash(node->sub_nodes, local_sum, scratch_pool);
+
+  if (node->pattern_sub_nodes)
+    {
+      finalize_tree(node->pattern_sub_nodes->any, local_sum, scratch_pool);
+      finalize_tree(node->pattern_sub_nodes->any_var, local_sum, scratch_pool);
+
+      finalize_subnode_array(node->pattern_sub_nodes->prefixes, local_sum,
+                             scratch_pool);
+      finalize_subnode_array(node->pattern_sub_nodes->suffixes, local_sum,
+                             scratch_pool);
+      finalize_subnode_array(node->pattern_sub_nodes->complex, local_sum,
+                             scratch_pool);
+
+      /* Link up the prefixes / suffixes. */
+      link_prefix_patterns(node->pattern_sub_nodes->prefixes);
+      link_prefix_patterns(node->pattern_sub_nodes->suffixes);
+    }
+
+  /* Add our min / max info to the parent's info.
+   * Idempotent for parent == node (happens at root). */
+  combine_right_limits(sum, local_sum);
+}
+
+/* From the authz CONFIG, extract the parts relevant to USER and REPOSITORY.
+ * Return the filtered rule tree.
+ */
+static node_t *
+create_user_authz(authz_full_t *authz,
+                  const char *repository,
+                  const char *user,
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  int i;
+  node_t *root = create_node(NULL, result_pool);
+  construction_context_t *ctx = create_construction_context(scratch_pool);
+
+  /* Use a separate sub-pool to keep memory usage tight. */
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
+
+  /* Find all ACLs for REPOSITORY. 
+   * Note that repo-specific rules replace global rules,
+   * even if they don't apply to the current user. */
+  apr_array_header_t *acls = apr_array_make(subpool, authz->acls->nelts,
+                                            sizeof(authz_acl_t *));
+  for (i = 0; i < authz->acls->nelts; ++i)
+    {
+      const authz_acl_t *acl = &APR_ARRAY_IDX(authz->acls, i, authz_acl_t);
+      if (svn_authz__acl_applies_to_repo(acl, repository))
         {
-          b->err = svn_error_createf(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                                     "Section name '%s' contains non-canonical "
-                                     "fspath '%s'",
-                                     name, fspath);
-          return FALSE;
+          /* ACLs in the AUTHZ are sorted by path and repository.
+           * So, if there is a rule for the repo and a global rule for the
+           * same path, we will detect them here. */
+          if (acls->nelts)
+            {
+              const authz_acl_t *prev_acl
+                = APR_ARRAY_IDX(acls, acls->nelts - 1, const authz_acl_t *);
+              if (svn_authz__compare_paths(&prev_acl->rule, &acl->rule) == 0)
+                {
+                  SVN_ERR_ASSERT_NO_RETURN(!strcmp(prev_acl->rule.repos,
+                                                   AUTHZ_ANY_REPOSITORY));
+                  SVN_ERR_ASSERT_NO_RETURN(strcmp(acl->rule.repos,
+                                                  AUTHZ_ANY_REPOSITORY));
+                  apr_array_pop(acls);
+                }
+            }
+
+          APR_ARRAY_PUSH(acls, const authz_acl_t *) = acl;
         }
+    }
 
-      svn_config_enumerate2(b->config, name, authz_validate_rule,
-                            baton, pool);
+  /* Filtering and tree construction. */
+  for (i = 0; i < acls->nelts; ++i)
+    process_acl(ctx, APR_ARRAY_IDX(acls, i, const authz_acl_t *),
+                root, repository, user, result_pool, subpool);
+
+  /* If there is no relevant rule at the root node, the "no access" default
+   * applies. Give it a SEQUENCE_NUMBER that will never overrule others. */
+  if (!has_local_rule(&root->rights))
+    {
+      root->rights.access.sequence_number = 0;
+      root->rights.access.rights = authz_access_none;
     }
 
-  if (b->err)
-    return FALSE;
+  /* Trim the tree.
+   *
+   * We can't do pattern comparison, so for most pattern rules we cannot
+   * say that a set of rules "eclipses" / overrides a given other set of
+   * rules for all possible paths.  That limits the accuracy of our check
+   * for recursive access in similar ways than for non-pattern rules.
+   *
+   * However, the user expects a rule ending with "**" to eclipse any older
+   * rule in that sub-tree recursively.  So, this trim function removes
+   * eclipsed nodes from the tree.
+   */
+  svn_pool_clear(subpool);
+  trim_tree(root, NO_SEQUENCE_NUMBER, subpool);
 
-  return TRUE;
+  /* Calculate recursive rights. 
+   *
+   * This is a bottom-up calculation of the range of access rights
+   * specified anywhere in  the respective sub-tree, including the base
+   * node itself.
+   *
+   * To prevent additional finalization passes, we piggy-back the addition
+   * of the ordering links of the prefix and suffix sub-node rules.
+   */
+  svn_pool_clear(subpool);
+  finalize_tree(root, &root->rights, subpool);
+
+  /* Done. */
+  svn_pool_destroy(subpool);
+  return root;
 }
 
+
+/*** Lookup. ***/
 
-svn_error_t *
-svn_repos__authz_validate(svn_authz_t *authz, apr_pool_t *pool)
+/* Reusable lookup state object. It is easy to pass to functions and
+ * recycling it between lookups saves significant setup costs. */
+typedef struct lookup_state_t
 {
-  struct authz_validate_baton baton = { 0 };
+  /* Rights immediately applying to this node and limits to the rights to
+   * any sub-path. */
+  limited_rights_t rights;
 
-  baton.err = SVN_NO_ERROR;
-  baton.config = authz->cfg;
+  /* Nodes applying to the path followed so far. */
+  apr_array_header_t *current;
 
-  /* Step through the entire rule file stopping on error. */
-  svn_config_enumerate_sections2(authz->cfg, authz_validate_section,
-                                 &baton, pool);
-  SVN_ERR(baton.err);
+  /* Temporary array containing the nodes applying to the next path
+   * segment (used to build up the next contents of CURRENT). */
+  apr_array_header_t *next;
 
-  return SVN_NO_ERROR;
+  /* Scratch pad for path operations. */
+  svn_stringbuf_t *scratch_pad;
+
+  /* After each lookup iteration, CURRENT and PARENT_RIGHTS will
+   * apply to this path. */
+  svn_stringbuf_t *parent_path;
+
+  /* Rights that apply at PARENT_PATH, if PARENT_PATH is not empty. */
+  limited_rights_t parent_rights;
+
+} lookup_state_t;
+
+/* Constructor for lookup_state_t. */
+static lookup_state_t *
+create_lookup_state(apr_pool_t *result_pool)
+{
+  lookup_state_t *state = apr_pcalloc(result_pool, sizeof(*state));
+ 
+  state->next = apr_array_make(result_pool, 4, sizeof(node_t *));
+  state->current = apr_array_make(result_pool, 4, sizeof(node_t *));
+
+  /* Virtually all path segments should fit into this buffer.  If they
+   * don't, the buffer gets automatically reallocated.
+   *
+   * Using a smaller initial size would be fine as well but does not
+   * buy us much for the increased risk of being expanded anyway - at
+   * some extra cost. */
+  state->scratch_pad = svn_stringbuf_create_ensure(200, result_pool);
+
+  /* Most paths should fit into this buffer.  The same rationale as
+   * above applies. */
+  state->parent_path = svn_stringbuf_create_ensure(200, result_pool);
+
+  return state;
+}
+
+/* Clear the current contents of STATE and re-initialize it for ROOT.
+ * Check whether we can reuse a previous parent path lookup to shorten
+ * the current PATH walk.  Return the full or remaining portion of
+ * PATH, respectively.  PATH must not be NULL. */
+static const char *
+init_lockup_state(lookup_state_t *state,
+                  node_t *root,
+                  const char *path)
+{
+  apr_size_t len = strlen(path);
+  if (   (len > state->parent_path->len)
+      && state->parent_path->len
+      && (path[state->parent_path->len] == '/')
+      && !memcmp(path, state->parent_path->data, state->parent_path->len))
+    {
+      /* The PARENT_PATH of the previous lookup is actually a parent path
+       * of PATH.  The CURRENT node list already matches the parent path
+       * and we only have to set the correct rights info. */
+      state->rights = state->parent_rights;
+
+      /* Tell the caller where to proceed. */
+      return path + state->parent_path->len;
+    }
+
+  /* Start lookup at ROOT for the full PATH. */
+  state->rights = root->rights;
+  state->parent_rights = root->rights;
+
+  apr_array_clear(state->next);
+  apr_array_clear(state->current);
+  APR_ARRAY_PUSH(state->current, node_t *) = root;
+
+  /* Var-segment rules match empty segments as well */
+  if (root->pattern_sub_nodes && root->pattern_sub_nodes->any_var)
+   {
+      node_t *node = root->pattern_sub_nodes->any_var;
+
+      /* This is non-recursive due to ACL normalization. */
+      combine_access(&state->rights, &node->rights);
+      combine_right_limits(&state->rights, &node->rights);
+      APR_ARRAY_PUSH(state->current, node_t *) = node;
+   }
+
+  svn_stringbuf_setempty(state->parent_path);
+  svn_stringbuf_setempty(state->scratch_pad);
+
+  return path;
+}
+
+/* Add NODE to the list of NEXT nodes in STATE.  NODE may be NULL in which
+ * case this is a no-op.  Also update and aggregate the access rights data
+ * for the next path segment.
+ */
+static void
+add_next_node(lookup_state_t *state,
+              node_t *node)
+{
+  /* Allowing NULL nodes simplifies the caller. */
+  if (node)
+    {
+      /* The rule with the highest sequence number is the one that applies.
+       * Not all nodes that we are following have rules that apply directly
+       * to this path but are mere intermediates that may only have some
+       * matching deep sub-node. */
+      combine_access(&state->rights, &node->rights);
+
+      /* The rule tree node can be seen as an overlay of all the nodes that
+       * we are following.  Any of them _may_ match eventually, so the min/
+       * max possible access rights are a combination of all these sub-trees.
+       */
+      combine_right_limits(&state->rights, &node->rights);
+
+      /* NODE is now enlisted as a (potential) match for the next segment. */
+      APR_ARRAY_PUSH(state->next, node_t *) = node;
+
+      /* Variable length sub-segment sequences apply to the same node as
+       * they match empty sequences as well. */
+      if (node->pattern_sub_nodes && node->pattern_sub_nodes->any_var)
+        {
+          node = node->pattern_sub_nodes->any_var;
+
+          /* This is non-recursive due to ACL normalization. */
+          combine_access(&state->rights, &node->rights);
+          combine_right_limits(&state->rights, &node->rights);
+          APR_ARRAY_PUSH(state->next, node_t *) = node;
+        }
+    }
 }
 
+/* If PREFIX is indeed a prefix (or exact match) or SEGMENT, add the
+ * node in PREFIX to STATE. */
+static void
+add_if_prefix_matches(lookup_state_t *state,
+                      const sorted_pattern_t *prefix,
+                      const svn_stringbuf_t *segment)
+{
+  node_t *node = prefix->node;
+  if (   node->segment.len <= segment->len
+      && !memcmp(node->segment.data, segment->data, node->segment.len))
+    add_next_node(state, node);
+}
+
+/* Scan the PREFIXES array of node_t* for all entries whose SEGMENT members
+ * are prefixes of SEGMENT.  Add these to STATE for the next tree level. */
+static void
+add_prefix_matches(lookup_state_t *state,
+                   const svn_stringbuf_t *segment,
+                   apr_array_header_t *prefixes)
+{
+  /* Index of the first node that might be a match.  All matches will
+   * be at this and the immediately following indexes. */
+  int i = svn_sort__bsearch_lower_bound(prefixes, segment->data,
+                                        compare_node_path_segment);
+
+  /* The entry we found may be an exact match (but not a true prefix).
+   * The prefix matching test will still work. */
+  if (i < prefixes->nelts)
+    add_if_prefix_matches(state,
+                          &APR_ARRAY_IDX(prefixes, i, sorted_pattern_t),
+                          segment);
+
+  /* The immediate predecessor may be a true prefix and all potential
+   * prefixes can be found following the NEXT links between the array
+   * indexes. */
+  if (i > 0)
+    {
+      sorted_pattern_t *pattern;
+      for (pattern = &APR_ARRAY_IDX(prefixes, i - 1, sorted_pattern_t);
+           pattern;
+           pattern = pattern->next)
+        {
+          add_if_prefix_matches(state, pattern, segment);
+        }
+    }
+}
 
-/* Retrieve the file at DIRENT (contained in a repo) then parse it as a config
- * file placing the result into CFG_P allocated in POOL.
+/* Scan the PATTERNS array of node_t* for all entries whose SEGMENT members
+ * (usually containing wildcards) match SEGMENT.  Add these to STATE for the
+ * next tree level. */
+static void
+add_complex_matches(lookup_state_t *state,
+                    const svn_stringbuf_t *segment,
+                    apr_array_header_t *patterns)
+{
+  int i;
+  for (i = 0; i < patterns->nelts; ++i)
+    {
+      node_t *node = APR_ARRAY_IDX(patterns, i, sorted_pattern_t).node;
+      if (0 == apr_fnmatch(node->segment.data, segment->data, 0))
+        add_next_node(state, node);
+    }
+}
+
+/* Extract the next segment from PATH and copy it into SEGMENT, whose current
+ * contents get overwritten.  Empty paths ("") are supported and leading '/'
+ * segment separators will be interpreted as an empty segment ("").  Non-
+ * normalizes parts, i.e. sequences of '/', will be treated as a single '/'.
  *
- * If DIRENT cannot be parsed as a config file then an error is returned.  The
- * contents of CFG_P is then undefined.  If MUST_EXIST is TRUE, a missing
- * authz file is also an error.  The CASE_SENSITIVE controls the lookup
- * behavior for section and option names alike.
+ * Return the start of the next segment within PATH, skipping the '/'
+ * separator(s).  Return NULL, if there are no further segments.
  *
- * SCRATCH_POOL will be used for temporary allocations. */
-static svn_error_t *
-authz_retrieve_config_repo(svn_config_t **cfg_p,
-                           const char *dirent,
-                           svn_boolean_t must_exist,
-                           svn_boolean_t case_sensitive,
-                           apr_pool_t *result_pool,
-                           apr_pool_t *scratch_pool)
-{
-  svn_error_t *err;
-  svn_repos_t *repos;
-  const char *repos_root_dirent;
-  const char *fs_path;
-  svn_fs_t *fs;
-  svn_fs_root_t *root;
-  svn_revnum_t youngest_rev;
-  svn_node_kind_t node_kind;
-  svn_stream_t *contents;
-
-  /* Search for a repository in the full path. */
-  repos_root_dirent = svn_repos_find_root_path(dirent, scratch_pool);
-  if (!repos_root_dirent)
-    return svn_error_createf(SVN_ERR_RA_LOCAL_REPOS_NOT_FOUND, NULL,
-                             "Unable to find repository at '%s'", dirent);
-
-  /* Attempt to open a repository at repos_root_dirent. */
-  SVN_ERR(svn_repos_open3(&repos, repos_root_dirent, NULL, scratch_pool,
-                          scratch_pool));
-
-  fs_path = &dirent[strlen(repos_root_dirent)];
-
-  /* Root path is always a directory so no reason to go any further */
-  if (*fs_path == '\0')
-    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
-                             "'/' is not a file in repo '%s'",
-                             repos_root_dirent);
-
-  /* We skip some things that are non-important for how we're going to use
-   * this repo connection.  We do not set any capabilities since none of
-   * the current ones are important for what we're doing.  We also do not
-   * setup the environment that repos hooks would run under since we won't
-   * be triggering any. */
-
-  /* Get the filesystem. */
-  fs = svn_repos_fs(repos);
-
-  /* Find HEAD and the revision root */
-  SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, scratch_pool));
-  SVN_ERR(svn_fs_revision_root(&root, fs, youngest_rev, scratch_pool));
+ * The caller (only called by lookup(), ATM) must ensure that SEGMENT has
+ * enough room to store all of PATH.
+ */
+static const char *
+next_segment(svn_stringbuf_t *segment,
+             const char *path)
+{
+  apr_size_t len;
+  char c;
+
+  /* Read and scan PATH for NUL and '/' -- whichever comes first. */
+  for (len = 0, c = *path; c; c = path[++len])
+    if (c == '/')
+      {
+        /* End of segment. */
+        segment->data[len] = 0;
+        segment->len = len;
+
+        /* If PATH is not normalized, this is where we skip whole sequences
+         * of separators. */
+        while (path[++len] == '/')
+          ;
+
+        /* Continue behind the last separator in the sequence.  We will
+         * treat trailing '/' as indicating an empty trailing segment.
+         * Therefore, we never have to return NULL here. */
+        return path + len;
+      }
+    else
+      {
+        /* Copy segment contents directly into the result buffer.
+         * On many architectures, this is almost or entirely for free. */
+        segment->data[len] = c;
+      }
+
+  /* No separator found, so all of PATH has been the last segment. */
+  segment->data[len] = 0;
+  segment->len = len;
+
+  /* Tell the caller that this has been the last segment. */
+  return NULL;
+}
+
+/* Starting at the respective user's authz root node provided with STATE,
+ * follow PATH and return TRUE, iff the REQUIRED access has been granted to
+ * that user for this PATH.  REQUIRED must not contain svn_authz_recursive.
+ * If RECURSIVE is set, all paths in the sub-tree at and below PATH must
+ * have REQUIRED access.  PATH does not need to be normalized, may be empty
+ * but must not be NULL.
+ */
+static svn_boolean_t
+lookup(lookup_state_t *state,
+       const char *path,
+       authz_access_t required,
+       svn_boolean_t recursive,
+       apr_pool_t *scratch_pool)
+{
+  /* Create a scratch pad large enough to hold any of PATH's segments. */
+  apr_size_t path_len = strlen(path);
+  svn_stringbuf_ensure(state->scratch_pad, path_len);
+
+  /* Normalize start and end of PATH.  Most paths will be fully normalized,
+   * so keep the overhead as low as possible. */
+  if (path_len && path[path_len-1] == '/')
+    {
+      do
+      {
+        --path_len;
+      }
+      while (path_len && path[path_len-1] == '/');
+      path = apr_pstrmemdup(scratch_pool, path, path_len);
+    }
 
-  SVN_ERR(svn_fs_check_path(&node_kind, root, fs_path, scratch_pool));
-  if (node_kind == svn_node_none)
+  while (path[0] == '/')
+    ++path;     /* Don't update PATH_LEN as we won't need it anymore. */
+
+  /* Actually walk the path rule tree following PATH until we run out of
+   * either tree or PATH. */
+  while (state->current->nelts && path)
     {
-      if (!must_exist)
+      apr_array_header_t *temp;
+      int i;
+      svn_stringbuf_t *segment = state->scratch_pad;
+
+      /* Shortcut 1: We could nowhere find enough rights in this sub-tree. */
+      if ((state->rights.max_rights & required) != required)
+        return FALSE;
+
+      /* Shortcut 2: We will find enough rights everywhere in this sub-tree. */
+      if ((state->rights.min_rights & required) == required)
+        return TRUE;
+
+      /* Extract the next segment. */
+      path = next_segment(segment, path);
+
+      /* Initial state for this segment. */
+      apr_array_clear(state->next);
+      state->rights.access.sequence_number = NO_SEQUENCE_NUMBER;
+      state->rights.access.rights = authz_access_none;
+
+      /* These init values ensure that the first node's value will be used
+       * when combined with them.  If there is no first node,
+       * state->access.sequence_number remains unchanged and we will use
+       * the parent's (i.e. inherited) access rights. */
+      state->rights.min_rights = authz_access_write;
+      state->rights.max_rights = authz_access_none;
+
+      /* Update the PARENT_PATH member in STATE to match the nodes in
+       * CURRENT at the end of this iteration, i.e. if and when NEXT
+       * has become CURRENT. */
+      if (path)
         {
-          SVN_ERR(svn_config_create2(cfg_p, case_sensitive, case_sensitive,
-                                     result_pool));
-          return SVN_NO_ERROR;
+          svn_stringbuf_appendbyte(state->parent_path, '/');
+          svn_stringbuf_appendbytes(state->parent_path, segment->data,
+                                    segment->len);
         }
-      else
+
+      /* Scan follow all alternative routes to the next level. */
+      for (i = 0; i < state->current->nelts; ++i)
+        {
+          node_t *node = APR_ARRAY_IDX(state->current, i, node_t *);
+          if (node->sub_nodes)
+            add_next_node(state, apr_hash_get(node->sub_nodes, segment->data,
+                                              segment->len));
+
+          /* Process alternative, wildcard-based sub-nodes. */
+          if (node->pattern_sub_nodes)
+            {
+              add_next_node(state, node->pattern_sub_nodes->any);
+
+              /* If the current node represents a "**" pattern, it matches
+               * to all levels. So, add it to the list for the NEXT level. */
+              if (node->pattern_sub_nodes->repeat)
+                add_next_node(state, node);
+
+              /* Find all prefix pattern matches. */
+              if (node->pattern_sub_nodes->prefixes)
+                add_prefix_matches(state, segment,
+                                   node->pattern_sub_nodes->prefixes);
+
+              if (node->pattern_sub_nodes->complex)
+                add_complex_matches(state, segment,
+                                    node->pattern_sub_nodes->complex);
+
+              /* Find all suffux pattern matches.
+               * This must be the last check as it destroys SEGMENT. */
+              if (node->pattern_sub_nodes->suffixes)
+                {
+                  /* Suffixes behave like reversed prefixes. */
+                  svn_authz__reverse_string(segment->data, segment->len);
+                  add_prefix_matches(state, segment,
+                                     node->pattern_sub_nodes->suffixes);
+                }
+            }
+        }
+
+      /* If no rule applied to this SEGMENT directly, the parent rights
+       * will apply to at least the SEGMENT node itself and possibly
+       * other parts deeper in it's subtree. */
+      if (!has_local_rule(&state->rights))
         {
-          return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
-                                   "'%s' path not found in repo '%s'", fs_path,
-                                   repos_root_dirent);
+          state->rights.access = state->parent_rights.access;
+          state->rights.min_rights &= state->parent_rights.access.rights;
+          state->rights.max_rights |= state->parent_rights.access.rights;
+        }
+
+      /* The list of nodes for SEGMENT is now complete.  If we need to
+       * continue, make it the current and put the old one into the recycler.
+       *
+       * If this is the end of the path, keep the parent path and rights in
+       * STATE as are such that sibling lookups will benefit from it.
+       */
+      if (path)
+        {
+          temp = state->current;
+          state->current = state->next;
+          state->next = temp;
+
+          /* In STATE, PARENT_PATH, PARENT_RIGHTS and CURRENT are now in sync. */
+          state->parent_rights = state->rights;
         }
     }
-  else if (node_kind != svn_node_file)
-    {
-      return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
-                               "'%s' is not a file in repo '%s'", fs_path,
-                               repos_root_dirent);
-    }
-
-  SVN_ERR(svn_fs_file_contents(&contents, root, fs_path, scratch_pool));
-  err = svn_config_parse(cfg_p, contents, case_sensitive, case_sensitive,
-                         result_pool);
-
-  /* Add the URL to the error stack since the parser doesn't have it. */
-  if (err != SVN_NO_ERROR)
-    return svn_error_createf(err->apr_err, err,
-                             "Error while parsing config file: '%s' in repo '%s':",
-                             fs_path, repos_root_dirent);
 
-  return SVN_NO_ERROR;
+  /* If we check recursively, none of the (potential) sub-paths must have
+   * less than the REQUIRED access rights.  "Potential" because we don't
+   * verify that the respective paths actually exist in the repository.
+   */
+  if (recursive)
+    return (state->rights.min_rights & required) == required;
+
+  /* Return whether the access rights on PATH fully include REQUIRED. */
+  return (state->rights.access.rights & required) == required;
 }
 
-svn_error_t *
-svn_repos__retrieve_config(svn_config_t **cfg_p,
-                           const char *path,
-                           svn_boolean_t must_exist,
-                           svn_boolean_t case_sensitive,
-                           apr_pool_t *pool)
-{
-  if (svn_path_is_url(path))
-    {
-      const char *dirent;
-      svn_error_t *err;
-      apr_pool_t *scratch_pool = svn_pool_create(pool);
+
 
-      err = svn_uri_get_dirent_from_file_url(&dirent, path, scratch_pool);
+/*** The authz data structure. ***/
 
-      if (err == SVN_NO_ERROR)
-        err = authz_retrieve_config_repo(cfg_p, dirent, must_exist,
-                                         case_sensitive, pool, scratch_pool);
+/* An entry in svn_authz_t's USER_RULES cache.  All members must be
+ * allocated in the POOL and the latter has to be cleared / destroyed
+ * before overwriting the entries' contents.
+ */
+struct authz_user_rules_t
+{
+  /* User name for which we filtered the rules.
+   * User NULL for the anonymous user. */
+  const char *user;
 
-      /* Close the repos and streams we opened. */
-      svn_pool_destroy(scratch_pool);
+  /* Repository name for which we filtered the rules.
+   * May be empty but never NULL for used entries. */
+  const char *repository;
+
+  /* The combined min/max rights USER has on REPOSITORY. */
+  authz_rights_t global_rights;
+
+  /* Root of the filtered path rule tree.
+   * Will remain NULL until the first usage. */
+  node_t *root;
+
+  /* Reusable lookup state instance. */
+  lookup_state_t *lookup_state;
+
+  /* Pool from which all data within this struct got allocated.
+   * Can be destroyed or cleaned up with no further side-effects. */
+  apr_pool_t *pool;
+};
 
-      return err;
-    }
-  else
+/* Return TRUE, iff AUTHZ matches the pair of REPOS_NAME and USER.
+ * Note that USER may be NULL.
+ */
+static svn_boolean_t
+matches_filtered_tree(const authz_user_rules_t *authz,
+                      const char *repos_name,
+                      const char *user)
+{
+  /* Does the user match? */
+  if (user)
     {
-      /* Outside of repo file or Windows registry*/
-      SVN_ERR(svn_config_read3(cfg_p, path, must_exist, case_sensitive,
-                               case_sensitive, pool));
+      if (authz->user == NULL || strcmp(user, authz->user))
+        return FALSE;
     }
+  else if (authz->user != NULL)
+    return FALSE;
 
-  return SVN_NO_ERROR;
+  /* Does the repository match as well? */
+  return strcmp(repos_name, authz->repository) == 0;
 }
 
-
-/* Callback to copy (name, value) group into the "groups" section
-   of another configuration. */
-static svn_boolean_t
-authz_copy_group(const char *name, const char *value,
-                 void *baton, apr_pool_t *pool)
+/* Check if AUTHZ's already contains a path rule tree filtered for this
+ * USER, REPOS_NAME combination.  If that does not exist, yet, create one
+ * but don't construct the actual filtered tree, yet.
+ */
+static authz_user_rules_t *
+get_user_rules(svn_authz_t *authz,
+               const char *repos_name,
+               const char *user)
 {
-  svn_config_t *authz_cfg = baton;
+  apr_pool_t *pool;
+
+  /* Search our cache for a suitable previously filtered tree. */
+  if (authz->filtered)
+    {
+      /* Is this a suitable filtered tree? */
+      if (matches_filtered_tree(authz->filtered, repos_name, user))
+        return authz->filtered;
+
+      /* Drop the old filtered tree before creating a new one. */
+      svn_pool_destroy(authz->filtered->pool);
+      authz->filtered = NULL;
+    }
 
-  svn_config_set(authz_cfg, SVN_CONFIG_SECTION_GROUPS, name, value);
+  /* Global cache lookup.  Filter the full model only if necessary. */
+  pool = svn_pool_create(authz->pool);
 
-  return TRUE;
+  /* Write a new entry. */
+  authz->filtered = apr_palloc(pool, sizeof(*authz->filtered));
+  authz->filtered->pool = pool;
+  authz->filtered->repository = apr_pstrdup(pool, repos_name);
+  authz->filtered->user = user ? apr_pstrdup(pool, user) : NULL;
+  authz->filtered->lookup_state = create_lookup_state(pool);
+  authz->filtered->root = NULL;
+
+  svn_authz__get_global_rights(&authz->filtered->global_rights,
+                               authz->full, user, repos_name);
+
+  return authz->filtered;
 }
 
-/* Copy group definitions from GROUPS_CFG to the resulting AUTHZ.
- * If AUTHZ already contains any group definition, report an error.
- * Use POOL for temporary allocations. */
+/* In AUTHZ's user rules, construct the actual filtered tree.
+ * Use SCRATCH_POOL for temporary allocations.
+ */
 static svn_error_t *
-authz_copy_groups(svn_authz_t *authz, svn_config_t *groups_cfg,
-                  apr_pool_t *pool)
+filter_tree(svn_authz_t *authz,
+            apr_pool_t *scratch_pool)
 {
-  /* Easy out: we prohibit local groups in the authz file when global
-     groups are being used. */
-  if (svn_config_has_section(authz->cfg, SVN_CONFIG_SECTION_GROUPS))
+  apr_pool_t *pool = authz->filtered->pool;
+  const char *repos_name = authz->filtered->repository;
+  const char *user = authz->filtered->user;
+  node_t *root;
+
+  if (filtered_pool)
     {
-      return svn_error_create(SVN_ERR_AUTHZ_INVALID_CONFIG, NULL,
-                              "Authz file cannot contain any groups "
-                              "when global groups are being used.");
+      svn_membuf_t *key = construct_filtered_key(repos_name, user,
+                                                 authz->authz_id,
+                                                 scratch_pool);
+
+      /* Cache lookup. */
+      SVN_ERR(svn_object_pool__lookup((void **)&root, filtered_pool, key,
+                                      pool));
+
+      if (!root)
+        {
+          apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
+          authz_full_t *add_ref = NULL;
+
+          /* Make sure the underlying full authz object lives as long as the
+           * filtered one that we are about to create.  We do this by adding
+           * a reference to it in ITEM_POOL (which may live longer than AUTHZ).
+           *
+           * Note that we already have a reference to that full authz in
+           * AUTHZ->FULL. Assert that we actually don't created multiple
+           * instances of the same full model.
+           */
+          svn_error_clear(svn_object_pool__lookup((void **)&add_ref,
+                                                  authz_pool, authz->authz_id,
+                                                  item_pool));
+          SVN_ERR_ASSERT(add_ref == authz->full);
+
+          /* Now construct the new filtered tree and cache it. */
+          root = create_user_authz(authz->full, repos_name, user, item_pool,
+                                   scratch_pool);
+          svn_error_clear(svn_object_pool__insert((void **)&root,
+                                                  filtered_pool, key, root,
+                                                  item_pool, pool));
+        }
+     }
+  else
+    {
+      root = create_user_authz(authz->full, repos_name, user, pool,
+                               scratch_pool);
     }
 
-  svn_config_enumerate2(groups_cfg, SVN_CONFIG_SECTION_GROUPS,
-                        authz_copy_group, authz->cfg, pool);
+  /* Write a new entry. */
+  authz->filtered->root = root;
 
   return SVN_NO_ERROR;
 }
 
-svn_error_t *
-svn_repos__authz_read(svn_authz_t **authz_p, const char *path,
-                      const char *groups_path, svn_boolean_t must_exist,
-                      svn_boolean_t accept_urls, apr_pool_t *pool)
-{
-  svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
-
-  /* Load the authz file */
-  if (accept_urls)
-    SVN_ERR(svn_repos__retrieve_config(&authz->cfg, path, must_exist, TRUE,
-                                       pool));
-  else
-    SVN_ERR(svn_config_read3(&authz->cfg, path, must_exist, TRUE, TRUE,
-                             pool));
+
 
+/* Read authz configuration data from PATH into *AUTHZ_P, allocated in
+   RESULT_POOL.  Return the cache key in *AUTHZ_ID.  If GROUPS_PATH is set,
+   use the global groups parsed from it.  Use SCRATCH_POOL for temporary
+   allocations.
+
+   PATH and GROUPS_PATH may be a dirent or an absolute file url.  REPOS_HINT
+   may be specified to speed up access to in-repo authz files.
+
+   If PATH or GROUPS_PATH is not a valid authz rule file, then return
+   SVN_AUTHZ_INVALID_CONFIG.  The contents of *AUTHZ_P is then
+   undefined.  If MUST_EXIST is TRUE, a missing authz or global groups file
+   is also an error. */
+static svn_error_t *
+authz_read(authz_full_t **authz_p,
+           svn_membuf_t **authz_id,
+           const char *path,
+           const char *groups_path,
+           svn_boolean_t must_exist,
+           svn_repos_t *repos_hint,
+           apr_pool_t *result_pool,
+           apr_pool_t *scratch_pool)
+{
+  svn_error_t* err = NULL;
+  svn_stream_t *rules_stream = NULL;
+  svn_stream_t *groups_stream = NULL;
+  svn_checksum_t *rules_checksum = NULL;
+  svn_checksum_t *groups_checksum = NULL;
+
+  config_access_t *config_access =
+    svn_repos__create_config_access(repos_hint, scratch_pool);
+
+  /* Open the main authz file */
+  SVN_ERR(svn_repos__get_config(&rules_stream, &rules_checksum, config_access,
+                                path, must_exist, scratch_pool));
+
+  /* Open the optional groups file */
   if (groups_path)
+    SVN_ERR(svn_repos__get_config(&groups_stream, &groups_checksum,
+                                  config_access, groups_path, must_exist,
+                                  scratch_pool));
+
+  /* The authz cache is optional. */
+  *authz_id = construct_authz_key(rules_checksum, groups_checksum,
+                                  result_pool);
+  if (authz_pool)
     {
-      svn_config_t *groups_cfg;
-      svn_error_t *err;
-
-      /* Load the groups file */
-      if (accept_urls)
-        SVN_ERR(svn_repos__retrieve_config(&groups_cfg, groups_path,
-                                           must_exist, TRUE, pool));
-      else
-        SVN_ERR(svn_config_read3(&groups_cfg, groups_path, must_exist,
-                                 TRUE, TRUE, pool));
+      /* Cache lookup. */
+      SVN_ERR(svn_object_pool__lookup((void **)authz_p, authz_pool,
+                                      *authz_id, result_pool));
 
-      /* Copy the groups from groups_cfg into authz. */
-      err = authz_copy_groups(authz, groups_cfg, pool);
+      /* If not found, parse and add to cache. */
+      if (!*authz_p)
+        {
+          apr_pool_t *item_pool = svn_object_pool__new_item_pool(authz_pool);
 
-      /* Add the paths to the error stack since the authz_copy_groups
-         routine knows nothing about them. */
-      if (err != SVN_NO_ERROR)
-        return svn_error_createf(err->apr_err, err,
-                                 "Error reading authz file '%s' with "
-                                 "groups file '%s':", path, groups_path);
+          /* Parse the configuration(s) and construct the full authz model
+           * from it. */
+          err = svn_authz__parse(authz_p, rules_stream, groups_stream,
+                                item_pool, scratch_pool);
+          if (err != SVN_NO_ERROR)
+            {
+              /* That pool would otherwise never get destroyed. */
+              svn_pool_destroy(item_pool);
+
+              /* Add the URL / file name to the error stack since the parser
+               * doesn't have it. */
+              err = svn_error_quick_wrapf(err,
+                                   "Error while parsing config file: '%s':",
+                                   path);
+            }
+          else
+            {
+              SVN_ERR(svn_object_pool__insert((void **)authz_p, authz_pool,
+                                              *authz_id, *authz_p,
+                                              item_pool, result_pool));
+            }
+        }
+    }
+  else
+    {
+      /* Parse the configuration(s) and construct the full authz model from
+       * it. */
+      err = svn_error_quick_wrapf(svn_authz__parse(authz_p, rules_stream,
+                                                   groups_stream,
+                                                   result_pool, scratch_pool),
+                                  "Error while parsing authz file: '%s':",
+                                  path);
     }
 
-  /* Make sure there are no errors in the configuration. */
-  SVN_ERR(svn_repos__authz_validate(authz, pool));
+  svn_repos__destroy_config_access(config_access);
 
-  *authz_p = authz;
-  return SVN_NO_ERROR;
+  return err;
 }
 
 
@@ -981,12 +1628,22 @@ svn_repos__authz_read(svn_authz_t **auth
 /*** Public functions. ***/
 
 svn_error_t *
-svn_repos_authz_read2(svn_authz_t **authz_p, const char *path,
-                      const char *groups_path, svn_boolean_t must_exist,
-                      apr_pool_t *pool)
+svn_repos_authz_read3(svn_authz_t **authz_p,
+                      const char *path,
+                      const char *groups_path,
+                      svn_boolean_t must_exist,
+                      svn_repos_t *repos_hint,
+                      apr_pool_t *result_pool,
+                      apr_pool_t *scratch_pool)
 {
-  return svn_repos__authz_read(authz_p, path, groups_path, must_exist,
-                               TRUE, pool);
+  svn_authz_t *authz = apr_pcalloc(result_pool, sizeof(*authz));
+  authz->pool = result_pool;
+
+  SVN_ERR(authz_read(&authz->full, &authz->authz_id, path, groups_path,
+                     must_exist, repos_hint, result_pool, scratch_pool));
+
+  *authz_p = authz;
+  return SVN_NO_ERROR;
 }
 
 
@@ -994,29 +1651,20 @@ svn_error_t *
 svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream,
                       svn_stream_t *groups_stream, apr_pool_t *pool)
 {
-  svn_authz_t *authz = apr_palloc(pool, sizeof(*authz));
+  apr_pool_t *scratch_pool = svn_pool_create(pool);
+  svn_authz_t *authz = apr_pcalloc(pool, sizeof(*authz));
+  authz->pool = pool;
+
+  /* Parse the configuration and construct the full authz model from it. */
+  SVN_ERR(svn_authz__parse(&authz->full, stream, groups_stream, pool,
+                           scratch_pool));
 
-  /* Parse the authz stream */
-  SVN_ERR(svn_config_parse(&authz->cfg, stream, TRUE, TRUE, pool));
-

[... 117 lines stripped ...]