httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From gst...@locus.apache.org
Subject cvs commit: apache-2.0/src/lib/sdbm sdbm.c sdbm.h sdbm_hash.c sdbm_lock.c sdbm_pair.c sdbm_pair.h sdbm_tune.h
Date Wed, 28 Jun 2000 08:56:18 GMT
gstein      00/06/28 01:56:06

  Added:       src/main util_xml.c
               src/modules/dav/main dav_dyn.c dav_opaquelock.h mod_dav.c
                        mod_dav.h opaquelock.c props.c util.c util_lock.c
               src/modules/dav/fs dbm.c lock.c repos.c repos.h
               src/lib/sdbm sdbm.c sdbm.h sdbm_hash.c sdbm_lock.c
                        sdbm_pair.c sdbm_pair.h sdbm_tune.h
  Log:
  initial checkin of the new Apache DAV code. this is a pristine copy of
  mod_dav 1.0.1 (tag "V1_0_1" in the mod_dav CVS repository).
  
  For historical information about these files, see the (old) mod_dav web
  site at http://www.webdav.org/mod_dav/. CVS repository information can
  be located from those pages.
  
  Revision  Changes    Path
  1.1                  apache-2.0/src/main/util_xml.c
  
  Index: util_xml.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV extension module for Apache 1.3.*
  **  - XML parser for the body of a request
  **
  ** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
  */
  
  /* James Clark's Expat parser */
  #include <xmlparse.h>
  
  #include "httpd.h"
  #include "http_protocol.h"
  #include "http_log.h"
  
  #include "mod_dav.h"
  
  
  /* errors related to namespace processing */
  #define DAV_NS_ERROR_UNKNOWN_PREFIX	(DAV_NS_ERROR_BASE)
  
  /* test for a namespace prefix that begins with [Xx][Mm][Ll] */
  #define DAV_NS_IS_RESERVED(name) \
  	( (name[0] == 'X' || name[0] == 'x') && \
  	  (name[1] == 'M' || name[1] == 'm') && \
  	  (name[2] == 'L' || name[2] == 'l') )
  
  /* content for parsing */
  typedef struct dav_xml_ctx {
      dav_xml_doc *doc;		/* the doc we're parsing */
      ap_pool *p;			/* the pool we allocate from */
      dav_xml_elem *cur_elem;	/* current element */
  
      int error;			/* an error has occurred */
      /* errors may be DAV_NS_ERROR_* or other errors defined here (none yet) */
  
  } dav_xml_ctx;
  
  /* struct for scoping namespace declarations */
  typedef struct dav_xml_ns_scope {
      const char *prefix;		/* prefix used for this ns */
      int ns;			/* index into namespace table */
      int emptyURI;		/* the namespace URI is the empty string */
      struct dav_xml_ns_scope *next;	/* next scoped namespace */
  } dav_xml_ns_scope;
  
  /* ### need a similar mechanism for xml:lang values */
  
  
  /* return namespace table index for a given prefix */
  static int dav_find_prefix(dav_xml_ctx *ctx, const char *prefix)
  {
      dav_xml_elem *elem = ctx->cur_elem;
  
      /*
      ** Walk up the tree, looking for a namespace scope that defines this
      ** prefix.
      */
      for (; elem; elem = elem->parent) {
  	dav_xml_ns_scope *ns_scope = elem->ns_scope;
  
  	for (ns_scope = elem->ns_scope; ns_scope; ns_scope = ns_scope->next) {
  	    if (strcmp(prefix, ns_scope->prefix) == 0) {
  		if (ns_scope->emptyURI) {
  		    /*
  		    ** It is possible to set the default namespace to an
  		    ** empty URI string; this resets the default namespace
  		    ** to mean "no namespace." We just found the prefix
  		    ** refers to an empty URI, so return "no namespace."
  		    */
  		    return DAV_NS_NONE;
  		}
  
  		return ns_scope->ns;
  	    }
  	}
      }
  
      /*
       * If the prefix is empty (""), this means that a prefix was not
       * specified in the element/attribute. The search that was performed
       * just above did not locate a default namespace URI (which is stored
       * into ns_scope with an empty prefix). This means the element/attribute
       * has "no namespace". We have a reserved value for this.
       */
      if (*prefix == '\0') {
  	return DAV_NS_NONE;
      }
  
      /* not found */
      return DAV_NS_ERROR_UNKNOWN_PREFIX;
  }
  
  static void dav_start_handler(void *userdata, const char *name, const char **attrs)
  {
      dav_xml_ctx *ctx = userdata;
      dav_xml_elem *elem;
      dav_xml_attr *attr;
      dav_xml_attr *prev;
      char *colon;
      const char *quoted;
  
      /* punt once we find an error */
      if (ctx->error)
  	return;
  
      elem = ap_pcalloc(ctx->p, sizeof(*elem));
  
      /* prep the element */
      elem->name = ap_pstrdup(ctx->p, name);
  
      /* fill in the attributes (note: ends up in reverse order) */
      while (*attrs) {
  	attr = ap_palloc(ctx->p, sizeof(*attr));
  	attr->name = ap_pstrdup(ctx->p, *attrs++);
  	attr->value = ap_pstrdup(ctx->p, *attrs++);
  	attr->next = elem->attr;
  	elem->attr = attr;
      }
  
      /* hook the element into the tree */
      if (ctx->cur_elem == NULL) {
  	/* no current element; this also becomes the root */
  	ctx->cur_elem = ctx->doc->root = elem;
      }
      else {
  	/* this element appeared within the current elem */
  	elem->parent = ctx->cur_elem;
  
  	/* set up the child/sibling links */
  	if (elem->parent->last_child == NULL) {
  	    /* no first child either */
  	    elem->parent->first_child = elem->parent->last_child = elem;
  	}
  	else {
  	    /* hook onto the end of the parent's children */
  	    elem->parent->last_child->next = elem;
  	    elem->parent->last_child = elem;
  	}
  
  	/* this element is now the current element */
  	ctx->cur_elem = elem;
      }
  
      /* scan the attributes for namespace declarations */
      for (prev = NULL, attr = elem->attr;
  	 attr;
  	 attr = attr->next) {
  	if (strncmp(attr->name, "xmlns", 5) == 0) {
  	    const char *prefix = &attr->name[5];
  	    dav_xml_ns_scope *ns_scope;
  
  	    /* test for xmlns:foo= form and xmlns= form */
  	    if (*prefix == ':')
  		++prefix;
  	    else if (*prefix != '\0') {
  		/* advance "prev" since "attr" is still present */
  		prev = attr;
  		continue;
  	    }
  
  	    /* quote the URI before we ever start working with it */
  	    quoted = dav_quote_string(ctx->p, attr->value, 1);
  
  	    /* build and insert the new scope */
  	    ns_scope = ap_pcalloc(ctx->p, sizeof(*ns_scope));
  	    ns_scope->prefix = prefix;
  	    ns_scope->ns = dav_insert_uri(ctx->doc->namespaces, quoted);
  	    ns_scope->emptyURI = *quoted == '\0';
  	    ns_scope->next = elem->ns_scope;
  	    elem->ns_scope = ns_scope;
  
  	    /* remove this attribute from the element */
  	    if (prev == NULL)
  		elem->attr = attr->next;
  	    else
  		prev->next = attr->next;
  
  	    /* Note: prev will not be advanced since we just removed "attr" */
  	}
  	else if (strcmp(attr->name, "xml:lang") == 0) {
  	    /* save away the language (in quoted form) */
  	    elem->lang = dav_quote_string(ctx->p, attr->value, 1);
  
  	    /* remove this attribute from the element */
  	    if (prev == NULL)
  		elem->attr = attr->next;
  	    else
  		prev->next = attr->next;
  
  	    /* Note: prev will not be advanced since we just removed "attr" */
  	}
  	else {
  	    /* advance "prev" since "attr" is still present */
  	    prev = attr;
  	}
      }
  
      /*
      ** If an xml:lang attribute didn't exist (lang==NULL), then copy the
      ** language from the parent element (if present).
      **
      ** NOTE: dav_elem_size() *depends* upon this pointer equality.
      */
      if (elem->lang == NULL && elem->parent != NULL)
  	elem->lang = elem->parent->lang;
  
      /* adjust the element's namespace */
      colon = strchr(elem->name, ':');
      if (colon == NULL) {
  	/*
  	 * The element is using the default namespace, which will always
  	 * be found. Either it will be "no namespace", or a default
  	 * namespace URI has been specified at some point.
  	 */
  	elem->ns = dav_find_prefix(ctx, "");
      }
      else if (DAV_NS_IS_RESERVED(elem->name)) {
  	elem->ns = DAV_NS_NONE;
      }
      else {
  	*colon = '\0';
  	elem->ns = dav_find_prefix(ctx, elem->name);
  	elem->name = colon + 1;
  
  	if (DAV_NS_IS_ERROR(elem->ns)) {
  	    ctx->error = elem->ns;
  	    return;
  	}
      }
  
      /* adjust all remaining attributes' namespaces */
      for (attr = elem->attr; attr; attr = attr->next) {
  	colon = strchr(attr->name, ':');
  	if (colon == NULL) {
  	    /*
  	     * Attributes do NOT use the default namespace. Therefore,
  	     * we place them into the "no namespace" category.
  	     */
  	    attr->ns = DAV_NS_NONE;
  	}
  	else if (DAV_NS_IS_RESERVED(attr->name)) {
  	    attr->ns = DAV_NS_NONE;
  	}
  	else {
  	    *colon = '\0';
  	    attr->ns = dav_find_prefix(ctx, attr->name);
  	    attr->name = colon + 1;
  
  	    if (DAV_NS_IS_ERROR(attr->ns)) {
  		ctx->error = attr->ns;
  		return;
  	    }
  	}
      }
  }
  
  static void dav_end_handler(void *userdata, const char *name)
  {
      dav_xml_ctx *ctx = userdata;
  
      /* punt once we find an error */
      if (ctx->error)
  	return;
  
      /* pop up one level */
      ctx->cur_elem = ctx->cur_elem->parent;
  }
  
  static void dav_cdata_handler(void *userdata, const char *data, int len)
  {
      dav_xml_ctx *ctx = userdata;
      dav_xml_elem *elem;
      dav_text_header *hdr;
      const char *s;
  
      /* punt once we find an error */
      if (ctx->error)
  	return;
  
      elem = ctx->cur_elem;
      s = ap_pstrndup(ctx->p, data, len);
  
      if (elem->last_child == NULL) {
  	/* no children yet. this cdata follows the start tag */
  	hdr = &elem->first_cdata;
      }
      else {
  	/* child elements exist. this cdata follows the last child. */
  	hdr = &elem->last_child->following_cdata;
      }
  
      dav_text_append(ctx->p, hdr, s);
  }
  
  int dav_parse_input(request_rec * r, dav_xml_doc **pdoc)
  {
      int result;
      dav_xml_ctx ctx =
      {0};
      XML_Parser parser;
  
      if ((result = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK)
  	return result;
  
      if (r->remaining == 0) {
  	*pdoc = NULL;
  	return OK;
      }
  
      ctx.p = r->pool;
      ctx.doc = ap_pcalloc(ctx.p, sizeof(*ctx.doc));
  
      ctx.doc->namespaces = ap_make_array(ctx.p, 5, sizeof(const char *));
      dav_insert_uri(ctx.doc->namespaces, "DAV:");
  
      /* ### we should get the encoding from Content-Encoding */
      parser = XML_ParserCreate(NULL);
      if (parser == NULL) {
  	/* ### anything better to do? */
  	fprintf(stderr, "Ouch!  XML_ParserCreate() failed!\n");
  	exit(1);
      }
  
      XML_SetUserData(parser, (void *) &ctx);
      XML_SetElementHandler(parser, dav_start_handler, dav_end_handler);
      XML_SetCharacterDataHandler(parser, dav_cdata_handler);
  
      if (ap_should_client_block(r)) {
  	long len;
  	char *buffer;
  	char end;
  	int rv;
  	size_t total_read = 0;
  	size_t limit_xml_body = dav_get_limit_xml_body(r);
  
  	/* allocate our working buffer */
  	buffer = ap_palloc(r->pool, DAV_READ_BLOCKSIZE);
  
  	/* read the body, stuffing it into the parser */
  	while ((len = ap_get_client_block(r, buffer, DAV_READ_BLOCKSIZE)) > 0) {
  	    total_read += len;
  	    if (limit_xml_body && total_read > limit_xml_body) {
  		ap_log_rerror(APLOG_MARK, APLOG_NOERRNO | APLOG_ERR, r,
  			      "XML request body is larger than the configured "
  			      "limit of %lu", (unsigned long)limit_xml_body);
  		goto read_error;
  	    }
  
  	    rv = XML_Parse(parser, buffer, len, 0);
  	    if (rv == 0)
  		goto parser_error;
  	}
  	if (len == -1) {
  	    /* ap_get_client_block() has logged an error */
  	    goto read_error;
  	}
  
  	/* tell the parser that we're done */
  	rv = XML_Parse(parser, &end, 0, 1);
  	if (rv == 0)
  	    goto parser_error;
      }
  
      XML_ParserFree(parser);
  
      if (ctx.error) {
  	switch (ctx.error) {
  	case DAV_NS_ERROR_UNKNOWN_PREFIX:
  	    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  			  "An undefined namespace prefix was used.");
  	    break;
  
  	default:
  	    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  			  "There was an error within the XML request body.");
  	    break;
  	}
  
  	/* Apache will supply a default error, plus the error log above. */
  	return HTTP_BAD_REQUEST;
      }
  
      /* ### assert: ctx.cur_elem == NULL */
  
      *pdoc = ctx.doc;
  
      return OK;
  
    parser_error:
      {
  	enum XML_Error err = XML_GetErrorCode(parser);
  
  	/* ### fix this error message (default vs special) */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "XML parser error code: %s (%d).",
  		      XML_ErrorString(err), err);
  
  	XML_ParserFree(parser);
  
  	/* Apache will supply a default error, plus the error log above. */
  	return HTTP_BAD_REQUEST;
      }
  
    read_error:
      XML_ParserFree(parser);
  
      /* Apache will supply a default error, plus whatever was logged. */
      return HTTP_BAD_REQUEST;
  }
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/dav_dyn.c
  
  Index: dav_dyn.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV extension module for Apache 1.3.*
  **  - handle dynamic DAV extensions
  **
  ** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
  */
  
  #include "mod_dav.h"
  
  #include "http_log.h"
  
  /*
  ** Hold the runtime information associated with a module.
  **
  ** One of these will exist per loaded module.
  */
  typedef struct dav_dyn_runtime
  {
      void *handle;		/* DSO handle */
      int index;			/* unique index for module */
  
      const dav_dyn_module *module;	/* dynamic module info */
  
      void *m_context;		/* module-level ctx (i.e. managed globals) */
  
      int num_providers;		/* number of providers in module */
      int **ns_maps;		/* map providers' URIs to global URIs */
  
      struct dav_dyn_runtime *next;
  } dav_dyn_runtime;
  
  /*
  ** Hold the runtime information associated with a provider.
  **
  ** One per dir/loc per provider.
  */
  typedef struct dav_dyn_prov_ctx
  {
      int active;		/* is this provider active? */
  
      const dav_dyn_provider *provider;
  
  } dav_dyn_prov_ctx;
  
  /*
  ** Hold the runtime information associated with a directory/location and
  ** a module.
  **
  ** One per dir/loc per module.
  */
  typedef struct dav_dyn_mod_ctx
  {
      dav_dyn_runtime *runtime;
  
      int state;				/* rolled up "active" state */
  #define DAV_DYN_STATE_UNUSED	0
  #define DAV_DYN_STATE_POTENTIAL	1	/* module's params seen */
  #define DAV_DYN_STATE_HAS_CTX	2	/* per-dir context exists */
  #define DAV_DYN_STATE_ACTIVE	3	/* a provider is active */
  
      void *d_context;			/* per-directory context */
  
      dav_dyn_prov_ctx *pc;		/* array of provider context */
  
      struct dav_dyn_mod_ctx *next;
  } dav_dyn_mod_ctx;
  
  typedef struct
  {
      pool *p;
  
      const dav_dyn_module *mod;
      const dav_dyn_runtime *runtime;
  
      int index;
      const dav_dyn_provider *provider;
  
  } dav_provider_scan_ctx;
  
  
  /* ### needs thread protection */
  static int dav_loaded_count = 0;
  static dav_dyn_runtime *dav_loaded_modules = NULL;
  
  /* record the namespaces found in all liveprop providers in all modules */
  array_header *dav_liveprop_uris = NULL;
  
  
  /* builtin, default module for filesystem-based access */
  extern const dav_dyn_module dav_dyn_module_default;
  
  
  int dav_load_module(const char *name, const char *module_sym,
  		    const char *filename)
  {
      return 0;
  }
  
  const dav_dyn_module *dav_find_module(const char *name)
  {
      /* name == NULL means the default. */
  
      /* ### look through dav_loaded_modules for the right module */
  
      return &dav_dyn_module_default;
  }
  
  static void dav_cleanup_liveprops(void *ptr)
  {
      /* DBG1("dav_cleanup_liveprops (0x%08ld)", (unsigned long)dav_liveprop_uris); */
  
      dav_liveprop_uris = NULL;
  }
  
  /* return a mapping from namespace_uris to dav_liveprop_uris */
  int * dav_collect_liveprop_uris(pool *p, const dav_hooks_liveprop *hooks)
  {
      int count = 0;
      const char * const * p_uri;
      int *ns_map;
      int *cur_map;
  
      for (p_uri = hooks->namespace_uris; *p_uri != NULL; ++p_uri)
  	++count;
      ns_map = ap_palloc(p, count * sizeof(*ns_map));
  
      /* clean up this stuff if the pool goes away */
      ap_register_cleanup(p, NULL, dav_cleanup_liveprops, dav_cleanup_liveprops);
  
      if (dav_liveprop_uris == NULL) {
  	dav_liveprop_uris = ap_make_array(p, 5, sizeof(const char *));
  	(void) dav_insert_uri(dav_liveprop_uris, "DAV:");
      }
  
      for (cur_map = ns_map, p_uri = hooks->namespace_uris; *p_uri != NULL; ) {
  	*cur_map++ = dav_insert_uri(dav_liveprop_uris, *p_uri++);
  
  	/* DBG2("collect_liveprop: local %d  =>  global %d",
  	     p_uri - hooks->namespace_uris - 1, *(cur_map - 1));
  	*/
      }
  
      return ns_map;
  }
  
  static void dav_cleanup_module(void *ptr)
  {
      const dav_dyn_runtime *ddr = ptr;
      dav_dyn_runtime *scan;
  
      /* DBG2("cleanup 0x%08lx  (mod=0x%08lx)", (unsigned long)ddr, (unsigned long)ddr->module); */
  
      --dav_loaded_count;
      /* DBG1("dav_loaded_count=%d", dav_loaded_count); */
  
      if (ddr == dav_loaded_modules) {
          dav_loaded_modules = dav_loaded_modules->next;
          return;
      }
  
      for (scan = dav_loaded_modules; scan->next == ddr; scan = scan->next)
          ;
      scan->next = scan->next->next;
  }
  
  void dav_process_module(pool *p, const dav_dyn_module *mod)
  {
      dav_dyn_runtime *ddr = ap_pcalloc(p, sizeof(*ddr));
      int count = 0;
      const dav_dyn_provider *provider = mod->providers;
      int i;
  
      for (provider = mod->providers;
  	 provider->type != DAV_DYN_TYPE_SENTINEL;
  	 ++provider)
  	++count;
  
      ddr->index = ++dav_loaded_count;
      ddr->module = mod;
      ddr->num_providers = count;
      ddr->ns_maps = ap_pcalloc(p, count * sizeof(*ddr->ns_maps));
  
      ddr->next = dav_loaded_modules;
      dav_loaded_modules = ddr;
      ap_register_cleanup(p, ddr, dav_cleanup_module, dav_cleanup_module);
  
      /* DBG2("alloc 0x%08lx  (mod=0x%08lx)", (unsigned long)ddr, (unsigned long)mod); */
  
      for (provider = mod->providers, i = 0;
  	 provider->type != DAV_DYN_TYPE_SENTINEL;
  	 ++provider, ++i) {
  
  	/* ### null provider? */
  	if (provider->hooks == NULL)
  	    continue;
  
  	/* process any LIVEPROP providers */
  	if (provider->type == DAV_DYN_TYPE_LIVEPROP) {
  	    ddr->ns_maps[i] = dav_collect_liveprop_uris(p, DAV_AS_HOOKS_LIVEPROP(provider));
  	}
      }
  
      /* DBG1("dav_loaded_count=%d", dav_loaded_count); */
  }
  
  void dav_process_builtin_modules(pool *p)
  {
      const dav_dyn_module *mod;
  
      /* ### how to loop over these? */
      mod = &dav_dyn_module_default;
  
      while (mod != NULL) {
  	dav_process_module(p, mod);
  
  	/* ### how to see the next builtin? */
  	mod = NULL;
      }
  }
  
  void *dav_prepare_scan(pool *p, const dav_dyn_module *mod)
  {
      dav_provider_scan_ctx *dpsc = ap_pcalloc(p, sizeof(*dpsc));
      const dav_dyn_runtime *ddr;
  
      /*
      ** create_dir_config is called before init_handler, so we need to
      ** process the builtin modules right now.
      */
      if (dav_loaded_modules == NULL || dav_liveprop_uris == NULL) {
          /* DBG0("reloading during scan"); */
  	dav_process_builtin_modules(p);
      }
  
      for (ddr = dav_loaded_modules; ddr != NULL; ddr = ddr->next) {
  	if (ddr->module == mod)
  	    break;
      }
      if (ddr == NULL) {
  	/* ### we don't know about that module! */
  	return NULL;
      }
  
      dpsc->p = p;
      dpsc->mod = mod;
      dpsc->provider = mod->providers;
      dpsc->runtime = ddr;
  
      return dpsc;
  }
  
  int dav_scan_providers(void *ctx,
  		       const dav_dyn_provider **provider,
  		       dav_dyn_hooks *output)
  {
      dav_provider_scan_ctx *dpsc = ctx;
      int idx;
  
      *provider = dpsc->provider++;
      if ((*provider)->type == DAV_DYN_TYPE_SENTINEL) {
  	return 1;	/* end of list */
      }
  
      idx = dpsc->index++;
  
      memset(output, 0, sizeof(*output));
      output->ctx.id = (*provider)->id;
      output->ctx.m_context = dpsc->runtime->m_context;
      output->ctx.ns_map = dpsc->runtime->ns_maps[idx];
      output->hooks = (*provider)->hooks;
  
      return 0;
  }
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/dav_opaquelock.h
  
  Index: dav_opaquelock.h
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV opaquelocktoken scheme implementation
  **
  ** Written 5/99 by Keith Wannamaker, wannamak@us.ibm.com
  ** Adapted from ISO/DCE RPC spec and a former Internet Draft
  ** by Leach and Salz:
  ** http://www.ics.uci.edu/pub/ietf/webdav/uuid-guid/draft-leach-uuids-guids-01
  **
  ** Portions of the code are covered by the following license:
  **
  ** Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc.
  ** Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. &
  ** Digital Equipment Corporation, Maynard, Mass.
  ** Copyright (c) 1998 Microsoft.
  ** To anyone who acknowledges that this file is provided "AS IS"
  ** without any express or implied warranty: permission to use, copy,
  ** modify, and distribute this file for any purpose is hereby
  ** granted without fee, provided that the above copyright notices and
  ** this notice appears in all source code copies, and that none of
  ** the names of Open Software Foundation, Inc., Hewlett-Packard
  ** Company, or Digital Equipment Corporation be used in advertising
  ** or publicity pertaining to distribution of the software without
  ** specific, written prior permission.  Neither Open Software
  ** Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital Equipment
  ** Corporation makes any representations about the suitability of
  ** this software for any purpose.
  */
  
  #ifndef _DAV_OPAQUELOCK_H_
  #define _DAV_OPAQUELOCK_H_
  
  #ifdef __cplusplus
  extern "C" {
  #endif
  
  typedef unsigned long   unsigned32;
  typedef unsigned short  unsigned16;
  typedef unsigned char   unsigned8;
  
  typedef struct {
      char nodeID[6];
  } uuid_node_t;
  
  #undef uuid_t
  
  typedef struct _uuid_t
  {
      unsigned32	time_low;
      unsigned16	time_mid;
      unsigned16	time_hi_and_version;
      unsigned8	clock_seq_hi_and_reserved;
      unsigned8	clock_seq_low;
      unsigned8	node[6];
  } uuid_t;
  
  /* data type for UUID generator persistent state */
  	
  typedef struct {
      uuid_node_t node;     /* saved node ID */
      unsigned16 cs;        /* saved clock sequence */
  } uuid_state;
  
  /* in dav_opaquelock.c */
  void dav_create_opaquelocktoken(uuid_state *st, uuid_t *u);
  void dav_create_uuid_state(uuid_state *st);
  const char *dav_format_opaquelocktoken(pool *p, const uuid_t *u);
  int dav_compare_opaquelocktoken(const uuid_t a, const uuid_t b);
  int dav_parse_opaquelocktoken(const char *char_token, uuid_t *bin_token);
  
  /* in mod_dav.c */
  uuid_state *dav_get_uuid_state(const request_rec *r);
  
  #ifdef __cplusplus
  }
  #endif
  
  #endif /* _DAV_OPAQUELOCK_H_ */
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/mod_dav.c
  
  Index: mod_dav.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV extension module for Apache 1.3.*
  ** This module is repository-independent. It depends on hooks provided by a
  ** repository implementation.
  **
  ** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
  **
  ** APACHE ISSUES:
  **   - within a DAV hierarchy, if an unknown method is used and we default
  **     to Apache's implementation, it sends back an OPTIONS with the wrong
  **     set of methods -- there is NO HOOK for us.
  **     therefore: we need to manually handle the HTTP_METHOD_NOT_ALLOWED
  **       and HTTP_NOT_IMPLEMENTED responses (not ap_send_error_response).
  **   - process_mkcol_body() had to dup code from ap_setup_client_block().
  **   - it would be nice to get status lines from Apache for arbitrary
  **     status codes
  **   - it would be nice to be able to extend Apache's set of response
  **     codes so that it doesn't return 500 when an unknown code is placed
  **     into r->status.
  **   - http_vhost functions should apply "const" to their params
  **
  ** DESIGN NOTES:
  **   - For PROPFIND, we batch up the entire response in memory before
  **     sending it. We may want to reorganize around sending the information
  **     as we suck it in from the propdb. Alternatively, we should at least
  **     generate a total Content-Length if we're going to buffer in memory
  **     so that we can keep the connection open.
  */
  
  #include "httpd.h"
  #include "http_config.h"
  #include "http_core.h"
  #include "http_log.h"
  #include "http_main.h"
  #include "http_protocol.h"
  #include "http_request.h"
  #include "util_script.h"
  
  #include "mod_dav.h"
  
  #include "dav_opaquelock.h"
  
  
  enum {
      DAV_ENABLED_UNSET = 0,
      DAV_ENABLED_OFF,
      DAV_ENABLED_ON
  };
  
  /* per-dir configuration */
  typedef struct {
      int enabled;
      const char *dir;
      int locktimeout;
      int handle_get;		/* cached from repository hook structure */
      int allow_depthinfinity;
      long limit_xml_body;
  
      table *d_params;		/* per-directory DAV config parameters */
      struct dav_dyn_mod_ctx *dmc;
  
      dav_dyn_hooks propdb;
      dav_dyn_hooks locks;
      dav_dyn_hooks *liveprop;
      dav_dyn_hooks repository;
      dav_dyn_hooks vsn;
  } dav_dir_conf;
  
  /* per-server configuration */
  typedef struct {
      const char *lockdb_path;	/* lock database path */
  
      uuid_state st;		/* UUID state for opaquelocktoken */
  
  } dav_server_conf;
  
  #define DAV_INHERIT_VALUE(parent, child, field) \
  		((child)->field ? (child)->field : (parent)->field)
  
  /* LimitXMLRequestBody handling */
  #define DAV_LIMIT_UNSET                 ((long) -1)
  #define DAV_DEFAULT_LIMIT_XML_BODY      ((size_t)1000000)
  
  
  /* forward-declare for use in configuration lookup */
  extern module MODULE_VAR_EXPORT dav_module;
  
  /* copy a module's providers into our per-directory configuration state */
  static void dav_copy_providers(pool *p, const char *name, dav_dir_conf *conf)
  {
      const dav_dyn_module *mod;
      const dav_dyn_provider *provider;
      dav_dyn_hooks hooks;
      void *ctx;
  
      mod = dav_find_module(name);
      /* ### if NULL? need to error out somehow... */
  
      /* Set hooks for any providers in the module */
      ctx = dav_prepare_scan(p, mod);
      if (ctx == NULL) {
  	/* ### how to signal an error? */
  	return;
      }
  
      while (!dav_scan_providers(ctx, &provider, &hooks)) {
  
  	switch (provider->type) {
  
  	case DAV_DYN_TYPE_PROPDB:
  	    conf->propdb = hooks;
  	    break;
  
  	case DAV_DYN_TYPE_LOCKS:
  	    conf->locks = hooks;
  	    break;
  
  	case DAV_DYN_TYPE_QUERY_GRAMMAR:
  	    /* ### not yet defined */
  	    break;
  
  	case DAV_DYN_TYPE_ACL:
  	    /* ### not yet defined */
  	    break;
  
  	case DAV_DYN_TYPE_VSN:
  	    conf->vsn = hooks;
  	    break;
  
  	case DAV_DYN_TYPE_REPOSITORY:
  	    conf->repository = hooks;
  	    conf->handle_get = DAV_AS_HOOKS_REPOSITORY(&hooks)->handle_get;
  	    break;
  
  	case DAV_DYN_TYPE_LIVEPROP:
  	{
  	    dav_dyn_hooks *ddh = ap_palloc(p, sizeof(*ddh));
  
  	    *ddh = hooks;
  	    ddh->next = conf->liveprop;
  	    conf->liveprop = ddh;
  	    break;
  	}
  
  	default:
  	    /* ### need to error out somehow... */
  	    break;
  	}
      }
  }
  
  static void dav_init_handler(server_rec *s, pool *p)
  {
      /* DBG0("dav_init_handler"); */
  
      ap_add_version_component("DAV/" DAV_VERSION);
  
      dav_process_builtin_modules(p);
  }
  
  static void *dav_create_server_config(pool *p, server_rec *s)
  {
      dav_server_conf *newconf;
  
      newconf = (dav_server_conf *) ap_pcalloc(p, sizeof(*newconf));
  
      newconf->lockdb_path = NULL;
      dav_create_uuid_state(&newconf->st);
  
      return newconf;
  }
  
  static void *dav_merge_server_config(pool *p, void *base, void *overrides)
  {
      dav_server_conf *parent = base;
      dav_server_conf *child = overrides;
      dav_server_conf *newconf;
  
      newconf = (dav_server_conf *) ap_pcalloc(p, sizeof(*newconf));
  
      newconf->lockdb_path = DAV_INHERIT_VALUE(parent, child, lockdb_path);
  
      memcpy(&newconf->st, &child->st, sizeof(newconf->st));
  
      return newconf;
  }
  
  static void *dav_create_dir_config(pool *p, char *dir)
  {
      /* NOTE: dir==NULL creates the default per-dir config */
  
      dav_dir_conf *conf;
  
      conf = (dav_dir_conf *) ap_pcalloc(p, sizeof(*conf));
      conf->dir = ap_pstrdup(p, dir);
      conf->d_params = ap_make_table(p, 1);
      conf->limit_xml_body = DAV_LIMIT_UNSET;
  
      /* DBG1("dav_create_dir_config: %08lx", (long)conf); */
  
      /*
      ** Locate the appropriate module (NULL == default) and copy the module's
      ** providers' hooks into our configuration state.
      */
      dav_copy_providers(p, NULL, conf);
  
      return conf;
  }
  
  static void *dav_merge_dir_config(pool *p, void *base, void *overrides)
  {
      dav_dir_conf *parent = base;
      dav_dir_conf *child = overrides;
      dav_dir_conf *newconf = (dav_dir_conf *) ap_pcalloc(p, sizeof(*newconf));
  
      /* DBG3("dav_merge_dir_config: new=%08lx  base=%08lx  overrides=%08lx",
         (long)newconf, (long)base, (long)overrides); */
  
      newconf->enabled = DAV_INHERIT_VALUE(parent, child, enabled);
      newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout);
      newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
      newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
                                                       allow_depthinfinity);
  
      if (child->limit_xml_body != DAV_LIMIT_UNSET)
          newconf->limit_xml_body = child->limit_xml_body;
      else
          newconf->limit_xml_body = parent->limit_xml_body;
  
      newconf->d_params = ap_copy_table(p, parent->d_params);
      ap_overlap_tables(newconf->d_params, child->d_params,
  		      AP_OVERLAP_TABLES_SET);
  
      if (child->propdb.hooks != NULL)
          newconf->propdb = child->propdb;
      else
          newconf->propdb = parent->propdb;
      
      if (child->locks.hooks != NULL)
          newconf->locks = child->locks;
      else
          newconf->locks = parent->locks;
  
      if (child->vsn.hooks != NULL)
          newconf->vsn = child->vsn;
      else
          newconf->vsn = parent->vsn;
  
      if (child->repository.hooks != NULL)
          newconf->repository = child->repository;
      else
          newconf->repository = parent->repository;
      newconf->handle_get =
  	newconf->repository.hooks != NULL
  	&& DAV_AS_HOOKS_REPOSITORY(&newconf->repository)->handle_get;
  
      if (child->liveprop != NULL)
          newconf->liveprop = child->liveprop;
      else
          newconf->liveprop = parent->liveprop;
  
      return newconf;
  }
  
  uuid_state *dav_get_uuid_state(const request_rec *r)
  {
      dav_server_conf *conf;
  
      conf = ap_get_module_config(r->server->module_config, &dav_module);
  
      return &conf->st;
  }
  
  const char *dav_get_lockdb_path(const request_rec *r)
  {
      dav_server_conf *conf;
  
      conf = ap_get_module_config(r->server->module_config, &dav_module);
      return conf->lockdb_path;
  }
  
  table *dav_get_dir_params(const request_rec *r)
  {
      dav_dir_conf *conf;
  
      conf = ap_get_module_config(r->per_dir_config, &dav_module);
      return conf->d_params;
  }
  
  size_t dav_get_limit_xml_body(const request_rec *r)
  {
      dav_dir_conf *conf;
  
      conf = ap_get_module_config(r->per_dir_config, &dav_module);
      if (conf->limit_xml_body == DAV_LIMIT_UNSET)
          return DAV_DEFAULT_LIMIT_XML_BODY;
      return (size_t)conf->limit_xml_body;
  }
  
  const dav_dyn_hooks *dav_get_provider_hooks(request_rec *r, int provider_type)
  {
      dav_dir_conf *conf;
      const dav_dyn_hooks *hooks;
      static const dav_dyn_hooks null_hooks = { { 0 } };
  
      /* Call repository hook to resolve resource */
      conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						 &dav_module);
      switch (provider_type) {
  
      case DAV_DYN_TYPE_PROPDB:
          hooks = &conf->propdb;
          break;
  
      case DAV_DYN_TYPE_LOCKS:
          hooks = &conf->locks;
          break;
  
      case DAV_DYN_TYPE_QUERY_GRAMMAR:
          /* ### not yet defined */
          hooks = &null_hooks;
          break;
  
      case DAV_DYN_TYPE_ACL:
          /* ### not yet defined */
          hooks = &null_hooks;
          break;
  
      case DAV_DYN_TYPE_VSN:
          hooks = &conf->vsn;
          break;
  
      case DAV_DYN_TYPE_REPOSITORY:
          hooks = &conf->repository;
          break;
  
      case DAV_DYN_TYPE_LIVEPROP:
          hooks = conf->liveprop;
          break;
  
      default:
          /* unknown provider type */
          hooks = &null_hooks;
  	break;
      }
  
      return hooks;
  }
  
  /*
   * Command handler for the DAV directive, which is FLAG.
   */
  static const char *dav_cmd_dav(cmd_parms *cmd, void *config, int arg)
  {
      dav_dir_conf *conf = (dav_dir_conf *) config;
  
      if (arg)
  	conf->enabled = DAV_ENABLED_ON;
      else
  	conf->enabled = DAV_ENABLED_OFF;
      return NULL;
  }
  
  /*
   * Command handler for the DAVDepthInfinity directive, which is FLAG.
   */
  static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config,
                                              int arg)
  {
      dav_dir_conf *conf = (dav_dir_conf *) config;
  
      if (arg)
  	conf->allow_depthinfinity = DAV_ENABLED_ON;
      else
  	conf->allow_depthinfinity = DAV_ENABLED_OFF;
      return NULL;
  }
  
  /*
   * Command handler for the DAVLockDB directive, which is TAKE1
   */
  static const char *dav_cmd_davlockdb(cmd_parms *cmd, void *config, char *arg1)
  {
      dav_server_conf *conf;
  
      conf = (dav_server_conf *) ap_get_module_config(cmd->server->module_config,
  						    &dav_module);
      arg1 = ap_os_canonical_filename(cmd->pool, arg1);
      conf->lockdb_path = ap_server_root_relative(cmd->pool, arg1);
  
      return NULL;
  }
  
  /*
   * Command handler for DAVMinTimeout directive, which is TAKE1
   */
  static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config,
                                           char *arg1)
  {
      dav_dir_conf *conf = (dav_dir_conf *) config;
  
      conf->locktimeout = atoi(arg1);
      if (conf->locktimeout < 0)
          return "DAVMinTimeout requires a non-negative integer.";
  
      return NULL;
  }
  
  /*
   * Command handler for DAVParam directive, which is TAKE2
   */
  static const char *dav_cmd_davparam(cmd_parms *cmd, void *config,
                                      char *arg1, char *arg2)
  {
      dav_dir_conf *conf = (dav_dir_conf *) config;
  
      ap_table_set(conf->d_params, arg1, arg2);
  
      return NULL;
  }
  
  /*
   * Command handler for LimitXMLRequestBody directive, which is TAKE1
   */
  static const char *dav_cmd_limitxmlrequestbody(cmd_parms *cmd, void *config,
                                                 char *arg1)
  {
      dav_dir_conf *conf = (dav_dir_conf *) config;
  
      conf->limit_xml_body = atol(arg1);
      if (conf->limit_xml_body < 0)
          return "LimitXMLRequestBody requires a non-negative integer.";
  
      return NULL;
  }
  
  /*
  ** dav_error_response()
  **
  ** Send a nice response back to the user. In most cases, Apache doesn't
  ** allow us to provide details in the body about what happened. This
  ** function allows us to completely specify the response body.
  */
  static int dav_error_response(request_rec *r, int status, const char *body)
  {
      r->status = status;
      r->content_type = "text/html";
  
      /* since we're returning DONE, ensure the request body is consumed. */
      (void) ap_discard_request_body(r);
  
      /* begin the response now... */
      ap_send_http_header(r);
  
      /* ### hard or soft? */
      ap_soft_timeout("send error body", r);
  
      ap_rvputs(r,
  	      DAV_RESPONSE_BODY_1,
  	      r->status_line,
  	      DAV_RESPONSE_BODY_2,
  	      &r->status_line[4],
  	      DAV_RESPONSE_BODY_3,
  	      NULL);
  
      ap_rputs(body, r);
  
      ap_rputs(ap_psignature("\n<P><HR>\n", r), r);
      ap_rputs(DAV_RESPONSE_BODY_4, r);
  
      ap_kill_timeout(r);
  
      /* the response has been sent. */
      /*
       * ### Use of DONE obviates logging..!
       */
      return DONE;
  }
  
  /*
  ** Apache's URI escaping does not replace '&' since that is a valid character
  ** in a URI (to form a query section). We must explicitly handle it so that
  ** we can embed the URI into an XML document.
  */
  static const char *dav_xml_escape_uri(pool *p, const char *uri)
  {
      const char *e_uri = ap_escape_uri(p, uri);
  
      /* check the easy case... */
      if (strchr(e_uri, '&') == NULL)
  	return e_uri;
  
      /* more work needed... sigh. */
  
      /*
      ** Note: this is a teeny bit of overkill since we know there are no
      ** '<' or '>' characters, but who cares.
      */
      return dav_quote_string(p, e_uri, 0);
  }
  
  static void dav_send_multistatus(request_rec *r, int status,
                                   dav_response *first,
  				 array_header *namespaces)
  {
      /* Set the correct status and Content-Type */
      r->status = status;
      r->content_type = DAV_XML_CONTENT_TYPE;
  
      /* Send all of the headers now */
      ap_send_http_header(r);
  
      /* Start a timeout for delivering the response. */
      ap_soft_timeout("sending multistatus response", r);
  
      /* Send the actual multistatus response now... */
      ap_rputs(DAV_XML_HEADER DEBUG_CR
  	     "<D:multistatus xmlns:D=\"DAV:\"", r);
  
      if (namespaces != NULL) {
  	int i;
  
  	for (i = namespaces->nelts; i--; ) {
  	    ap_rprintf(r, " xmlns:ns%d=\"%s\"", i,
  		       DAV_GET_URI_ITEM(namespaces, i));
  	}
      }
  
      /* ap_rputc('>', r); */
      ap_rputs(">" DEBUG_CR, r);
  
      for (; first != NULL; first = first->next) {
  	dav_text *t;
  
  	if (first->propresult.xmlns == NULL) {
  	    ap_rputs("<D:response>", r);
  	}
  	else {
  	    ap_rputs("<D:response", r);
  	    for (t = first->propresult.xmlns; t; t = t->next) {
  		ap_rputs(t->text, r);
  	    }
  	    ap_rputc('>', r);
  	}
  
  	ap_rputs(DEBUG_CR "<D:href>", r);
  	ap_rputs(dav_xml_escape_uri(r->pool, first->href), r);
  	ap_rputs("</D:href>" DEBUG_CR, r);
  
  	if (first->propresult.propstats == NULL) {
  	    /* ### it would be nice to get a status line from Apache */
  	    ap_rprintf(r,
  		       "<D:status>HTTP/1.1 %d status text goes here</D:status>"
  		       DEBUG_CR, first->status);
  	}
  	else {
  	    /* assume this includes <propstat> and is quoted properly */
  	    for (t = first->propresult.propstats; t; t = t->next) {
  		ap_rputs(t->text, r);
  	    }
  	}
  
  	if (first->desc != NULL) {
  	    /*
  	    ** We supply the description, so we know it doesn't have to
  	    ** have any escaping/encoding applied to it.
  	    */
  	    ap_rputs("<D:responsedescription>", r);
  	    ap_rputs(first->desc, r);
  	    ap_rputs("</D:responsedescription>" DEBUG_CR, r);
  	}
  
  	ap_rputs("</D:response>" DEBUG_CR, r);
      }
  
      ap_rputs("</D:multistatus>" DEBUG_CR, r);
  
      /* Done with sending and the timeout. */
      ap_kill_timeout(r);
  }
  
  /*
  ** dav_log_err()
  **
  ** Write error information to the log.
  */
  static void dav_log_err(request_rec *r, dav_error *err, int level)
  {
      dav_error *errscan;
  
      /* Log the errors */
      /* ### should have a directive to log the first or all */
      for (errscan = err; errscan != NULL; errscan = errscan->prev) {
  	if (errscan->desc == NULL)
  	    continue;
  	if (errscan->save_errno != 0) {
  	    errno = errscan->save_errno;
  	    ap_log_rerror(APLOG_MARK, level, r, "%s  [%d, #%d]",
  			  errscan->desc, errscan->status, errscan->error_id);
  	}
  	else {
  	    ap_log_rerror(APLOG_MARK, level | APLOG_NOERRNO, r,
  			  "%s  [%d, #%d]",
  			  errscan->desc, errscan->status, errscan->error_id);
  	}
      }
  }
  
  /*
  ** dav_handle_err()
  **
  ** Handle the standard error processing. <err> must be non-NULL.
  **
  ** <response> is set by the following:
  **   - dav_validate_request()
  **   - dav_add_lock()
  **   - repos_hooks->remove_resource
  **   - repos_hooks->move_resource
  **   - repos_hooks->copy_resource
  */
  static int dav_handle_err(request_rec *r, dav_error *err,
  			  dav_response *response)
  {
      /* log the errors */
      dav_log_err(r, err, APLOG_ERR);
  
      if (response == NULL) {
  	/* our error messages are safe; tell Apache this */
  	ap_table_setn(r->notes, "verbose-error-to", "*");
  	return err->status;
      }
  
      /* since we're returning DONE, ensure the request body is consumed. */
      (void) ap_discard_request_body(r);
  
      /* send the multistatus and tell Apache the request/response is DONE. */
      dav_send_multistatus(r, err->status, response, NULL);
      return DONE;
  }
  
  /* handy function for return values of methods that (may) create things */
  static int dav_created(request_rec *r, request_rec *rnew, 
                         dav_resource *res, const char *what,
  		       int replaced)
  {
      const char *body;
  
      if (rnew == NULL) {
  	rnew = r;
      }
  
      /* did the target resource already exist? */
      if (replaced) {
  	/* Apache will supply a default message */
  	return HTTP_NO_CONTENT;
      }
  
      /* Per HTTP/1.1, S10.2.2: add a Location header to contain the
       * URI that was created. */
  
      /* ### rnew->uri does not contain an absoluteURI. S14.30 states that
       * ### the Location header requires an absoluteURI. where to get it? */
      /* ### disable until we get the right value */
  #if 0
      ap_table_setn(r->headers_out, "Location", rnew->uri);
  #endif
  
      /* ### insert an ETag header? see HTTP/1.1 S10.2.2 */
  
      /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so
       * we must manufacture the entire response. */
      body = ap_psprintf(r->pool, "%s %s has been created.",
  		       what,
  		       ap_escape_html(rnew->pool, rnew->uri));
      return dav_error_response(r, HTTP_CREATED, body);
  }
  
  /* ### move to dav_util? */
  int dav_get_depth(request_rec *r, int def_depth)
  {
      const char *depth = ap_table_get(r->headers_in, "Depth");
  
      if (depth == NULL) {
  	return def_depth;
      }
      if (strcasecmp(depth, "infinity") == 0) {
  	return DAV_INFINITY;
      }
      else if (strcmp(depth, "0") == 0) {
  	return 0;
      }
      else if (strcmp(depth, "1") == 0) {
  	return 1;
      }
  
      /* The caller will return an HTTP_BAD_REQUEST. This will augment the
       * default message that Apache provides. */
      ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		  "An invalid Depth header was specified.");
      return -1;
  }
  
  static int dav_get_overwrite(request_rec *r)
  {
      const char *overwrite = ap_table_get(r->headers_in, "Overwrite");
  
      if (overwrite == NULL) {
  	return 1;		/* default is "T" */
      }
  
      if ((*overwrite == 'F' || *overwrite == 'f') && overwrite[1] == '\0') {
  	return 0;
      }
      if ((*overwrite == 'T' || *overwrite == 't') && overwrite[1] == '\0') {
  	return 1;
      }
  
      /* The caller will return an HTTP_BAD_REQUEST. This will augment the
       * default message that Apache provides. */
      ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		  "An invalid Overwrite header was specified.");
      return -1;
  }
  
  /* resolve a request URI to a resource descriptor */
  static int dav_get_resource(request_rec *r, dav_resource **res_p)
  {
      dav_dir_conf *conf;
      const dav_hooks_repository *repos_hooks;
  
      /* Call repository hook to resolve resource */
      conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						 &dav_module);
  
      repos_hooks = DAV_AS_HOOKS_REPOSITORY(&conf->repository);
      if (repos_hooks == NULL || repos_hooks->get_resource == NULL) {
  	/* ### this should happen at startup rather than per-request */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
                        "No %s has been configured.",
                        repos_hooks == NULL
                        ? "repository module"
                        : "GET handler");
          return HTTP_INTERNAL_SERVER_ERROR;
      }
  
      *res_p = (*repos_hooks->get_resource)(r, conf->dir,
                                            dav_get_target_selector(r));
      if (*res_p == NULL) {
  	/* Apache will supply a default error for this. */
          return HTTP_NOT_FOUND;
      }
  
      return OK;
  }
  
  static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb)
  {
      const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
  
      if (hooks == NULL) {
  	*lockdb = NULL;
  	return NULL;
      }
  
      /* open the thing lazily */
      return (*hooks->open_lockdb)(r, ro, 0, lockdb);
  }
  
  static int dav_parse_range(request_rec *r,
                             off_t *range_start, off_t *range_end)
  {
      const char *range;
      char *dash;
      char *slash;
  
      range = ap_table_get(r->headers_in, "content-range");
      if (range == NULL)
          return 0;
  
      range = ap_pstrdup(r->pool, range);
      if (strncasecmp(range, "bytes ", 6) != 0
          || (dash = strchr(range, '-')) == NULL
          || (slash = strchr(range, '/')) == NULL) {
          /* malformed header. ignore it (per S14.16 of RFC2616) */
          return 0;
      }
  
      *dash = *slash = '\0';
      *range_start = atol(range + 6);
      *range_end = atol(dash + 1);
      if (*range_end < *range_start
          || (slash[1] != '*' && atol(slash + 1) <= *range_end)) {
          /* invalid range. ignore it (per S14.16 of RFC2616) */
          return 0;
      }
  
      /* we now have a valid range */
      return 1;
  }
  
  /* handle the GET method */
  static int dav_method_get(request_rec *r)
  {
      dav_resource *resource;
      int result;
  
      /* This method should only be called when the resource is not
       * visible to Apache. We will fetch the resource from the repository,
       * then create a subrequest for Apache to handle.
       */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
      if (!resource->exists) {
          /* Apache will supply a default error for this. */
          return HTTP_NOT_FOUND;
      }
  
      /* Check resource type */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR &&
          resource->type != DAV_RESOURCE_TYPE_REVISION) {
          return dav_error_response(r, HTTP_CONFLICT,
                                    "Cannot GET this type of resource.");
      }
  
      /* Cannot handle GET of a collection from a repository */
      if (resource->collection) {
  	return dav_error_response(r, HTTP_CONFLICT, 
                                    "No default response to GET for a "
                                    "collection.");
      }
  
      /*
      ** We can use two different approaches for a GET.
      **
      ** 1) get_pathname will return a pathname to a file which should be
      **    sent to the client. If the repository provides this, then we
      **    use it.
      **
      **    This is the best alternative since it allows us to do a sub-
      **    request on the file, which gives the Apache framework a chance
      **    to deal with negotiation, MIME types, or whatever.
      **
      ** 2) open_stream and read_stream.
      */
      if (resource->hooks->get_pathname != NULL) {
  	const char *pathname;
  	void *fhandle;
  	request_rec *new_req;
  	
  	/* Ask repository for copy of file */
  	pathname = (*resource->hooks->get_pathname)(resource, &fhandle);
  	if (pathname == NULL) {
  	    return HTTP_NOT_FOUND;
  	}
  
  	/* Convert to canonical filename, so Apache detects component
  	 * separators (on Windows, it only looks for '/', not '\')
  	 */
  	pathname = ap_os_case_canonical_filename(r->pool, pathname);
  
  	/* Create a sub-request with the new filename */
  	new_req = ap_sub_req_lookup_file(pathname, r);
  	if (new_req == NULL) {
  	    (*resource->hooks->free_file)(fhandle);
  	    return HTTP_INTERNAL_SERVER_ERROR;
  	}
  
  	/* This may be a HEAD request */
  	new_req->header_only = r->header_only;
  
  	/* ### this enables header generation */
  	new_req->assbackwards = 0;
  
  	/* Run the sub-request */
  	result = ap_run_sub_req(new_req);
  	ap_destroy_sub_req(new_req);
  
  	/* Free resources */
  	(*resource->hooks->free_file)(fhandle);
  
  	return result;
      }
      else {
  	dav_stream_mode mode;
  	dav_stream *stream;
  	dav_error *err;
  	void *buffer;
          int has_range;
          off_t range_start;
          off_t range_end;
  
  	/* set up the HTTP headers for the response */
  	if ((err = (*resource->hooks->set_headers)(r, resource)) != NULL) {
  	    err = dav_push_error(r->pool, err->status, 0,
  				 "Unable to set up HTTP headers.",
  				 err);
  	    return dav_handle_err(r, err, NULL);
  	}
  
          /* use plain READ mode unless we see a Content-Range */
  	mode = DAV_MODE_READ;
  
          /* process the Content-Range header (if present) */
          has_range = dav_parse_range(r, &range_start, &range_end);
          if (has_range) {
              /* use a read mode which is seekable */
              mode = DAV_MODE_READ_SEEKABLE;
  
              /* prep the output */
              r->status = HTTP_PARTIAL_CONTENT;
              ap_table_setn(r->headers_out,
                            "Content-Range",
                            ap_psprintf(r->pool, "bytes %ld-%ld/*",
                                        range_start, range_end));
              ap_set_content_length(r, range_end - range_start + 1);
          }
  
          if (r->header_only) {
              ap_send_http_header(r);
              return DONE;
          }
  
  	if ((err = (*resource->hooks->open_stream)(resource, mode,
                                                     &stream)) != NULL) {
  	    /* ### assuming FORBIDDEN is probably not quite right... */
  	    err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
  				 ap_psprintf(r->pool,
  					     "Unable to GET contents for %s.",
  					     ap_escape_html(r->pool, r->uri)),
  				 err);
  	    return dav_handle_err(r, err, NULL);
  	}
  
          if (has_range
              && (err = (*resource->hooks->seek_stream)(stream,
                                                        range_start)) != NULL) {
              err = dav_push_error(r->pool, err->status, 0,
                                   "Could not seek to beginning of the "
                                   "specified Content-Range.", err);
              return dav_handle_err(r, err, NULL);
          }
  
  	/* all set. send the headers now. */
  	ap_send_http_header(r);
  
  	/* start a timeout for delivering the response. */
  	ap_soft_timeout("sending GET response", r);
  
  	buffer = ap_palloc(r->pool, DAV_READ_BLOCKSIZE);
  	while (1) {
  	    size_t amt;
  
              if (!has_range)
                  amt = DAV_READ_BLOCKSIZE;
              else if ((range_end - range_start + 1) > DAV_READ_BLOCKSIZE)
                  amt = DAV_READ_BLOCKSIZE;
              else {
                  /* note: range_end - range_start is an ssize_t */
                  amt = (size_t)(range_end - range_start + 1);
              }
  
  	    if ((err = (*resource->hooks->read_stream)(stream, buffer,
                                                         &amt)) != NULL) {
  		break;
  	    }
  	    if (amt == 0) {
  		/* no more content */
  		break;
  	    }
  	    if (ap_rwrite(buffer, amt, r) < 0) {
  		/* ### what to do with this error? */
  		break;
  	    }
  
              if (has_range) {
                  range_start += amt;
                  if (range_start > range_end)
                      break;
              }
  
  	    /* reset the timeout after a successful write */
  	    ap_reset_timeout(r);
  	}
  
  	/* Done with the request; clear its timeout */
  	ap_kill_timeout(r);
  
  	if (err != NULL)
  	    return dav_handle_err(r, err, NULL);
  
          /*
          ** ### range_start should equal range_end+1. if it doesn't, then
          ** ### we did not send enough data to the client. the client will
          ** ### hang (and timeout) waiting for the data.
          **
          ** ### what to do? abort the connection?
          */
  	return DONE;
      }
  
      /* NOTREACHED */
  }
  
  /* validate resource on POST, then pass it off to the default handler */
  static int dav_method_post(request_rec *r)
  {
      dav_resource *resource;
      dav_error *err;
      int result;
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK) {
          return result;
      }
  
      /* Note: depth == 0. Implies no need for a multistatus response. */
      if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
  				    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      return DECLINED;
  }
  
  /* handle the PUT method */
  static int dav_method_put(request_rec *r)
  {
      dav_resource *resource;
      int resource_state;
      dav_resource *resource_parent;
      const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
      const char *body;
      dav_error *err;
      dav_error *err2;
      int result;
      int resource_existed = 0;
      int resource_was_writable = 0;
      int parent_was_writable = 0;
      dav_stream_mode mode;
      dav_stream *stream;
      dav_response *multi_response;
      int has_range;
      off_t range_start;
      off_t range_end;
  
      if ((result = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK) {
  	return result;
      }
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK) {
          return result;
      }
  
      /* If not a file or collection resource, PUT not allowed */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
          body = ap_psprintf(r->pool,
                             "Cannot create resource %s with PUT.",
                             ap_escape_html(r->pool, r->uri));
  	return dav_error_response(r, HTTP_CONFLICT, body);
      }
  
      /* Cannot PUT a collection */
      if (resource->collection) {
  	return dav_error_response(r, HTTP_CONFLICT,
                                    "Cannot PUT to a collection.");
  
      }
  
      resource_state = dav_get_resource_state(r, resource);
  
      /*
      ** Note: depth == 0 normally requires no multistatus response. However,
      ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
      ** other than the Request-URI, thereby requiring a multistatus.
      **
      ** If the resource does not exist (DAV_RESOURCE_NULL), then we must
      ** check the resource *and* its parent. If the resource exists or is
      ** a locknull resource, then we check only the resource.
      */
      if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
  				    resource_state == DAV_RESOURCE_NULL ?
  				    DAV_VALIDATE_PARENT :
  				    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, multi_response);
      }
  
      /* make sure the resource can be modified (if versioning repository) */
      if ((err = dav_ensure_resource_writable(r, resource,
  					    0 /* not parent_only */,
  					    &resource_parent,
  					    &resource_existed,
  					    &resource_was_writable,
  					    &parent_was_writable)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      /* truncate and rewrite the file unless we see a Content-Range */
      mode = DAV_MODE_WRITE_TRUNC;
  
      has_range = dav_parse_range(r, &range_start, &range_end);
      if (has_range) {
          mode = DAV_MODE_WRITE_SEEKABLE;
      }
  
      /* Create the new file in the repository */
      if ((err = (*resource->hooks->open_stream)(resource, mode,
                                                 &stream)) != NULL) {
  	/* ### assuming FORBIDDEN is probably not quite right... */
  	err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
  			     ap_psprintf(r->pool,
  					 "Unable to PUT new contents for %s.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
      }
  
      if (err == NULL && has_range) {
          /* a range was provided. seek to the start */
          err = (*resource->hooks->seek_stream)(stream, range_start);
      }
  
      if (err == NULL) {
          if (ap_should_client_block(r)) {
  	    char *buffer = ap_palloc(r->pool, DAV_READ_BLOCKSIZE);
  	    long len;
  
              /*
              ** Once we start reading the request, then we must read the
              ** whole darn thing. ap_discard_request_body() won't do anything
              ** for a partially-read request.
              */
  
  	    while ((len = ap_get_client_block(r, buffer,
  					      DAV_READ_BLOCKSIZE)) > 0) {
  		if (err == NULL) {
  		    /* write whatever we read, until we see an error */
  		    err = (*resource->hooks->write_stream)(stream,
                                                             buffer, len);
  		}
  	    }
  
              /*
              ** ### what happens if we read more/less than the amount
              ** ### specified in the Content-Range? eek...
              */
  
  	    if (len == -1) {
  		/*
  		** Error reading request body. This has precedence over
  		** prior errors.
  		*/
  		err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
  				    "An error occurred while reading the "
  				    "request body.");
  	    }
          }
  
          err2 = (*resource->hooks->close_stream)(stream,
                                                  err == NULL /* commit */);
  	if (err2 != NULL && err == NULL) {
  	    /* no error during the write, but we hit one at close. use it. */
  	    err = err2;
  	}
      }
  
      /*
      ** Ensure that we think the resource exists now.
      ** ### eek. if an error occurred during the write and we did not commit,
      ** ### then the resource might NOT exist (e.g. dav_fs_repos.c)
      */
      if (err == NULL) {
  	resource->exists = 1;
      }
  
      /* restore modifiability of resources back to what they were */
      err2 = dav_revert_resource_writability(r, resource, resource_parent,
  					   err != NULL /* undo if error */,
  					   resource_existed,
  					   resource_was_writable,
  					   parent_was_writable);
  
      /* check for errors now */
      if (err != NULL) {
  	return dav_handle_err(r, err, NULL);
      }
      if (err2 != NULL) {
  	/* just log a warning */
  	err2 = dav_push_error(r->pool, err->status, 0,
  			      "The PUT was successful, but there "
  			      "was a problem reverting the writability of "
  			      "the resource or its parent collection.",
  			      err2);
  	dav_log_err(r, err2, APLOG_WARNING);
      }
  
      /* ### place the Content-Type and Content-Language into the propdb */
  
      if (locks_hooks != NULL) {
          dav_lockdb *lockdb;
  
          if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
  	    /* The file creation was successful, but the locking failed. */
  	    err = dav_push_error(r->pool, err->status, 0,
  				 "The file was PUT successfully, but there "
  				 "was a problem opening the lock database "
  				 "which prevents inheriting locks from the "
  				 "parent resources.",
  				 err);
  	    return dav_handle_err(r, err, NULL);
          }
  
  	/* notify lock system that we have created/replaced a resource */
  	err = dav_notify_created(r, lockdb, resource, resource_state, 0);
  
  	(*locks_hooks->close_lockdb)(lockdb);
  
  	if (err != NULL) {
  	    /* The file creation was successful, but the locking failed. */
  	    err = dav_push_error(r->pool, err->status, 0,
  				 "The file was PUT successfully, but there "
  				 "was a problem updating its lock "
  				 "information.",
  				 err);
  	    return dav_handle_err(r, err, NULL);
  	}
      }
  
      /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
  
      /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
      return dav_created(r, NULL, resource, "Resource", resource_existed);
  }
  
  /* ### move this to dav_util? */
  void dav_add_response(dav_walker_ctx *ctx, const char *href, int status,
  		      dav_get_props_result *propstats)
  {
      dav_response *resp;
  
      /* just drop some data into an dav_response */
      resp = ap_pcalloc(ctx->pool, sizeof(*resp));
      resp->href = ap_pstrdup(ctx->pool, href);
      resp->status = status;
      if (propstats) {
  	resp->propresult = *propstats;
      }
      resp->next = ctx->response;
  
      ctx->response = resp;
  }
  
  /* handle the DELETE method */
  static int dav_method_delete(request_rec *r)
  {
      dav_resource *resource;
      dav_resource *resource_parent = NULL;
      dav_error *err;
      dav_error *err2;
      dav_response *multi_response;
      const char *body;
      int result;
      int depth;
      int parent_was_writable = 0;
  
      /* We don't use the request body right now, so torch it. */
      if ((result = ap_discard_request_body(r)) != OK) {
  	return result;
      }
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
      if (!resource->exists) {
          /* Apache will supply a default error for this. */
  	return HTTP_NOT_FOUND;
      }
  
      /* 2518 says that depth must be infinity only for collections.
       * For non-collections, depth is ignored, unless it is an illegal value (1).
       */
      depth = dav_get_depth(r, DAV_INFINITY);
  
      if (resource->collection && depth != DAV_INFINITY) {
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "Depth must be \"infinity\" for DELETE of a collection.");
  	return HTTP_BAD_REQUEST;
      }
      if (!resource->collection && depth == 1) {
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "Depth of \"1\" is not allowed for DELETE.");
  	return HTTP_BAD_REQUEST;
      }
  
      /* Check for valid resource type */
      /* ### allow DAV_RESOURCE_TYPE_REVISION with All-Bindings header */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR &&
          resource->type != DAV_RESOURCE_TYPE_WORKSPACE) {
          body = ap_psprintf(r->pool,
                             "Cannot delete resource %s.",
                             ap_escape_html(r->pool, r->uri));
  	return dav_error_response(r, HTTP_CONFLICT, body);
      }
  
      /*
      ** If any resources fail the lock/If: conditions, then we must fail
      ** the delete. Each of the failing resources will be listed within
      ** a DAV:multistatus body, wrapped into a 424 response.
      **
      ** Note that a failure on the resource itself does not generate a
      ** multistatus response -- only internal members/collections.
      */
      if ((err = dav_validate_request(r, resource, depth, NULL,
  				    &multi_response,
  				    DAV_VALIDATE_PARENT
                                      | DAV_VALIDATE_USE_424, NULL)) != NULL) {
  	err = dav_push_error(r->pool, err->status, 0,
  			     ap_psprintf(r->pool,
  					 "Could not DELETE %s due to a failed "
  					 "precondition (e.g. locks).",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	return dav_handle_err(r, err, multi_response);
      }
  
      /* ### RFC 2518 s. 8.10.5 says to remove _all_ locks, not just those
       *     locked by the token(s) in the if_header.
       */
      if ((result = dav_unlock(r, resource, NULL)) != OK) {
  	return result;
      }
  
      /* if versioned resource, make sure parent is checked out */
      if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
  					    &resource_parent,
  					    NULL, NULL,
  					    &parent_was_writable)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      /* try to remove the resource */
      err = (*resource->hooks->remove_resource)(resource, &multi_response);
  
      /* restore writability of parent back to what it was */
      err2 = dav_revert_resource_writability(r, NULL, resource_parent,
  					   err != NULL /* undo if error */,
  					   0, 0, parent_was_writable);
  
      /* check for errors now */
      if (err != NULL) {
  	err = dav_push_error(r->pool, err->status, 0,
  			     ap_psprintf(r->pool,
  					 "Could not DELETE %s.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	return dav_handle_err(r, err, multi_response);
      }
      if (err2 != NULL) {
  	/* just log a warning */
  	err = dav_push_error(r->pool, err2->status, 0,
  			     "The DELETE was successful, but there "
  			     "was a problem reverting the writability of "
  			     "its parent collection.",
  			     err2);
  	dav_log_err(r, err, APLOG_WARNING);
      }
  
      /* ### HTTP_NO_CONTENT if no body, HTTP_OK if there is a body (some day) */
  
      /* Apache will supply a default error for this. */
      return HTTP_NO_CONTENT;
  }
  
  /* handle the OPTIONS method */
  static int dav_method_options(request_rec *r)
  {
      const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
      const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
      dav_resource *resource;
      const char *options;
      const char *dav_level;
      const char *vsn_level;
      int result;
      const dav_dir_conf *conf;
      const dav_dyn_hooks *lp;
  
      /* per HTTP/1.1 S9.2, we can discard this body */
      if ((result = ap_discard_request_body(r)) != OK) {
  	return result;
      }
  
      /* no body */
      ap_set_content_length(r, 0);
  
      /* resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
  
      /* determine which providers are available */
      dav_level = "1";
      vsn_level = NULL;
  
      if (locks_hooks != NULL) {
          dav_level = "1,2";
      }
      if (vsn_hooks != NULL) {
          vsn_level = (*vsn_hooks->get_vsn_header)();
      }
  
      /*
      ** Iterate through the live property providers; add their URIs to
      ** the dav_level string.
      */
      conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						 &dav_module);
      for (lp = conf->liveprop; lp != NULL; lp = lp->next) {
          const char *uri = DAV_AS_HOOKS_LIVEPROP(lp)->propset_uri;
  
          if (uri != NULL)
              dav_level = ap_pstrcat(r->pool, dav_level, ",<", uri, ">", NULL);
      }
  
      /* this tells MSFT products to skip looking for FrontPage extensions */
      ap_table_setn(r->headers_out, "MS-Author-Via", "DAV");
  
      /*
      ** Three cases:  resource is null (3), is lock-null (7.4), or exists.
      **
      ** All cases support OPTIONS and LOCK.
      ** (Lock-) null resources also support MKCOL and PUT.
      ** Lock-null support PROPFIND and UNLOCK.
      ** Existing resources support lots of stuff.
      */
  
      /* ### take into account resource type */
      switch (dav_get_resource_state(r, resource))
      {
      case DAV_RESOURCE_EXISTS:
  	/* resource exists */
  	if (resource->collection) {
  	    options = ap_pstrcat(r->pool,
  		"OPTIONS, "
  		"GET, HEAD, POST, DELETE, TRACE, "
  		"PROPFIND, PROPPATCH, COPY, MOVE",
                  locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
                  NULL);
  	}
  	else {
  	    /* files also support PUT */
  	    options = ap_pstrcat(r->pool,
  		"OPTIONS, "
  		"GET, HEAD, POST, DELETE, TRACE, "
  		"PROPFIND, PROPPATCH, COPY, MOVE, PUT",
                  locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
                  NULL);
  	}
  	break;
  
      case DAV_RESOURCE_LOCK_NULL:
  	/* resource is lock-null. */
  	options = ap_pstrcat(r->pool, "OPTIONS, MKCOL, PUT, PROPFIND",
                               locks_hooks != NULL ? ", LOCK, UNLOCK" : "",
                               NULL);
  	break;
  
      case DAV_RESOURCE_NULL:
  	/* resource is null. */
  	options = ap_pstrcat(r->pool, "OPTIONS, MKCOL, PUT",
                               locks_hooks != NULL ? ", LOCK" : "",
                               NULL);
  	break;
  
      default:
  	/* ### internal error! */
  	options = "OPTIONS";
  	break;
      }
  
      /* If there is a versioning provider, add versioning options */
      if (vsn_hooks != NULL) {
          const char *vsn_options = NULL;
  
          /* ### take into account resource type */
          if (!resource->exists) {
              if ((*vsn_hooks->versionable)(resource))
                  vsn_options = ", MKRESOURCE";
          }
          else if (!resource->versioned) {
              if ((*vsn_hooks->versionable)(resource))
                  vsn_options = ", CHECKIN";
          }
          else if (resource->working)
              vsn_options = ", CHECKIN, UNCHECKOUT";
          else
              vsn_options = ", CHECKOUT";
  
          if (vsn_options != NULL)
              options = ap_pstrcat(r->pool, options, vsn_options, NULL);
      }
  
      ap_table_setn(r->headers_out, "Allow", options);
      ap_table_setn(r->headers_out, "DAV", dav_level);
  
      if (vsn_level != NULL)
          ap_table_setn(r->headers_out, "Versioning", vsn_level);
  
      /* ### this will send a Content-Type. the default OPTIONS does not. */
      ap_send_http_header(r);
  
      /* ### the default (ap_send_http_options) returns OK, but I believe
       * ### that is because it is the default handler and nothing else
       * ### will run after the thing. */
  
      /* we've sent everything necessary to the client. */
      return DONE;
  }
  
  static void dav_cache_badprops(dav_walker_ctx *ctx)
  {
      const dav_xml_elem *elem;
      dav_text_header hdr = { 0 };
  
      /* just return if we built the thing already */
      if (ctx->propstat_404 != NULL) {
  	return;
      }
  
      dav_text_append(ctx->pool, &hdr,
  		    "<D:propstat>" DEBUG_CR
  		    "<D:prop>" DEBUG_CR);
  
      elem = dav_find_child(ctx->doc->root, "prop");
      for (elem = elem->first_child; elem; elem = elem->next) {
  	dav_text_append(ctx->pool, &hdr, dav_empty_elem(ctx->pool, elem));
      }
  
      dav_text_append(ctx->pool, &hdr,
  		    "</D:prop>" DEBUG_CR
  		    "<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
  		    "</D:propstat>" DEBUG_CR);
  
      ctx->propstat_404 = hdr.first;
  }
  
  static dav_error * dav_propfind_walker(dav_walker_ctx *ctx, int calltype)
  {
      dav_error *err;
      dav_propdb *propdb;
      dav_get_props_result propstats = { 0 };
  
      /*
      ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since
      ** dav_get_allprops() does not need to do namespace translation,
      ** we're okay.
      **
      ** Note: we cast to lose the "const". The propdb won't try to change
      ** the resource, however, since we are opening readonly.
      */
      err = dav_open_propdb(ctx->r, ctx->lockdb,
  			  (dav_resource *)ctx->resource, 1,
  			  ctx->doc ? ctx->doc->namespaces : NULL, &propdb);
      if (err != NULL) {
  	/* ### do something with err! */
  
  	if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
  	    dav_get_props_result badprops = { 0 };
  
  	    /* some props were expected on this collection/resource */
  	    dav_cache_badprops(ctx);
  	    badprops.propstats = ctx->propstat_404;
  	    dav_add_response(ctx, ctx->uri.buf, 0, &badprops);
  	}
  	else {
  	    /* no props on this collection/resource */
  	    dav_add_response(ctx, ctx->uri.buf, HTTP_OK, NULL);
  	}
  	return NULL;
      }
      /* ### what to do about closing the propdb on server failure? */
  
      if (ctx->propfind_type == DAV_PROPFIND_IS_PROP) {
  	propstats = dav_get_props(propdb, ctx->doc);
      }
      else {
  	propstats = dav_get_allprops(propdb,
  			     ctx->propfind_type == DAV_PROPFIND_IS_ALLPROP);
      }
      dav_close_propdb(propdb);
  
      dav_add_response(ctx, ctx->uri.buf, 0, &propstats);
  
      return NULL;
  }
  
  /* handle the PROPFIND method */
  static int dav_method_propfind(request_rec *r)
  {
      dav_resource *resource;
      int depth;
      dav_error *err;
      int result;
      dav_xml_doc *doc;
      const dav_xml_elem *child;
      dav_walker_ctx ctx = { 0 };
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
  
      if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) {
  	/* Apache will supply a default error for this. */
  	return HTTP_NOT_FOUND;
      }
  
      if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
  	/* dav_get_depth() supplies additional information for the
  	 * default message. */
  	return HTTP_BAD_REQUEST;
      }
  
      if (depth == DAV_INFINITY) {
  	dav_dir_conf *conf;
  	conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						     &dav_module);
          /* default is to DISALLOW these requests */
  	if (conf->allow_depthinfinity != DAV_ENABLED_ON) {
              return dav_error_response(r, HTTP_FORBIDDEN,
                                        ap_psprintf(r->pool,
                                                    "PROPFIND requests with a "
                                                    "Depth of \"infinity\" are "
                                                    "not allowed for %s.",
                                                    ap_escape_html(r->pool,
                                                                   r->uri)));
  	}
      }
  
      if ((result = dav_parse_input(r, &doc)) != OK) {
  	return result;
      }
      /* note: doc == NULL if no request body */
  
      if (doc && !dav_validate_root(doc, "propfind")) {
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "The \"propfind\" element was not found.");
  	return HTTP_BAD_REQUEST;
      }
  
      /* ### validate that only one of these three elements is present */
  
      if (doc == NULL
  	|| (child = dav_find_child(doc->root, "allprop")) != NULL) {
  	/* note: no request body implies allprop */
  	ctx.propfind_type = DAV_PROPFIND_IS_ALLPROP;
      }
      else if ((child = dav_find_child(doc->root, "propname")) != NULL) {
  	ctx.propfind_type = DAV_PROPFIND_IS_PROPNAME;
      }
      else if ((child = dav_find_child(doc->root, "prop")) != NULL) {
  	ctx.propfind_type = DAV_PROPFIND_IS_PROP;
      }
      else {
  	/* "propfind" element must have one of the above three children */
  
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "The \"propfind\" element does not contain one of "
  		      "the required child elements (the specific command).");
  	return HTTP_BAD_REQUEST;
      }
  
      ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_AUTH;
      ctx.func = dav_propfind_walker;
      ctx.pool = r->pool;
      ctx.doc = doc;
      ctx.r = r;
      ctx.resource = resource;
  
      dav_buffer_init(r->pool, &ctx.uri, r->uri);
  
      /* ### should open read-only */
      if ((err = dav_open_lockdb(r, 0, &ctx.lockdb)) != NULL) {
  	err = dav_push_error(r->pool, err->status, 0,
  			     "The lock database could not be opened, "
  			     "preventing access to the various lock "
  			     "properties for the PROPFIND.",
  			     err);
  	return dav_handle_err(r, err, NULL);
      }
      if (ctx.lockdb != NULL) {
  	/* if we have a lock database, then we can walk locknull resources */
  	ctx.walk_type |= DAV_WALKTYPE_LOCKNULL;
      }
  
      err = (*resource->hooks->walk)(&ctx, depth);
  
      if (ctx.lockdb != NULL) {
  	(*ctx.lockdb->hooks->close_lockdb)(ctx.lockdb);
      }
  
      if (err != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      /* return a 207 (Multi-Status) response now. */
  
      /* if a 404 was generated for an HREF, then we need to spit out the
       * doc's namespaces for use by the 404. Note that <response> elements
       * will override these ns0, ns1, etc, but NOT within the <response>
       * scope for the badprops. */
      /* NOTE: propstat_404 != NULL implies doc != NULL */
      if (ctx.propstat_404 != NULL) {
  	dav_send_multistatus(r, HTTP_MULTI_STATUS, ctx.response,
                               doc->namespaces);
      }
      else {
  	dav_send_multistatus(r, HTTP_MULTI_STATUS, ctx.response, NULL);
      }
  
      /* the response has been sent. */
      return DONE;
  }
  
  static dav_text * dav_failed_proppatch(pool *p, array_header *prop_ctx)
  {
      dav_text_header hdr = { 0 };
      int i = prop_ctx->nelts;
      dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
      dav_error *err424_set = NULL;
      dav_error *err424_delete = NULL;
      const char *s;
  
      /* ### might be nice to sort by status code and description */
  
      for ( ; i-- > 0; ++ctx ) {
  	dav_text_append(p, &hdr,
  			"<D:propstat>" DEBUG_CR
  			"<D:prop>");
  	dav_text_append(p, &hdr, dav_empty_elem(p, ctx->prop));
  	dav_text_append(p, &hdr, "</D:prop>" DEBUG_CR);
  
  	if (ctx->err == NULL) {
  	    /* nothing was assigned here yet, so make it a 424 */
  
  	    if (ctx->operation == DAV_PROP_OP_SET) {
  		if (err424_set == NULL)
  		    err424_set = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
  					       "Attempted DAV:set operation "
  					       "could not be completed due "
  					       "to other errors.");
  		ctx->err = err424_set;
  	    }
  	    else if (ctx->operation == DAV_PROP_OP_DELETE) {
  		if (err424_delete == NULL)
  		    err424_delete = dav_new_error(p, HTTP_FAILED_DEPENDENCY, 0,
  						  "Attempted DAV:remove "
  						  "operation could not be "
  						  "completed due to other "
  						  "errors.");
  		ctx->err = err424_delete;
  	    }
  	}
  
  	s = ap_psprintf(p,
  			"<D:status>"
  			"HTTP/1.1 %d (status)"
  			"</D:status>" DEBUG_CR,
  			ctx->err->status);
  	dav_text_append(p, &hdr, s);
  
  	/* ### we should use compute_desc if necessary... */
  	if (ctx->err->desc != NULL) {
  	    dav_text_append(p, &hdr, "<D:responsedescription>" DEBUG_CR);
  	    dav_text_append(p, &hdr, ctx->err->desc);
  	    dav_text_append(p, &hdr, "</D:responsedescription>" DEBUG_CR);
  	}
  
  	dav_text_append(p, &hdr, "</D:propstat>" DEBUG_CR);
      }
  
      return hdr.first;
  }
  
  static dav_text * dav_success_proppatch(pool *p, array_header *prop_ctx)
  {
      dav_text_header hdr = { 0 };
      int i = prop_ctx->nelts;
      dav_prop_ctx *ctx = (dav_prop_ctx *)prop_ctx->elts;
  
      /*
      ** ### we probably need to revise the way we assemble the response...
      ** ### this code assumes everything will return status==200.
      */
  
      dav_text_append(p, &hdr,
  		    "<D:propstat>" DEBUG_CR
  		    "<D:prop>" DEBUG_CR);
  
      for ( ; i-- > 0; ++ctx ) {
  	dav_text_append(p, &hdr, dav_empty_elem(p, ctx->prop));
      }
  
      dav_text_append(p, &hdr,
  		    "</D:prop>" DEBUG_CR
  		    "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
  		    "</D:propstat>" DEBUG_CR);
  
      return hdr.first;
  }
  
  static void dav_prop_log_errors(dav_prop_ctx *ctx)
  {
      dav_log_err(ctx->r, ctx->err, APLOG_ERR);
  }
  
  /*
  ** Call <func> for each context. This can stop when an error occurs, or
  ** simply iterate through the whole list.
  **
  ** Returns 1 if an error occurs (and the iteration is aborted). Returns 0
  ** if all elements are processed.
  **
  ** If <reverse> is true (non-zero), then the list is traversed in
  ** reverse order.
  */
  static int dav_process_ctx_list(void (*func)(dav_prop_ctx *ctx),
  				array_header *ctx_list, int stop_on_error,
  				int reverse)
  {
      int i = ctx_list->nelts;
      dav_prop_ctx *ctx = (dav_prop_ctx *)ctx_list->elts;
  
      if (reverse)
  	ctx += i;
  
      while (i--) {
  	if (reverse)
  	    --ctx;
  
  	(*func)(ctx);
  	if (stop_on_error && DAV_PROP_CTX_HAS_ERR(*ctx)) {
  	    return 1;
  	}
  
  	if (!reverse)
  	    ++ctx;
      }
  
      return 0;
  }
  
  /* handle the PROPPATCH method */
  static int dav_method_proppatch(request_rec *r)
  {
      dav_error *err;
      dav_resource *resource;
      int result;
      dav_xml_doc *doc;
      dav_xml_elem *child;
      dav_propdb *propdb;
      int failure = 0;
      dav_response resp = { 0 };
      dav_text *propstat_text;
      array_header *ctx_list;
      dav_prop_ctx *ctx;
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
      if (!resource->exists) {
  	/* Apache will supply a default error for this. */
  	return HTTP_NOT_FOUND;
      }
  
      if ((result = dav_parse_input(r, &doc)) != OK) {
  	return result;
      }
      /* note: doc == NULL if no request body */
  
      if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) {
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "The request body does not contain "
  		      "a \"propertyupdate\" element.");
  	return HTTP_BAD_REQUEST;
      }
  
      /* Check If-Headers and existing locks */
      /* Note: depth == 0. Implies no need for a multistatus response. */
      if ((err = dav_validate_request(r, resource, 0, NULL, NULL,
  				    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces,
  			       &propdb)) != NULL) {
  	err = dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     ap_psprintf(r->pool,
  					 "Could not open the property "
  					 "database for %s.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	return dav_handle_err(r, err, NULL);
      }
      /* ### what to do about closing the propdb on server failure? */
  
      /* ### validate "live" properties */
  
      /* set up an array to hold property operation contexts */
      ctx_list = ap_make_array(r->pool, 10, sizeof(dav_prop_ctx));
  
      /* do a first pass to ensure that all "remove" properties exist */
      for (child = doc->root->first_child; child; child = child->next) {
  	int is_remove;
  	dav_xml_elem *prop_group;
  	dav_xml_elem *one_prop;
  
  	/* Ignore children that are not set/remove */
  	if (child->ns != DAV_NS_DAV_ID
  	    || (!(is_remove = strcmp(child->name, "remove") == 0)
  		&& strcmp(child->name, "set") != 0)) {
  	    continue;
  	}
  
  	/* make sure that a "prop" child exists for set/remove */
  	if ((prop_group = dav_find_child(child, "prop")) == NULL) {
  	    dav_close_propdb(propdb);
  
  	    /* This supplies additional information for the default message. */
  	    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  			  "A \"prop\" element is missing inside "
  			  "the propertyupdate command.");
  	    return HTTP_BAD_REQUEST;
  	}
  
  	for (one_prop = prop_group->first_child; one_prop;
  	     one_prop = one_prop->next) {
  
  	    ctx = (dav_prop_ctx *)ap_push_array(ctx_list);
  	    ctx->propdb = propdb;
  	    ctx->operation = is_remove ? DAV_PROP_OP_DELETE : DAV_PROP_OP_SET;
  	    ctx->prop = one_prop;
  
              ctx->r = r;         /* for later use by dav_prop_log_errors() */
  
  	    dav_prop_validate(ctx);
  
  	    if ( DAV_PROP_CTX_HAS_ERR(*ctx) ) {
  		failure = 1;
  	    }
  	}
      }
  
      /* ### should test that we found at least one set/remove */
  
      /* execute all of the operations */
      if (!failure && dav_process_ctx_list(dav_prop_exec, ctx_list, 1, 0)) {
  	failure = 1;
      }
  
      /* generate a failure/success response */
      if (failure) {
  	(void)dav_process_ctx_list(dav_prop_rollback, ctx_list, 0, 1);
  	propstat_text = dav_failed_proppatch(r->pool, ctx_list);
      }
      else {
  	(void)dav_process_ctx_list(dav_prop_commit, ctx_list, 0, 0);
  	propstat_text = dav_success_proppatch(r->pool, ctx_list);
      }
  
      /* make sure this gets closed! */
      dav_close_propdb(propdb);
  
      /* log any errors that occurred */
      (void)dav_process_ctx_list(dav_prop_log_errors, ctx_list, 0, 0);
  
      resp.href = resource->uri;
  
      /* ### should probably use something new to pass along this text... */
      resp.propresult.propstats = propstat_text;
  
      dav_send_multistatus(r, HTTP_MULTI_STATUS, &resp, doc->namespaces);
  
      /* the response has been sent. */
      return DONE;
  }
  
  static int process_mkcol_body(request_rec *r)
  {
      /* This is snarfed from ap_setup_client_block(). We could get pretty
       * close to this behavior by passing REQUEST_NO_BODY, but we need to
       * return HTTP_UNSUPPORTED_MEDIA_TYPE (while ap_setup_client_block
       * returns HTTP_REQUEST_ENTITY_TOO_LARGE). */
  
      const char *tenc = ap_table_get(r->headers_in, "Transfer-Encoding");
      const char *lenp = ap_table_get(r->headers_in, "Content-Length");
  
      /* make sure to set the Apache request fields properly. */
      r->read_body = REQUEST_NO_BODY;
      r->read_chunked = 0;
      r->remaining = 0;
  
      if (tenc) {
  	if (strcasecmp(tenc, "chunked")) {
  	    /* Use this instead of Apache's default error string */
  	    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  			  "Unknown Transfer-Encoding %s", tenc);
  	    return HTTP_NOT_IMPLEMENTED;
  	}
  
  	r->read_chunked = 1;
      }
      else if (lenp) {
  	const char *pos = lenp;
  
  	while (ap_isdigit(*pos) || ap_isspace(*pos)) {
  	    ++pos;
  	}
  	if (*pos != '\0') {
  	    /* This supplies additional information for the default message. */
  	    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  			  "Invalid Content-Length %s", lenp);
  	    return HTTP_BAD_REQUEST;
  	}
  
  	r->remaining = atol(lenp);
      }
  
      if (r->read_chunked || r->remaining > 0) {
  	/* ### log something? */
  
  	/* Apache will supply a default error for this. */
  	return HTTP_UNSUPPORTED_MEDIA_TYPE;
      }
  
      /*
      ** Get rid of the body. this will call ap_setup_client_block(), but
      ** our copy above has already verified its work.
      */
      return ap_discard_request_body(r);
  }
  
  /* handle the MKCOL method */
  static int dav_method_mkcol(request_rec *r)
  {
      dav_resource *resource;
      int resource_state;
      dav_resource *resource_parent;
      const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
      dav_error *err;
      dav_error *err2;
      int result;
      dav_dir_conf *conf;
      int parent_was_writable = 0;
      dav_response *multi_status;
  
      /* handle the request body */
      /* ### this may move lower once we start processing bodies */
      if ((result = process_mkcol_body(r)) != OK) {
  	return result;
      }
  
      conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						 &dav_module);
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
  
      if (resource->exists) {
  	/* oops. something was already there! */
  
  	/* Apache will supply a default error for this. */
  	/* ### we should provide a specific error message! */
  	return HTTP_METHOD_NOT_ALLOWED;
      }
  
      resource_state = dav_get_resource_state(r, resource);
  
      /*
      ** Check If-Headers and existing locks.
      **
      ** Note: depth == 0 normally requires no multistatus response. However,
      ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
      ** other than the Request-URI, thereby requiring a multistatus.
      **
      ** If the resource does not exist (DAV_RESOURCE_NULL), then we must
      ** check the resource *and* its parent. If the resource exists or is
      ** a locknull resource, then we check only the resource.
      */
      if ((err = dav_validate_request(r, resource, 0, NULL, &multi_status,
  				    resource_state == DAV_RESOURCE_NULL ?
  				    DAV_VALIDATE_PARENT :
  				    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, multi_status);
      }
  
      /* if versioned resource, make sure parent is checked out */
      if ((err = dav_ensure_resource_writable(r, resource, 1 /* parent_only */,
  					    &resource_parent,
  					    NULL, NULL,
  					    &parent_was_writable)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      /* try to create the collection */
      resource->collection = 1;
      err = (*resource->hooks->create_collection)(r->pool, resource);
  
      /* restore modifiability of parent back to what it was */
      err2 = dav_revert_resource_writability(r, NULL, resource_parent,
  					  err != NULL /* undo if error */,
  					  0, 0, parent_was_writable);
  
      /* check for errors now */
      if (err != NULL) {
  	return dav_handle_err(r, err, NULL);
      }
      if (err2 != NULL) {
  	/* just log a warning */
  	err = dav_push_error(r->pool, err->status, 0,
  			     "The MKCOL was successful, but there "
  			     "was a problem reverting the writability of "
  			     "its parent collection.",
  			     err2);
  	dav_log_err(r, err, APLOG_WARNING);
      }
  
      if (locks_hooks != NULL) {
  	dav_lockdb *lockdb;
  
  	if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
  	    /* The directory creation was successful, but the locking failed. */
  	    err = dav_push_error(r->pool, err->status, 0,
  				 "The MKCOL was successful, but there "
  				 "was a problem opening the lock database "
  				 "which prevents inheriting locks from the "
  				 "parent resources.",
  				 err);
  	    return dav_handle_err(r, err, NULL);
  	}
  
  	/* notify lock system that we have created/replaced a resource */
  	err = dav_notify_created(r, lockdb, resource, resource_state, 0);
  
  	(*locks_hooks->close_lockdb)(lockdb);
  
  	if (err != NULL) {
  	    /* The dir creation was successful, but the locking failed. */
  	    err = dav_push_error(r->pool, err->status, 0,
  				 "The MKCOL was successful, but there "
  				 "was a problem updating its lock "
  				 "information.",
  				 err);
  	    return dav_handle_err(r, err, NULL);
  	}
      }
  
      /* return an appropriate response (HTTP_CREATED) */
      return dav_created(r, NULL, resource, "Collection", 0);
  }
  
  /* handle the COPY and MOVE methods */
  static int dav_method_copymove(request_rec *r, int is_move)
  {
      dav_resource *resource;
      dav_resource *resource_parent = NULL;
      dav_resource *resnew;
      dav_resource *resnew_parent = NULL;
      const char *body;
      const char *dest;
      dav_error *err;
      dav_error *err2;
      dav_error *err3;
      dav_response *multi_response;
      dav_lookup_result lookup;
      int is_dir;
      int overwrite;
      int depth;
      int result;
      dav_lockdb *lockdb;
      int replaced;
      int src_parent_was_writable = 0;
      int dst_parent_was_writable = 0;
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
      if (!resource->exists) {
  	/* Apache will supply a default error for this. */
  	return HTTP_NOT_FOUND;
      }
  
      /* If not a file or collection resource, COPY/MOVE not allowed */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
          body = ap_psprintf(r->pool,
                             "Cannot COPY/MOVE resource %s.",
                             ap_escape_html(r->pool, r->uri));
  	return dav_error_response(r, HTTP_METHOD_NOT_ALLOWED, body);
      }
  
      /* get the destination URI */
      dest = ap_table_get(r->headers_in, "Destination");
      if (dest == NULL) {
  	/* Look in headers provided by Netscape's Roaming Profiles */
  	const char *nscp_host = ap_table_get(r->headers_in, "Host");
  	const char *nscp_path = ap_table_get(r->headers_in, "New-uri");
  
  	if (nscp_host != NULL && nscp_path != NULL)
  	    dest = ap_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path);
      }
      if (dest == NULL) {
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "The request is missing a Destination header.");
  	return HTTP_BAD_REQUEST;
      }
  
      lookup = dav_lookup_uri(dest, r);
      if (lookup.rnew == NULL) {
  	if (lookup.err.status == HTTP_BAD_REQUEST) {
  	    /* This supplies additional information for the default message. */
  	    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  			  lookup.err.desc);
  	    return HTTP_BAD_REQUEST;
  	}
  
  	/* ### this assumes that dav_lookup_uri() only generates a status
  	 * ### that Apache can provide a status line for!! */
  
  	return dav_error_response(r, lookup.err.status, lookup.err.desc);
      }
      if (lookup.rnew->status != HTTP_OK) {
  	/* ### how best to report this... */
  	return dav_error_response(r, lookup.rnew->status,
  				  "Destination URI had an error.");
      }
  
      /* Resolve destination resource */
      result = dav_get_resource(lookup.rnew, &resnew);
      if (result != OK)
          return result;
  
      /* are the two resources handled by the same repository? */
      if (resource->hooks != resnew->hooks) {
  	/* ### this message exposes some backend config, but screw it... */
  	return dav_error_response(r, HTTP_BAD_GATEWAY,
  				  "Destination URI is handled by a "
  				  "different repository than the source URI. "
  				  "MOVE or COPY between repositories is "
  				  "not possible.");
      }
  
      /* get and parse the overwrite header value */
      if ((overwrite = dav_get_overwrite(r)) < 0) {
  	/* dav_get_overwrite() supplies additional information for the
  	 * default message. */
  	return HTTP_BAD_REQUEST;
      }
  
      /* quick failure test: if dest exists and overwrite is false. */
      if (resnew->exists && !overwrite) {
  	/* Supply some text for the error response body. */
  	return dav_error_response(r, HTTP_PRECONDITION_FAILED,
                                    "Destination is not empty and "
                                    "Overwrite is not \"T\"");
      }
  
      /* are the source and destination the same? */
      if ((*resource->hooks->is_same_resource)(resource, resnew)) {
  	/* Supply some text for the error response body. */
  	return dav_error_response(r, HTTP_FORBIDDEN,
                                    "Source and Destination URIs are the same.");
  
      }
  
      is_dir = resource->collection;
  
      /* get and parse the Depth header value. "0" and "infinity" are legal. */
      if ((depth = dav_get_depth(r, DAV_INFINITY)) < 0) {
  	/* dav_get_depth() supplies additional information for the
  	 * default message. */
  	return HTTP_BAD_REQUEST;
      }
      if (depth == 1) {
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		   "Depth must be \"0\" or \"infinity\" for COPY or MOVE.");
  	return HTTP_BAD_REQUEST;
      }
      if (is_move && is_dir && depth != DAV_INFINITY) {
  	/* This supplies additional information for the default message. */
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		    "Depth must be \"infinity\" when moving a collection.");
  	return HTTP_BAD_REQUEST;
      }
  
      /*
      ** Check If-Headers and existing locks for each resource in the source
      ** if we are performing a MOVE. We will return a 424 response with a
      ** DAV:multistatus body. The multistatus responses will contain the
      ** information about any resource that fails the validation.
      **
      ** We check the parent resource, too, since this is a MOVE. Moving the
      ** resource effectively removes it from the parent collection, so we
      ** must ensure that we have met the appropriate conditions.
      **
      ** If a problem occurs with the Request-URI itself, then a plain error
      ** (rather than a multistatus) will be returned.
      */
      if (is_move
  	&& (err = dav_validate_request(r, resource, depth, NULL,
  				       &multi_response,
  				       DAV_VALIDATE_PARENT
                                         | DAV_VALIDATE_USE_424,
                                         NULL)) != NULL) {
  	err = dav_push_error(r->pool, err->status, 0,
  			     ap_psprintf(r->pool,
  					 "Could not MOVE %s due to a failed "
  					 "precondition on the source "
  					 "(e.g. locks).",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	return dav_handle_err(r, err, multi_response);
      }
  
      /*
      ** Check If-Headers and existing locks for destination. Note that we
      ** use depth==infinity since the target (hierarchy) will be deleted
      ** before the move/copy is completed.
      **
      ** Note that we are overwriting the target, which implies a DELETE, so
      ** we are subject to the error/response rules as a DELETE. Namely, we
      ** will return a 424 error if any of the validations fail.
      ** (see dav_method_delete() for more information)
      */
      if ((err = dav_validate_request(lookup.rnew, resnew, DAV_INFINITY, NULL,
  				    &multi_response,
  				    DAV_VALIDATE_PARENT
                                      | DAV_VALIDATE_USE_424, NULL)) != NULL) {
  	err = dav_push_error(r->pool, err->status, 0,
  			     ap_psprintf(r->pool,
  					 "Could not MOVE/COPY %s due to a "
  					 "failed precondition on the "
  					 "destination (e.g. locks).",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	return dav_handle_err(r, err, multi_response);
      }
  
      if (is_dir
  	&& depth == DAV_INFINITY
  	&& (*resource->hooks->is_parent_resource)(resource, resnew)) {
  	/* Supply some text for the error response body. */
  	return dav_error_response(r, HTTP_FORBIDDEN,
                                    "Source collection contains the "
                                    "Destination.");
  
      }
      if (is_dir
  	&& (*resnew->hooks->is_parent_resource)(resnew, resource)) {
  	/* The destination must exist (since it contains the source), and
  	 * a condition above implies Overwrite==T. Obviously, we cannot
  	 * delete the Destination before the MOVE/COPY, as that would
  	 * delete the Source.
  	 */
  
  	/* Supply some text for the error response body. */
  	return dav_error_response(r, HTTP_FORBIDDEN,
                                    "Destination collection contains the Source "
                                    "and Overwrite has been specified.");
      }
  
      /* ### for now, we don't need anything in the body */
      if ((result = ap_discard_request_body(r)) != OK) {
  	return result;
      }
  
      if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      /* remove any locks from the old resources */
      /*
      ** ### this is Yet Another Traversal. if we do a rename(), then we
      ** ### really don't have to do this in some cases since the inode
      ** ### values will remain constant across the move. but we can't
      ** ### know that fact from outside the provider :-(
      **
      ** ### note that we now have a problem atomicity in the move/copy
      ** ### since a failure after this would have removed locks (technically,
      ** ### this is okay to do, but really...)
      */
      if (is_move && lockdb != NULL) {
  	/* ### this is wrong! it blasts direct locks on parent resources */
  	/* ### pass lockdb! */
  	(void)dav_unlock(r, resource, NULL);
      }
  
      /* remember whether target resource existed */
      replaced = resnew->exists;
  
      /* if this is a move, then the source parent collection will be modified */
      if (is_move) {
          if ((err = dav_ensure_resource_writable(r, resource,
  						1 /* parent_only */,
  						&resource_parent,
  						NULL, NULL,
  						&src_parent_was_writable)) != NULL) {
  	    if (lockdb != NULL)
  		(*lockdb->hooks->close_lockdb)(lockdb);
  
  	    /* ### add a higher-level description? */
  	    return dav_handle_err(r, err, NULL);
          }
      }
  
      /* prepare the destination collection for modification */
      if ((err = dav_ensure_resource_writable(r, resnew, 1 /* parent_only */,
  					    &resnew_parent,
  					    NULL, NULL,
  					    &dst_parent_was_writable)) != NULL) {
          /* could not make destination writable:
  	 * if move, restore state of source parent
  	 */
          if (is_move) {
              (void) dav_revert_resource_writability(r, NULL, resource_parent,
  						   1 /* undo */,
  						   0, 0,
  						   src_parent_was_writable);
          }
  
  	if (lockdb != NULL)
  	    (*lockdb->hooks->close_lockdb)(lockdb);
  
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      /* If source and destination parents are the same, then
       * use the same object, so status updates to one are reflected
       * in the other.
       */
      if (resource_parent != NULL
          && (*resource_parent->hooks->is_same_resource)(resource_parent,
                                                         resnew_parent))
          resnew_parent = resource_parent;
  
      /* New resource will be same kind as source */
      resnew->collection = resource->collection;
  
      /* If target exists, remove it first (we know Ovewrite must be TRUE).
       * Then try to copy/move the resource.
       */
      if (resnew->exists)
  	err = (*resnew->hooks->remove_resource)(resnew, &multi_response);
  
      if (err == NULL) {
  	if (is_move)
  	    err = (*resource->hooks->move_resource)(resource, resnew,
                                                      &multi_response);
  	else
  	    err = (*resource->hooks->copy_resource)(resource, resnew, depth,
                                                      &multi_response);
      }
  
      /* restore parent collection states */
      err2 = dav_revert_resource_writability(r, NULL, resnew_parent,
  					   err != NULL /* undo if error */,
  					   0, 0, dst_parent_was_writable);
  
      if (is_move) {
          err3 = dav_revert_resource_writability(r, NULL, resource_parent,
  					       err != NULL /* undo if error */,
  					       0, 0, src_parent_was_writable);
      }
      else
  	err3 = NULL;
  
      /* check for error from remove/copy/move operations */
      if (err != NULL) {
  	if (lockdb != NULL)
  	    (*lockdb->hooks->close_lockdb)(lockdb);
  
  	err = dav_push_error(r->pool, err->status, 0,
  			     ap_psprintf(r->pool,
  					 "Could not MOVE/COPY %s.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	return dav_handle_err(r, err, multi_response);
      }
  
      /* check for errors from reverting writability */
      if (err2 != NULL) {
  	/* just log a warning */
  	err = dav_push_error(r->pool, err2->status, 0,
  			     "The MOVE/COPY was successful, but there was a "
  			     "problem reverting the writability of the "
  			     "source parent collection.",
  			     err2);
  	dav_log_err(r, err, APLOG_WARNING);
      }
      if (err3 != NULL) {
  	/* just log a warning */
  	err = dav_push_error(r->pool, err3->status, 0,
  			     "The MOVE/COPY was successful, but there was a "
  			     "problem reverting the writability of the "
  			     "destination parent collection.",
  			     err3);
  	dav_log_err(r, err, APLOG_WARNING);
      }
  
      /* propagate any indirect locks at the target */
      if (lockdb != NULL) {
  	int resource_state = dav_get_resource_state(lookup.rnew, resnew);
  
  	/* notify lock system that we have created/replaced a resource */
  	err = dav_notify_created(r, lockdb, resnew, resource_state, depth);
  
  	(*lockdb->hooks->close_lockdb)(lockdb);
  
  	if (err != NULL) {
  	    /* The move/copy was successful, but the locking failed. */
  	    err = dav_push_error(r->pool, err->status, 0,
  				 "The MOVE/COPY was successful, but there "
  				 "was a problem updating the lock "
  				 "information.",
  				 err);
  	    return dav_handle_err(r, err, NULL);
  	}
      }
  
      /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
      return dav_created(r, lookup.rnew, resnew, "Destination", replaced);
  }
  
  /* dav_method_lock:  Handler to implement the DAV LOCK method
  **    Returns appropriate HTTP_* response.
  */
  static int dav_method_lock(request_rec *r)
  {
      dav_error *err;
      dav_resource *resource;
      const dav_hooks_locks *locks_hooks;
      int result;
      int depth;
      int new_lock_request = 0;
      dav_xml_doc *doc = NULL;
      dav_lock *lock;
      dav_response *multi_response = NULL;
      dav_lockdb *lockdb;
      int resource_state;
  
      /* If no locks provider, decline the request */
      locks_hooks = DAV_GET_HOOKS_LOCKS(r);
      if (locks_hooks == NULL)
          return DECLINED;
  
      if ((result = dav_parse_input(r, &doc)) != OK)
  	return result;
  
      depth = dav_get_depth(r, DAV_INFINITY);
      if (depth != 0 && depth != DAV_INFINITY) {
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "Depth must be 0 or \"infinity\" for LOCK.");
  	return HTTP_BAD_REQUEST;
      }
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
  
      /*
      ** Open writable. Unless an error occurs, we'll be
      ** writing into the database.
      */
      if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, NULL);
      }
  
      if (doc != NULL) {
          if ((err = dav_lock_parse_lockinfo(r, resource, lockdb, doc,
  	    			           &lock)) != NULL) {
  	    /* ### add a higher-level description to err? */
              goto error;
          }
          new_lock_request = 1;
  
          lock->auth_user = ap_pstrdup(r->pool, r->connection->user);
      }
  
      resource_state = dav_get_resource_state(r, resource);
  
      /*
      ** Check If-Headers and existing locks.
      **
      ** If this will create a locknull resource, then the LOCK will affect
      ** the parent collection (much like a PUT/MKCOL). For that case, we must
      ** validate the parent resource's conditions.
      */
      if ((err = dav_validate_request(r, resource, depth, NULL, &multi_response,
                                      (resource_state == DAV_RESOURCE_NULL
                                       ? DAV_VALIDATE_PARENT
                                       : DAV_VALIDATE_RESOURCE)
                                      | (new_lock_request ? lock->scope : 0)
                                      | DAV_VALIDATE_ADD_LD,
                                      lockdb)) != OK) {
  	err = dav_push_error(r->pool, err->status, 0,
  			     ap_psprintf(r->pool,
  					 "Could not LOCK %s due to a failed "
  					 "precondition (e.g. other locks).",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	goto error;
      }
  
      if (new_lock_request == 0) {
  	dav_locktoken_list *ltl;
  		
  	/*
  	** Refresh request 
  	** ### Assumption:  We can renew multiple locks on the same resource
  	** ### at once. First harvest all the positive lock-tokens given in
  	** ### the If header. Then modify the lock entries for this resource
  	** ### with the new Timeout val.
  	*/
  
  	if ((err = dav_get_locktoken_list(r, &ltl)) != NULL) {
  	    err = dav_push_error(r->pool, err->status, 0,
  				 ap_psprintf(r->pool,
  					     "The lock refresh for %s failed "
  					     "because no lock tokens were "
  					     "specified in an \"If:\" "
  					     "header.",
  					     ap_escape_html(r->pool, r->uri)),
  				 err);
  	    goto error;
  	}
  
  	if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, ltl,
  						 dav_get_timeout(r),
  						 &lock)) != NULL) {
  	    /* ### add a higher-level description to err? */
  	    goto error;
  	}
      } else {
  	/* New lock request */
          char *locktoken_txt;
  	dav_dir_conf *conf;
  
  	conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						     &dav_module);
  
  	/* apply lower bound (if any) from DAVMinTimeout directive */
  	if (lock->timeout != DAV_TIMEOUT_INFINITE
              && lock->timeout < time(NULL) + conf->locktimeout)
  	    lock->timeout = time(NULL) + conf->locktimeout;
  
          err = dav_add_lock(r, resource, lockdb, lock, &multi_response);
  	if (err != NULL) {
  	    /* ### add a higher-level description to err? */
  	    goto error;
  	}
  
          locktoken_txt = ap_pstrcat(r->pool, "<",
  				   (*locks_hooks->format_locktoken)(r->pool, lock->locktoken),
  				   ">", NULL);
  
  	ap_table_set(r->headers_out, "Lock-Token", locktoken_txt);
      }
  
      (*locks_hooks->close_lockdb)(lockdb);
  
      r->status = HTTP_OK;
      r->content_type = DAV_XML_CONTENT_TYPE;
  
      ap_send_http_header(r);
      ap_soft_timeout("send LOCK response", r);
  
      ap_rputs(DAV_XML_HEADER DEBUG_CR "<D:prop xmlns:D=\"DAV:\">" DEBUG_CR, r);
      if (lock == NULL)
  	ap_rputs("<D:lockdiscovery/>" DEBUG_CR, r);
      else {
  	ap_rprintf(r,
  		   "<D:lockdiscovery>" DEBUG_CR
  		   "%s" DEBUG_CR
  		   "</D:lockdiscovery>" DEBUG_CR,
  		   dav_lock_get_activelock(r, lock, NULL));
      }
      ap_rputs("</D:prop>", r);
  
      ap_kill_timeout(r);
  
      /* the response has been sent. */
      return DONE;
  
    error:
      (*locks_hooks->close_lockdb)(lockdb);
      return dav_handle_err(r, err, multi_response);
  }
  
  /* dav_method_unlock:  Handler to implement the DAV UNLOCK method
   *    Returns appropriate HTTP_* response.
   */
  static int dav_method_unlock(request_rec *r)
  {
      dav_error *err;
      dav_resource *resource;
      const dav_hooks_locks *locks_hooks;
      int result;
      const char *const_locktoken_txt;
      char *locktoken_txt;
      dav_locktoken *locktoken = NULL;
      int resource_state;
      dav_response *multi_response;
  
      /* If no locks provider, decline the request */
      locks_hooks = DAV_GET_HOOKS_LOCKS(r);
      if (locks_hooks == NULL)
          return DECLINED;
  
      if ((const_locktoken_txt = ap_table_get(r->headers_in, "Lock-Token")) == NULL) {
  	ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		      "Unlock failed (%s):  No Lock-Token specified in header", r->filename);
  	return HTTP_BAD_REQUEST;
      }
  
      locktoken_txt = ap_pstrdup(r->pool, const_locktoken_txt);
      if (locktoken_txt[0] != '<') {
  	/* ### should provide more specifics... */
  	return HTTP_BAD_REQUEST;
      }
      locktoken_txt++;
  
      if (locktoken_txt[strlen(locktoken_txt) - 1] != '>') {
  	/* ### should provide more specifics... */
  	return HTTP_BAD_REQUEST;
      }
      locktoken_txt[strlen(locktoken_txt) - 1] = '\0';
  		
      if ((err = (*locks_hooks->parse_locktoken)(r->pool, locktoken_txt,
  					       &locktoken)) != NULL) {
  	err = dav_push_error(r->pool, HTTP_BAD_REQUEST, 0,
  			     ap_psprintf(r->pool,
  					 "The UNLOCK on %s failed -- an "
  					 "invalid lock token was specified "
  					 "in the \"If:\" header.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
  	return dav_handle_err(r, err, NULL);
      }
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
  
      resource_state = dav_get_resource_state(r, resource);
  
      /*
      ** Check If-Headers and existing locks.
      **
      ** Note: depth == 0 normally requires no multistatus response. However,
      ** if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
      ** other than the Request-URI, thereby requiring a multistatus.
      **
      ** If the resource is a locknull resource, then the UNLOCK will affect
      ** the parent collection (much like a delete). For that case, we must
      ** validate the parent resource's conditions.
      */
      if ((err = dav_validate_request(r, resource, 0, locktoken,
                                      &multi_response,
                                      resource_state == DAV_RESOURCE_LOCK_NULL
                                      ? DAV_VALIDATE_PARENT
                                      : DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
  	/* ### add a higher-level description? */
  	return dav_handle_err(r, err, multi_response);
      }
  
      /* ### RFC 2518 s. 8.11: If this resource is locked by locktoken,
       *     _all_ resources locked by locktoken are released.  It does not say
       *     resource has to be the root of an infinte lock.  Thus, an UNLOCK
       *     on any part of an infinte lock will remove the lock on all resources.
       *     
       *     For us, if r->filename represents an indirect lock (part of an infinity lock),
       *     we must actually perform an UNLOCK on the direct lock for this resource.
       */     
      if ((result = dav_unlock(r, resource, locktoken)) != OK) {
  	return result;
      }
  
      return HTTP_NO_CONTENT;
  }
  
  /* handle the SEARCH method from DASL */
  static int dav_method_search(request_rec *r)
  {
      /* ### we know this method, but we won't allow it yet */
      /* Apache will supply a default error for this. */
      return HTTP_METHOD_NOT_ALLOWED;
  
      /* Do some error checking, like if the querygrammar is
       * supported by the content type, and then pass the
       * request on to the appropriate query module.
       */
  }
  
  /* handle the CHECKOUT method */
  static int dav_method_checkout(request_rec *r)
  {
      dav_resource *resource;
      const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
      dav_error *err;
      int result;
  
      /* If no versioning provider, decline the request */
      if (vsn_hooks == NULL)
          return DECLINED;
  
      /* ### eventually check body for DAV:checkin-policy */
      if ((result = ap_discard_request_body(r)) != OK) {
  	return result;
      }
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
      if (!resource->exists) {
          /* Apache will supply a default error for this. */
          return HTTP_NOT_FOUND;
      }
  
      /* Check the state of the resource: must be a file or collection,
       * must be versioned, and must not already be checked out.
       */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "Cannot checkout this type of resource.");
      }
  
      if (!resource->versioned) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "Cannot checkout unversioned resource.");
      }
  
      if (resource->working) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "The resource is already checked out to the workspace.");
      }
  
      /* ### do lock checks, once behavior is defined */
  
      /* Do the checkout */
      if ((err = (*vsn_hooks->checkout)(resource)) != NULL) {
  	err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
  			     ap_psprintf(r->pool,
  					 "Could not CHECKOUT resource %s.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
          return dav_handle_err(r, err, NULL);
      }
  
      /* no body */
      ap_set_content_length(r, 0);
      ap_send_http_header(r);
  
      return DONE;
  }
  
  /* handle the UNCHECKOUT method */
  static int dav_method_uncheckout(request_rec *r)
  {
      dav_resource *resource;
      const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
      dav_error *err;
      int result;
  
      /* If no versioning provider, decline the request */
      if (vsn_hooks == NULL)
          return DECLINED;
  
      if ((result = ap_discard_request_body(r)) != OK) {
  	return result;
      }
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
      if (!resource->exists) {
          /* Apache will supply a default error for this. */
          return HTTP_NOT_FOUND;
      }
  
      /* Check the state of the resource: must be a file or collection,
       * must be versioned, and must be checked out.
       */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "Cannot uncheckout this type of resource.");
      }
  
      if (!resource->versioned) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "Cannot uncheckout unversioned resource.");
      }
  
      if (!resource->working) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "The resource is not checked out to the workspace.");
      }
  
      /* ### do lock checks, once behavior is defined */
  
      /* Do the uncheckout */
      if ((err = (*vsn_hooks->uncheckout)(resource)) != NULL) {
  	err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
  			     ap_psprintf(r->pool,
  					 "Could not UNCHECKOUT resource %s.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
          return dav_handle_err(r, err, NULL);
      }
  
      /* no body */
      ap_set_content_length(r, 0);
      ap_send_http_header(r);
  
      return DONE;
  }
  
  /* handle the CHECKIN method */
  static int dav_method_checkin(request_rec *r)
  {
      dav_resource *resource;
      const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
      dav_error *err;
      int result;
  
      /* If no versioning provider, decline the request */
      if (vsn_hooks == NULL)
          return DECLINED;
  
      if ((result = ap_discard_request_body(r)) != OK) {
  	return result;
      }
  
      /* Ask repository module to resolve the resource */
      result = dav_get_resource(r, &resource);
      if (result != OK)
          return result;
      if (!resource->exists) {
          /* Apache will supply a default error for this. */
          return HTTP_NOT_FOUND;
      }
  
      /* Check the state of the resource: must be a file or collection,
       * must be versioned, and must be checked out.
       */
      if (resource->type != DAV_RESOURCE_TYPE_REGULAR) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "Cannot checkin this type of resource.");
      }
  
      if (!resource->versioned) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "Cannot checkin unversioned resource.");
      }
  
      if (!resource->working) {
  	return dav_error_response(r, HTTP_CONFLICT,
  				  "The resource is not checked out to the workspace.");
      }
  
      /* ### do lock checks, once behavior is defined */
  
      /* Do the checkin */
      if ((err = (*vsn_hooks->checkin)(resource)) != NULL) {
  	err = dav_push_error(r->pool, HTTP_CONFLICT, 0,
  			     ap_psprintf(r->pool,
  					 "Could not CHECKIN resource %s.",
  					 ap_escape_html(r->pool, r->uri)),
  			     err);
          return dav_handle_err(r, err, NULL);
      }
  
      /* no body */
      ap_set_content_length(r, 0);
      ap_send_http_header(r);
  
      return DONE;
  }
  
  
  /*
   * Response handler for DAV resources
   */
  static int dav_handler(request_rec *r)
  {
      dav_dir_conf *conf;
  
      /* quickly ignore any HTTP/0.9 requests */
      if (r->assbackwards) {
  	return DECLINED;
      }
  
      /* ### do we need to do anything with r->proxyreq ?? */
  
      conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						 &dav_module);
  
      /*
       * Set up the methods mask, since that's one of the reasons this handler
       * gets called, and lower-level things may need the info.
       *
       * First, set the mask to the methods we handle directly.  Since by
       * definition we own our managed space, we unconditionally set
       * the r->allowed field rather than ORing our values with anything
       * any other module may have put in there.
       *
       * These are the HTTP-defined methods that we handle directly.
       */
      r->allowed = 0
          | (1 << M_GET)
  	| (1 << M_PUT)
  	| (1 << M_DELETE)
  	| (1 << M_OPTIONS)
  	| (1 << M_INVALID);
      /*
       * These are the DAV methods we handle.
       */
      r->allowed |= 0
  	| (1 << M_COPY)
  	| (1 << M_LOCK)
  	| (1 << M_UNLOCK)
  	| (1 << M_MKCOL)
  	| (1 << M_MOVE)
  	| (1 << M_PROPFIND)
  	| (1 << M_PROPPATCH);
      /*
       * These are methods that we don't handle directly, but let the
       * server's default handler do for us as our agent.
       */
      r->allowed |= 0
  	| (1 << M_POST);
   
      /* ### hrm. if we return HTTP_METHOD_NOT_ALLOWED, then an Allow header
       * ### is sent; it will need the other allowed states; since the default
       * ### handler is not called on error, then it doesn't add the other
       * ### allowed states, so we must */
      /* ### we might need to refine this for just where we return the error.
       * ### also, there is the issue with other methods (see ISSUES) */
      /* ### more work necessary, now that we have M_foo for DAV methods */
  
      /* dispatch the appropriate method handler */
      if (r->method_number == M_GET) {
  	return dav_method_get(r);
      }
  
      if (r->method_number == M_PUT) {
  	return dav_method_put(r);
      }
  
      if (r->method_number == M_POST) {
  	return dav_method_post(r);
      }
  
      if (r->method_number == M_DELETE) {
  	return dav_method_delete(r);
      }
  
      if (r->method_number == M_OPTIONS) {
  	return dav_method_options(r);
      }
  
      if (r->method_number == M_PROPFIND) {
  	return dav_method_propfind(r);
      }
  
      if (r->method_number == M_PROPPATCH) {
  	return dav_method_proppatch(r);
      }
  
      if (r->method_number == M_MKCOL) {
  	return dav_method_mkcol(r);
      }
  
      if (r->method_number == M_COPY) {
  	return dav_method_copymove(r, DAV_DO_COPY);
      }
  
      if (r->method_number == M_MOVE) {
  	return dav_method_copymove(r, DAV_DO_MOVE);
      }
  
      if (r->method_number == M_LOCK) {
  	return dav_method_lock(r);
      }
  
      if (r->method_number == M_UNLOCK) {
  	return dav_method_unlock(r);
      }
  
      /*
       * NOTE: When Apache moves creates defines for the add'l DAV methods,
       *       then it will no longer use M_INVALID. This code must be
       *       updated each time Apache adds method defines.
       */
      if (r->method_number != M_INVALID) {
  	return DECLINED;
      }
  
      if (!strcmp(r->method, "SEARCH")) {
  	return dav_method_search(r);
      }
  
      if (!strcmp(r->method, "CHECKOUT")) {
  	return dav_method_checkout(r);
      }
  
      if (!strcmp(r->method, "UNCHECKOUT")) {
  	return dav_method_uncheckout(r);
      }
  
      if (!strcmp(r->method, "CHECKIN")) {
  	return dav_method_checkin(r);
      }
  
  #if 0
      if (!strcmp(r->method, "MKRESOURCE")) {
  	return dav_method_mkresource(r);
      }
  
      if (!strcmp(r->method, "REPORT")) {
  	return dav_method_report(r);
      }
  #endif
  
      /* ### add'l methods for Advanced Collections, ACLs, DASL */
  
      return DECLINED;
  }
  
  static int dav_type_checker(request_rec *r)
  {
      dav_dir_conf *conf;
  
      conf = (dav_dir_conf *) ap_get_module_config(r->per_dir_config,
  						 &dav_module);
  
      /* if DAV is not enabled, then we've got nothing to do */
      if (conf->enabled != DAV_ENABLED_ON) {
  	return DECLINED;
      }
  
      if (r->method_number == M_GET) {
  	/*
  	** ### need some work to pull Content-Type and Content-Language
  	** ### from the property database.
  	*/
  	    
  	/*
  	** If the repository hasn't indicated that it will handle the
  	** GET method, then just punt.
  	**
  	** ### this isn't quite right... taking over the response can break
  	** ### things like mod_negotiation. need to look into this some more.
  	*/
  	if (!conf->handle_get)
  	    return DECLINED;
      }
  
      /* ### we should (instead) trap the ones that we DO understand */
      /* ### the handler DOES handle POST, so we need to fix one of these */
      if (r->method_number != M_POST) {
  
  	/*
  	** ### anything else to do here? could another module and/or
  	** ### config option "take over" the handler here? i.e. how do
  	** ### we lock down this hierarchy so that we are the ultimate
  	** ### arbiter? (or do we simply depend on the administrator
  	** ### to avoid conflicting configurations?)
  	**
  	** ### I think the OK stops running type-checkers. need to look.
  	*/
  	r->handler = "dav-handler";
  	return OK;
      }
  
      return DECLINED;
  }
  
  
  /*---------------------------------------------------------------------------
  **
  ** Configuration info for the module
  */
  
  static const command_rec dav_cmds[] =
  {
      {
  	"DAV",
  	dav_cmd_dav,
  	NULL,
  	ACCESS_CONF,            /* per directory/location */
  	FLAG,
  	"turn DAV on/off for a directory or location"
      },
      {
  	"DAVLockDB",
  	dav_cmd_davlockdb,
  	NULL,
  	RSRC_CONF,              /* per server */
  	TAKE1,
  	"specify a lock database"
      },
      {
  	"DAVMinTimeout",
  	dav_cmd_davmintimeout,
  	NULL,
  	ACCESS_CONF|RSRC_CONF,  /* per directory/location, or per server */
  	TAKE1,
  	"specify minimum allowed timeout"
      },
      {
  	"DAVDepthInfinity",
  	dav_cmd_davdepthinfinity,
  	NULL,
  	ACCESS_CONF|RSRC_CONF,  /* per directory/location, or per server */
  	FLAG,
  	"allow Depth infinity PROPFIND requests"
      },
      {
  	"DAVParam",
  	dav_cmd_davparam,
  	NULL,
  	ACCESS_CONF|RSRC_CONF,  /* per directory/location, or per server */
  	TAKE2,
  	"DAVParam <parameter name> <parameter value>"
      },
      {
  	"LimitXMLRequestBody",
  	dav_cmd_limitxmlrequestbody,
  	NULL,
  	ACCESS_CONF|RSRC_CONF,  /* per directory/location, or per server */
  	TAKE1,
  	"Limit (in bytes) on maximum size of an XML-based request body"
      },
      { NULL }
  };
  
  static const handler_rec dav_handlers[] =
  {
      {"dav-handler", dav_handler},
      { NULL }
  };
  
  module MODULE_VAR_EXPORT dav_module =
  {
      STANDARD_MODULE_STUFF,
      dav_init_handler,		/* initializer */
      dav_create_dir_config,	/* dir config creater */
      dav_merge_dir_config,	/* dir merger --- default is to override */
      dav_create_server_config,	/* server config */
      dav_merge_server_config,	/* merge server config */
      dav_cmds,			/* command table */
      dav_handlers,		/* handlers */
      NULL,			/* filename translation */
      NULL,			/* check_user_id */
      NULL,			/* check auth */
      NULL,			/* check access */
      dav_type_checker,		/* type_checker */
      NULL,			/* fixups */
      NULL,			/* logger */
      NULL,			/* header parser */
      NULL,			/* child_init */
      NULL,			/* child_exit */
      NULL			/* post read-request */
  };
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/mod_dav.h
  
  Index: mod_dav.h
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV extension module for Apache 1.3.*
  **
  ** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
  */
  
  #ifndef _MOD_DAV_H_
  #define _MOD_DAV_H_
  
  #ifdef __cplusplus
  extern "C" {
  #endif
  
  #include "httpd.h"
  
  
  #define DAV_VERSION		"1.0.1"
  
  #define DAV_XML_HEADER		"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
  #define DAV_XML_CONTENT_TYPE	"text/xml; charset=\"utf-8\""
  
  #define DAV_READ_BLOCKSIZE	2048	/* used for reading input blocks */
  
  #ifdef WIN32
  #include <limits.h>
  typedef int ssize_t;
  #endif /* WIN32 */
  
  #define DAV_RESPONSE_BODY_1	"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<HTML><HEAD>\n<TITLE>"
  #define DAV_RESPONSE_BODY_2	"</TITLE>\n</HEAD><BODY>\n<H1>"
  #define DAV_RESPONSE_BODY_3	"</H1>\n"
  #define DAV_RESPONSE_BODY_4	"</BODY></HTML>\n"
  
  #define DAV_DO_COPY		0
  #define DAV_DO_MOVE		1
  
  
  #if 1
  #define DAV_DEBUG 1
  #define DEBUG_CR	"\n"
  #define DBG0(f)		ap_log_error(APLOG_MARK, \
  				APLOG_ERR|APLOG_NOERRNO, NULL, (f))
  #define DBG1(f,a1)	ap_log_error(APLOG_MARK, \
  				APLOG_ERR|APLOG_NOERRNO, NULL, f, a1)
  #define DBG2(f,a1,a2)	ap_log_error(APLOG_MARK, \
  				APLOG_ERR|APLOG_NOERRNO, NULL, f, a1, a2)
  #define DBG3(f,a1,a2,a3) ap_log_error(APLOG_MARK, \
  				APLOG_ERR|APLOG_NOERRNO, NULL, f, a1, a2, a3)
  #else
  #undef DAV_DEBUG
  #define DEBUG_CR	""
  #endif
  
  #define DAV_INFINITY	INT_MAX	/* for the Depth: header */
  
  
  /* --------------------------------------------------------------------
  **
  ** ERROR MANAGEMENT
  */
  
  /*
  ** dav_error structure.
  **
  ** In most cases, mod_dav uses a pointer to a dav_error structure. If the
  ** pointer is NULL, then no error has occurred.
  **
  ** In certain cases, a dav_error structure is directly used. In these cases,
  ** a status value of 0 means that an error has not occurred.
  **
  ** Note: this implies that status != 0 whenever an error occurs.
  **
  ** The desc field is optional (it may be NULL). When NULL, it typically
  ** implies that Apache has a proper description for the specified status.
  */
  typedef struct dav_error {
      int status;			/* suggested HTTP status (0 for no error) */
      int error_id;		/* DAV-specific error ID */
      const char *desc;		/* DAV:responsedescription and error log */
  
      int save_errno;		/* copy of errno causing the error */
  
      struct dav_error *prev;	/* previous error (in stack) */
  
      /* deferred computation of the description */
      void (*compute_desc)(struct dav_error *err, pool *p);
      int ctx_i;
      const char *ctx_s;
      void *ctx_p;
  
  } dav_error;
  
  /*
  ** Create a new error structure. save_errno will be filled with the current
  ** errno value.
  */
  dav_error *dav_new_error(pool *p, int status, int error_id, const char *desc);
  
  /*
  ** Push a new error description onto the stack of errors.
  **
  ** This function is used to provide an additional description to an existing
  ** error.
  **
  ** <status> should contain the caller's view of what the current status is,
  ** given the underlying error. If it doesn't have a better idea, then the
  ** caller should pass prev->status.
  **
  ** <error_id> can specify a new error_id since the topmost description has
  ** changed.
  */
  dav_error *dav_push_error(pool *p, int status, int error_id, const char *desc,
  			  dav_error *prev);
  
  
  /* error ID values... */
  
  /* IF: header errors */
  #define DAV_ERR_IF_PARSE		100	/* general parsing error */
  #define DAV_ERR_IF_MULTIPLE_NOT		101	/* multiple "Not" found */
  #define DAV_ERR_IF_UNK_CHAR		102	/* unknown char in header */
  #define DAV_ERR_IF_ABSENT		103	/* no locktokens given */
  #define DAV_ERR_IF_TAGGED		104	/* in parsing tagged-list */
  #define DAV_ERR_IF_UNCLOSED_PAREN	105	/* in no-tagged-list */
  
  /* Prop DB errors */
  #define DAV_ERR_PROP_BAD_MAJOR		200	/* major version was wrong */
  #define DAV_ERR_PROP_READONLY		201	/* prop is read-only */
  #define DAV_ERR_PROP_NO_DATABASE	202	/* writeable db not avail */
  #define DAV_ERR_PROP_NOT_FOUND		203	/* prop not found */
  #define DAV_ERR_PROP_BAD_LOCKDB		204	/* could not open lockdb */
  #define DAV_ERR_PROP_OPENING		205	/* problem opening propdb */
  #define DAV_ERR_PROP_EXEC		206	/* problem exec'ing patch */
  
  /* Predefined DB errors */
  /* ### any to define?? */
  
  /* Predefined locking system errors */
  #define DAV_ERR_LOCK_OPENDB		400	/* could not open lockdb */
  #define DAV_ERR_LOCK_NO_DB		401	/* no database defined */
  #define DAV_ERR_LOCK_CORRUPT_DB		402	/* DB is corrupt */
  #define DAV_ERR_LOCK_UNK_STATE_TOKEN	403	/* unknown State-token */
  #define DAV_ERR_LOCK_PARSE_TOKEN	404	/* bad opaquelocktoken */
  #define DAV_ERR_LOCK_SAVE_LOCK		405	/* err saving locks */
  
  /*
  ** Some comments on Error ID values:
  **
  ** The numbers do not necessarily need to be unique. Uniqueness simply means
  ** that two errors that have not been predefined above can be distinguished
  ** from each other. At the moment, mod_dav does not use this distinguishing
  ** feature, but it could be used in the future to collapse <response> elements
  ** into groups based on the error ID (and associated responsedescription).
  **
  ** If a compute_desc is provided, then the error ID should be unique within
  ** the context of the compute_desc function (so the function can figure out
  ** what to filled into the desc).
  **
  ** Basically, subsystems can ignore defining new error ID values if they want
  ** to. The subsystems *do* need to return the predefined errors when
  ** appropriate, so that mod_dav can figure out what to do. Subsystems can
  ** simply leave the error ID field unfilled (zero) if there isn't an error
  ** that must be placed there.
  */
  
  
  /* --------------------------------------------------------------------
  **
  ** HOOK STRUCTURES
  **
  ** These are here for forward-declaration purposes. For more info, see
  ** the section title "HOOK HANDLING" for more information, plus each
  ** structure definition.
  */
  
  /* forward-declare this structure */
  typedef struct dav_hooks_db dav_hooks_db;
  typedef struct dav_hooks_locks dav_hooks_locks;
  typedef struct dav_hooks_vsn dav_hooks_vsn;
  typedef struct dav_hooks_repository dav_hooks_repository;
  typedef struct dav_hooks_liveprop dav_hooks_liveprop;
  
  
  /* --------------------------------------------------------------------
  **
  ** RESOURCE HANDLING
  */
  
  /*
  ** Resource Types:
  ** The base protocol defines only file and collection resources.
  ** The versioning protocol defines several additional resource types
  ** to represent artifacts of a version control system.
  */
  typedef enum {
      DAV_RESOURCE_TYPE_REGULAR,      /* file or collection, working resource
  				       or revision */
      DAV_RESOURCE_TYPE_REVISION,     /* explicit revision-id */
      DAV_RESOURCE_TYPE_HISTORY,      /* explicit history-id */
      DAV_RESOURCE_TYPE_WORKSPACE,    /* workspace */
      DAV_RESOURCE_TYPE_ACTIVITY,     /* activity */
      DAV_RESOURCE_TYPE_CONFIGURATION /* configuration */
  } dav_resource_type;
  
  /*
  ** Opaque, repository-specific information for a resource.
  */
  typedef struct dav_resource_private dav_resource_private;
  
  /* Resource descriptor, generated by a repository provider.
   * Note: the lock-null state is not explicitly represented here,
   * since it may be expensive to compute. Use dav_get_resource_state()
   * to determine whether a non-existent resource is a lock-null resource.
   */
  typedef struct dav_resource {
      dav_resource_type type;
  
      int exists;		/* 0 => null resource */
      int collection;	/* 0 => file (if type == DAV_RESOURCE_TYPE_REGULAR) */
      int versioned;	/* 0 => unversioned */
      int working;	/* 0 => revision (if versioned) */
      int baselined;	/* 0 => not baselined */
  
      const char *uri;	/* the URI for this resource */
  
      dav_resource_private *info;
  
      const dav_hooks_repository *hooks;	/* hooks used for this resource */
  
  } dav_resource;
  
  /*
  ** Lock token type. Lock providers define the details of a lock token.
  ** However, all providers are expected to at least be able to parse
  ** the "opaquelocktoken" scheme, which is represented by a uuid_t.
  */
  typedef struct dav_locktoken dav_locktoken;
  
  
  /* --------------------------------------------------------------------
  **
  ** BUFFER HANDLING
  **
  ** These buffers are used as a lightweight buffer reuse mechanism. Apache
  ** provides sub-pool creation and destruction to much the same effect, but
  ** the sub-pools are a bit more general and heavyweight than these buffers.
  */
  
  /* buffer for reuse; can grow to accomodate needed size */
  typedef struct
  {
      size_t alloc_len;	/* how much has been allocated */
      size_t cur_len;	/* how much is currently being used */
      char *buf;		/* buffer contents */
  } dav_buffer;
  #define DAV_BUFFER_MINSIZE	256	/* minimum size for buffer */
  #define DAV_BUFFER_PAD		64	/* amount of pad when growing */
  
  /* set the cur_len to the given size and ensure space is available */
  void dav_set_bufsize(pool *p, dav_buffer *pbuf, size_t size);
  
  /* initialize a buffer and copy the specified (null-term'd) string into it */
  void dav_buffer_init(pool *p, dav_buffer *pbuf, const char *str);
  
  /* check that the buffer can accomodate <extra_needed> more bytes */
  void dav_check_bufsize(pool *p, dav_buffer *pbuf, size_t extra_needed);
  
  /* append a string to the end of the buffer, adjust length */
  void dav_buffer_append(pool *p, dav_buffer *pbuf, const char *str);
  
  /* place a string on the end of the buffer, do NOT adjust length */
  void dav_buffer_place(pool *p, dav_buffer *pbuf, const char *str);
  
  /* place some memory on the end of a buffer; do NOT adjust length */
  void dav_buffer_place_mem(pool *p, dav_buffer *pbuf, const void *mem,
                            size_t amt, size_t pad);
  
  
  /* --------------------------------------------------------------------
  **
  ** HANDY UTILITIES
  */
  
  /* simple strutures to keep a linked list of pieces of text */
  typedef struct dav_text
  {
      const char *text;
      struct dav_text *next;
  } dav_text;
  
  typedef struct
  {
      dav_text *first;
      dav_text *last;
  } dav_text_header;
  
  /* contains results from one of the getprop functions */
  typedef struct
  {
      dav_text * propstats;	/* <propstat> element text */
      dav_text * xmlns;		/* namespace decls for <response> elem */
  } dav_get_props_result;
  
  /* holds the contents of a <response> element */
  typedef struct dav_response
  {
      const char *href;		/* always */
      const char *desc;		/* optional description at <response> level */
  
      /* use status if propresult.propstats is NULL. */
      dav_get_props_result propresult;
  
      int status;
  
      struct dav_response *next;
  } dav_response;
  
  typedef struct
  {
      request_rec *rnew;		/* new subrequest */
      dav_error err;		/* potential error response */
  } dav_lookup_result;
  
  
  void dav_text_append(pool *p, dav_text_header *hdr, const char *text);
  
  dav_lookup_result dav_lookup_uri(const char *uri, request_rec *r);
  
  /* format a time string (buf must be at least DAV_TIMEBUF_SIZE chars) */
  #define DAV_STYLE_ISO8601	1
  #define DAV_STYLE_RFC822	2
  #define DAV_TIMEBUF_SIZE	30
  
  int dav_get_depth(request_rec *r, int def_depth);
  
  
  /* --------------------------------------------------------------------
  **
  ** DYNAMIC EXTENSIONS
  */
  
  /* ### docco goes here... */
  
  
  /*
  ** This structure is used to define the runtime, per-directory/location
  ** operating context for a single provider.
  */
  typedef struct
  {
      int id;		/* provider ID */
  
      void *m_context;	/* module-level context (i.e. managed globals) */
  
      void *d_context;	/* per-directory context */
      table *d_params;	/* per-directory DAV config parameters */
  
      int *ns_map;	/* for LIVEPROP, map provider URI to global URI */
  
  } dav_dyn_context;
  
  /*
  ** This structure is used to specify a set of hooks and its associated
  ** context, on a per-directory/location basis.
  **
  ** Note: the context is assembled from various sources. dav_dyn_hooks
  ** structures will typically have the same pointer values within the
  ** context (e.g. ctx.m_context is shared across all providers in a module).
  */
  typedef struct dav_dyn_hooks
  {
      dav_dyn_context ctx;	/* context for this set of hooks */
      const void *hooks;		/* the type-specific hooks */
  
      struct dav_dyn_hooks *next;	/* next set of hooks, if applicable */
  
  } dav_dyn_hooks;
  
  /*
  ** These enumerated values define the different types of functionality that
  ** a provider can implement.
  */
  enum
  {
      DAV_DYN_TYPE_SENTINEL,
  
      DAV_DYN_TYPE_PROPDB,	/* property database (1 per dir) */
      DAV_DYN_TYPE_LOCKS,		/* lock handling (1 per dir) */
      DAV_DYN_TYPE_QUERY_GRAMMAR,	/* DASL search grammar (N per dir) */
      DAV_DYN_TYPE_ACL,		/* ACL handling (1 per dir) */
      DAV_DYN_TYPE_VSN,		/* versioning (1 per dir) */
      DAV_DYN_TYPE_REPOSITORY,	/* resource repository (1 per dir) */
      DAV_DYN_TYPE_LIVEPROP,	/* live property handler (N per dir) */
  
      DAV_DYN_TYPE_MAX
  };
  
  /*
  ** This structure defines a provider for a particular type of functionality.
  **
  ** The ID is private to a provider and can be used to differentiate between
  ** different subclasses of functionality which are implemented using the
  ** same set of hooks. For example, a hook function could perform two entirely
  ** different operations based on the ID which is passed.
  **
  ** is_active() is used by the system to determine whether a particular
  ** provider is "active" for the given context. It is possible that a provider
  ** is configured for a directory, but has not been enabled -- the is_active()
  ** function is used to determine that information.
  **
  ** ### is_active is not used right now
  **
  ** Note: dav_dyn_provider structures are always treated as "const" by mod_dav.
  */
  typedef struct dav_dyn_provider
  {
      int id;			/* provider ID */
  
      int type;			/* provider's functionality type */
      const void *hooks;		/* pointer to type-specific hooks */
  
      int (*is_active)(dav_dyn_context *ctx, int id);
  
  } dav_dyn_provider;
  
  #define DAV_DYN_END_MARKER	{ 0, DAV_DYN_TYPE_SENTINEL, NULL, NULL }
  
  /*
  ** This structure defines a module (a set of providers).
  **
  ** The friendly name should be a single word. It is used with the "DAV"
  ** directive to specify the module to use for a particular directory/location.
  **
  ** The module_open/close functions are used to initialize per-module "global"
  ** data. The functions are expected to update ctx->m_context.
  **
  ** ### module_open/close are not used at the moment
  ** ### dir_* are not well-defined, nor are they used
  **
  ** Note: The DAV_DYN_VERSION specifies the version of the dav_dyn_module
  **       structure itself. It will be updated if changes in the structure
  **       are made. There are no provisions for forward or backward
  **       compatible changes.
  **
  ** Note: dav_dyn_module structures are always treated as "const" by mod_dav.
  */
  typedef struct
  {
      int magic;
  #define DAV_DYN_MAGIC		0x44415621	/* "DAV!" */
  
      int version;
  #define DAV_DYN_VERSION		1		/* must match exactly */
  
      const char *name;				/* friendly name */
  
      int (*module_open)(dav_dyn_context *ctx);
      int (*module_close)(dav_dyn_context *ctx);
  
      int (*dir_open)(dav_dyn_context *ctx);
      int (*dir_param)(dav_dyn_context *ctx, const char *param_name,
  		     const char *param_value);
      int (*dir_merge)(dav_dyn_context *base, dav_dyn_context *overrides,
  		     dav_dyn_context *result);
      int (*dir_close)(dav_dyn_context *ctx);
  
      const dav_dyn_provider *providers;		/* providers in this module */
  
  } dav_dyn_module;
  
  int dav_load_module(const char *name, const char *module_sym,
  		    const char *filename);
  const dav_dyn_module *dav_find_module(const char *name);
  
  /*
  ** Various management functions.
  **
  ** NOTE: the pool should be the "configuration pool"
  */
  void dav_process_builtin_modules(pool *p);
  void dav_process_module(pool *p, const dav_dyn_module *mod);
  
  int * dav_collect_liveprop_uris(pool *p, const dav_hooks_liveprop *hooks);
  extern array_header *dav_liveprop_uris;
  
  void *dav_prepare_scan(pool *p, const dav_dyn_module *mod);
  int dav_scan_providers(void *ctx,
  		       const dav_dyn_provider **provider,
  		       dav_dyn_hooks *output);
  
  /* handy macros to assist with dav_dyn_hooks.hooks usage */
  #define DAV_AS_HOOKS_PROPDB(ph)		((const dav_hooks_db *)((ph)->hooks))
  #define DAV_AS_HOOKS_LOCKS(ph)		((const dav_hooks_locks *)((ph)->hooks))
  #define DAV_AS_HOOKS_QUERY_GRAMMAR(ph)	((void *)((ph)->hooks))
  #define DAV_AS_HOOKS_ACL(ph)		((void *)((ph)->hooks))
  #define DAV_AS_HOOKS_VSN(ph)		((const dav_hooks_vsn *)((ph)->hooks))
  #define DAV_AS_HOOKS_REPOSITORY(ph)	((const dav_hooks_repository *)((ph)->hooks))
  #define DAV_AS_HOOKS_LIVEPROP(ph)	((const dav_hooks_liveprop *)((ph)->hooks))
  
  /* get provider hooks, given a request record */
  const dav_dyn_hooks *dav_get_provider_hooks(request_rec *r, int provider_type);
  
  #define DAV_GET_HOOKS_PROPDB(r)         DAV_AS_HOOKS_PROPDB(dav_get_provider_hooks(r, DAV_DYN_TYPE_PROPDB))
  #define DAV_GET_HOOKS_LOCKS(r)          DAV_AS_HOOKS_LOCKS(dav_get_provider_hooks(r, DAV_DYN_TYPE_LOCKS))
  #define DAV_GET_HOOKS_QUERY_GRAMMAR(r)  DAV_AS_HOOKS_QUERY_GRAMMAR(dav_get_provider_hooks(r, DAV_DYN_TYPE_QUERY_GRAMMAR))
  #define DAV_GET_HOOKS_ACL(r)            DAV_AS_HOOKS_ACL(dav_get_provider_hooks(r, DAV_DYN_TYPE_ACL))
  #define DAV_GET_HOOKS_VSN(r)            DAV_AS_HOOKS_VSN(dav_get_provider_hooks(r, DAV_DYN_TYPE_VSN))
  #define DAV_GET_HOOKS_REPOSITORY(r)     DAV_AS_HOOKS_REPOSITORY(dav_get_provider_hooks(r, DAV_DYN_TYPE_REPOSITORY))
  #define DAV_GET_HOOKS_LIVEPROP(r)       DAV_AS_HOOKS_LIVEPROP(dav_get_provider_hooks(r, DAV_DYN_TYPE_LIVEPROP))
  
  
  /* --------------------------------------------------------------------
  **
  ** IF HEADER PROCESSING
  **
  ** Here is the definition of the If: header from RFC 2518, S9.4:
  **
  **    If = "If" ":" (1*No-tag-list | 1*Tagged-list)
  **    No-tag-list = List
  **    Tagged-list = Resource 1*List
  **    Resource = Coded-URL
  **    List = "(" 1*(["Not"](State-token | "[" entity-tag "]")) ")"
  **    State-token = Coded-URL
  **    Coded-URL = "<" absoluteURI ">"        ; absoluteURI from RFC 2616
  **
  ** List corresponds to dav_if_state_list. No-tag-list corresponds to
  ** dav_if_header with uri==NULL. Tagged-list corresponds to a sequence of
  ** dav_if_header structures with (duplicate) uri==Resource -- one
  ** dav_if_header per state_list. A second Tagged-list will start a new
  ** sequence of dav_if_header structures with the new URI.
  **
  ** A summary of the semantics, mapped into our structures:
  **    - Chained dav_if_headers: OR
  **    - Chained dav_if_state_lists: AND
  **    - NULL uri matches all resources
  */
  
  typedef enum
  {
      dav_if_etag,
      dav_if_opaquelock
  } dav_if_state_type;
  
  typedef struct dav_if_state_list
  {
      dav_if_state_type type;
  
      int condition;
  #define DAV_IF_COND_NORMAL	0
  #define DAV_IF_COND_NOT		1	/* "Not" was applied */
  
      const char *etag;	/* etag */
      dav_locktoken *locktoken;   /* locktoken */
  
      struct dav_if_state_list *next;
  } dav_if_state_list;
  
  typedef struct dav_if_header
  {
      const char *uri;
      size_t uri_len;
      struct dav_if_state_list *state;
      struct dav_if_header *next;
  
      int dummy_header;	/* used internally by the lock/etag validation */
  } dav_if_header;
  
  typedef struct dav_locktoken_list 
  {
      dav_locktoken *locktoken;
      struct dav_locktoken_list *next;
  } dav_locktoken_list;
  
  dav_error * dav_get_locktoken_list(request_rec *r, dav_locktoken_list **ltl);
  
  
  /* --------------------------------------------------------------------
  **
  ** XML PARSING
  */
  
  /*
  ** Qualified namespace values
  **
  ** DAV_NS_DAV_ID
  **    We always insert the "DAV:" namespace URI at the head of the
  **    namespace array. This means that it will always be at ID==0,
  **    making it much easier to test for.
  **
  ** DAV_NS_NONE
  **    This special ID is used for two situations:
  **
  **    1) The namespace prefix begins with "xml" (and we do not know
  **       what it means). Namespace prefixes with "xml" (any case) as
  **       their first three characters are reserved by the XML Namespaces
  **       specification for future use. mod_dav will pass these through
  **       unchanged. When this identifier is used, the prefix is LEFT in
  **       the element/attribute name. Downstream processing should not
  **       prepend another prefix.
  **
  **    2) The element/attribute does not have a namespace.
  **
  **       a) No prefix was used, and a default namespace has not been
  **          defined.
  **       b) No prefix was used, and the default namespace was specified
  **          to mean "no namespace". This is done with a namespace
  **          declaration of:  xmlns=""
  **          (this declaration is typically used to override a previous
  **          specification for the default namespace)
  **
  **       In these cases, we need to record that the elem/attr has no
  **       namespace so that we will not attempt to prepend a prefix.
  **       All namespaces that are used will have a prefix assigned to
  **       them -- mod_dav will never set or use the default namespace
  **       when generating XML. This means that "no prefix" will always
  **       mean "no namespace".
  **
  **    In both cases, the XML generation will avoid prepending a prefix.
  **    For the first case, this means the original prefix/name will be
  **    inserted into the output stream. For the latter case, it means
  **    the name will have no prefix, and since we never define a default
  **    namespace, this means it will have no namespace.
  **
  ** Note: currently, mod_dav understands the "xmlns" prefix and the
  **     "xml:lang" attribute. These are handled specially (they aren't
  **     left within the XML tree), so the DAV_NS_NONE value won't ever
  **     really apply to these values.
  */
  #define DAV_NS_DAV_ID		0	/* namespace ID for "DAV:" */
  #define DAV_NS_NONE		-10	/* no namespace for this elem/attr */
  
  #define DAV_NS_ERROR_BASE	-100	/* used only during processing */
  #define DAV_NS_IS_ERROR(e)	((e) <= DAV_NS_ERROR_BASE)
  
  
  /*
  ** dav_xml_doc: holds a parsed XML document
  ** dav_xml_elem: holds a parsed XML element
  ** dav_xml_attr: holds a parsed XML attribute
  **
  ** dav_xml_ns_scope: internal struct used during processing to scope
  **                   namespace declarations
  */
  
  typedef struct dav_xml_attr
  {
      const char *name;			/* attribute name */
      int ns;				/* index into namespace array */
  
      const char *value;			/* attribute value */
  
      struct dav_xml_attr *next;		/* next attribute */
  } dav_xml_attr;
  
  typedef struct dav_xml_elem
  {
      const char *name;			/* element name */
      int ns;				/* index into namespace array */
      const char *lang;			/* xml:lang for attrs/contents */
  
      dav_text_header first_cdata;	/* cdata right after start tag */
      dav_text_header following_cdata;	/* cdata after MY end tag */
  
      struct dav_xml_elem *parent;	/* parent element */
      struct dav_xml_elem *next;		/* next (sibling) element */
      struct dav_xml_elem *first_child;	/* first child element */
      struct dav_xml_attr *attr;		/* first attribute */
  
      /* used only during parsing */
      struct dav_xml_elem *last_child;	/* last child element */
      struct dav_xml_ns_scope *ns_scope;	/* namespaces scoped by this elem */
  
      /* used during request processing */
      int propid;				/* live property ID */
      const dav_hooks_liveprop *provider;	/* the provider defining this prop */
      const int *ns_map;			/* ns map for this provider */
  
  } dav_xml_elem;
  
  #define DAV_ELEM_IS_EMPTY(e)	((e)->first_child == NULL && \
  				 (e)->first_cdata.first == NULL)
  
  typedef struct dav_xml_doc
  {
      dav_xml_elem *root;		/* root element */
      array_header *namespaces;	/* array of namespaces used */
  
  } dav_xml_doc;
  
  
  int dav_parse_input(request_rec *r, dav_xml_doc **pdoc);
  
  int dav_validate_root(const dav_xml_doc *doc, const char *tagname);
  
  dav_xml_elem *dav_find_child(
      const dav_xml_elem *elem,
      const char *tagname);
  
  void dav_xml2text(
      pool *p,
      const dav_xml_elem *elem,
      int style,
      array_header *namespaces,
      int *ns_map,
      const char **pbuf,
      size_t *psize
      );
  #define DAV_X2T_FULL		0	/* start tag, contents, end tag */
  #define DAV_X2T_INNER		1	/* contents only */
  #define DAV_X2T_LANG_INNER	2	/* xml:lang + inner contents */
  #define DAV_X2T_FULL_NS_LANG	3	/* FULL + ns defns + xml:lang */
  
  const char *dav_empty_elem(pool *p, const dav_xml_elem *elem);
  void dav_quote_xml_elem(pool *p, dav_xml_elem *elem);
  const char * dav_quote_string(pool *p, const char *s, int quotes);
  
  
  /* --------------------------------------------------------------------
  **
  ** LIVE PROPERTY HANDLING
  */
  
  typedef enum {
      DAV_PROP_INSERT_NOTME,	/* prop not defined by this provider */
      DAV_PROP_INSERT_NOTDEF,	/* property is defined by this provider,
  				   but nothing was inserted because the
  				   (live) property is not defined for this
  				   resource (it may be present as a dead
  				   property). */
      DAV_PROP_INSERT_NAME,	/* a property name (empty elem) was
  				   inserted into the text block */
      DAV_PROP_INSERT_VALUE	/* a property name/value pair was inserted
  				   into the text block */
  } dav_prop_insert;
  
  typedef enum {
      DAV_PROP_RW_NOTME,		/* not my property */
      DAV_PROP_RW_NO,		/* property is NOT writeable */
      DAV_PROP_RW_YES		/* property IS writeable */
  } dav_prop_rw;
  
  /* opaque type for PROPPATCH rollback information */
  typedef struct dav_liveprop_rollback dav_liveprop_rollback;
  
  struct dav_hooks_liveprop
  {
      /*
      ** This URI is returned in the DAV: header to let clients know what
      ** sets of live properties are supported by the installation. mod_dav
      ** will place open/close angle brackets around this value (much like
      ** a Coded-URL); quotes and brackets should not be in the value.
      **
      ** Example:    http://apache.org/dav/props/
      **
      ** (of course, use your own domain to ensure a unique value)
      */
      const char * propset_uri;
  
      /*
      ** Find a property, returning a non-zero, unique, opaque identifier.
      **
      ** NOTE: Providers must ensure this identifier is universally unique.
      **       See the registration table below.
      ** ### it would be nice to avoid this uniqueness constraint. however,
      ** ### that would mean our xml_elem annotation concept would need to
      ** ### change (w.r.t. the fact that it acts as a cache for find_prop).
      **
      ** Returns 0 if the property is not defined by this provider.
      */
      int (*find_prop)(const char *ns_uri, const char *name);
  
      /*
      ** Insert a property name/value into a text block. The property to
      ** insert is identified by the propid value. Providers should return
      ** DAV_PROP_INSERT_NOTME if they do not define the specified propid.
      ** If insvalue is true, then the property's value should be inserted;
      ** otherwise, an empty element (ie. just the prop's name) should be
      ** inserted.
      **
      ** Returns one of DAV_PROP_INSERT_* based on what happened.
      **
      ** ### we may need more context... ie. the lock database
      */
      dav_prop_insert (*insert_prop)(const dav_resource *resource,
  				   int propid, int insvalue,
  				   const int *ns_map, dav_text_header *phdr);
  
      /*
      ** Insert all known/defined property names (and values). This is
      ** similar to insert_prop, but *all* properties will be inserted
      ** rather than specific, individual properties.
      */
      void (*insert_all)(const dav_resource *resource, int insvalue,
  		       const int *ns_map, dav_text_header *phdr);
  
      /*
      ** Determine whether a given property is writeable.
      **
      ** ### we may want a different semantic. i.e. maybe it should be
      ** ### "can we write <value> into this property?"
      **
      ** Returns appropriate read/write status.
      */
      dav_prop_rw (*is_writeable)(const dav_resource *resource, int propid);
  
      /*
      ** This member defines the set of namespace URIs that the provider
      ** uses for its properties. When insert_all is called, it will be
      ** passed a list of integers that map from indices into this list
      ** to namespace IDs for output generation.
      **
      ** The last entry in this list should be a NULL value (sentinel).
      */
      const char * const * namespace_uris;
  
      /*
      ** ### this is not the final design. we want an open-ended way for
      ** ### liveprop providers to attach *new* properties. To this end,
      ** ### we'll have a "give me a list of the props you define", a way
      ** ### to check for a prop's existence, a way to validate a set/remove
      ** ### of a prop, and a way to execute/commit/rollback that change.
      */
  
      /*
      ** Validate that the live property can be assigned a value, and that
      ** the provided value is valid.
      **
      ** elem will point to the XML element that names the property. For
      ** example:
      **     <lp1:executable>T</lp1:executable>
      **
      ** The provider can access the cdata fields and the child elements
      ** to extract the relevant pieces.
      **
      ** operation is one of DAV_PROP_OP_SET or _DELETE.
      **
      ** The provider may return a value in *context which will be passed
      ** to each of the exec/commit/rollback functions. For example, this
      ** may contain an internal value which has been processed from the
      ** input element.
      **
      ** The provider must set defer_to_dead to true (non-zero) or false.
      ** If true, then the set/remove is deferred to the dead property
      ** database. Note: it will be set to zero on entry.
      */
      dav_error * (*patch_validate)(const dav_resource *resource,
  				  const dav_xml_elem *elem,
  				  int operation,
  				  void **context,
  				  int *defer_to_dead);
  
      /* ### doc... */
      dav_error * (*patch_exec)(dav_resource *resource,
  			      const dav_xml_elem *elem,
  			      int operation,
  			      void *context,
  			      dav_liveprop_rollback **rollback_ctx);
  
      /* ### doc... */
      void (*patch_commit)(dav_resource *resource,
  			 int operation,
  			 void *context,
  			 dav_liveprop_rollback *rollback_ctx);
  
      /* ### doc... */
      dav_error * (*patch_rollback)(dav_resource *resource,
  				  int operation,
  				  void *context,
  				  dav_liveprop_rollback *rollback_ctx);
  };
  
  /*
  ** Property Identifier Registration
  **
  ** At the moment, mod_dav requires live property providers to ensure that
  ** each property returned has a unique value. For now, this is done through
  ** central registration (there are no known providers other than the default,
  ** so this remains manageable).
  **
  ** WARNING: the TEST ranges should never be "shipped".
  */
  #define DAV_PROPID_CORE		10000	/* ..10099. defined by mod_dav */
  #define DAV_PROPID_FS		10100	/* ..10299.
  					   mod_dav filesystem provider. */
  #define DAV_PROPID_TEST1	10300	/* ..10399 */
  #define DAV_PROPID_TEST2	10400	/* ..10499 */
  #define DAV_PROPID_TEST3	10500	/* ..10599 */
  /* Next: 10600 */
  
  
  /* --------------------------------------------------------------------
  **
  ** DATABASE FUNCTIONS
  */
  
  typedef struct dav_db dav_db;
  typedef struct
  {
      char *dptr;
      size_t dsize;
  } dav_datum;
  
  /* hook functions to enable pluggable databases */
  struct dav_hooks_db
  {
      dav_error * (*open)(pool *p, const dav_resource *resource, int ro,
  			dav_db **pdb);
      void (*close)(dav_db *db);
  
      /*
      ** Fetch the value from the database. If the value does not exist,
      ** then *pvalue should be zeroed.
      **
      ** Note: it is NOT an error for the key/value pair to not exist.
      */
      dav_error * (*fetch)(dav_db *db, dav_datum key, dav_datum *pvalue);
  
      dav_error * (*store)(dav_db *db, dav_datum key, dav_datum value);
      dav_error * (*remove)(dav_db *db, dav_datum key);
  
      /* returns 1 if the record specified by "key" exists; 0 otherwise */
      int (*exists)(dav_db *db, dav_datum key);
  
      dav_error * (*firstkey)(dav_db *db, dav_datum *pkey);
      dav_error * (*nextkey)(dav_db *db, dav_datum *pkey);
  
      void (*freedatum)(dav_db *db, dav_datum data);
  };
  
  
  /* --------------------------------------------------------------------
  **
  ** LOCK FUNCTIONS
  */
  
  /* Used to represent a Timeout header of "Infinity" */
  #define DAV_TIMEOUT_INFINITE 0
  
  time_t dav_get_timeout(request_rec *r);
  
  /*
  ** Opaque, repository-specific information for a lock database.
  */
  typedef struct dav_lockdb_private dav_lockdb_private;
  
  /*
  ** Opaque, repository-specific information for a lock record.
  */
  typedef struct dav_lock_private dav_lock_private;
  
  /*
  ** Lock database type. Lock providers are urged to implement a "lazy" open, so
  ** doing an "open" is cheap until something is actually needed from the DB.
  */
  typedef struct
  {
      const dav_hooks_locks *hooks;	/* the hooks used for this lockdb */
      int ro;				/* was it opened readonly? */
  
      dav_lockdb_private *info;
  
  } dav_lockdb;
  
  typedef enum {
      DAV_LOCKSCOPE_UNKNOWN,
      DAV_LOCKSCOPE_EXCLUSIVE,
      DAV_LOCKSCOPE_SHARED
  } dav_lock_scope;
  
  typedef enum {
      DAV_LOCKTYPE_UNKNOWN,
      DAV_LOCKTYPE_WRITE
  } dav_lock_type;
  
  typedef enum {
      DAV_LOCKREC_DIRECT,			/* lock asserted on this resource */
      DAV_LOCKREC_INDIRECT,		/* lock inherited from a parent */
      DAV_LOCKREC_INDIRECT_PARTIAL	/* most info is not filled in */
  } dav_lock_rectype;
  
  /*
  ** dav_lock: hold information about a lock on a resource.
  **
  ** This structure is used for both direct and indirect locks. A direct lock
  ** is a lock applied to a specific resource by the client. An indirect lock
  ** is one that is inherited from a parent resource by virtue of a non-zero
  ** Depth: header when the lock was applied.
  **
  ** mod_dav records both types of locks in the lock database, managing their
  ** addition/removal as resources are moved about the namespace.
  **
  ** Note that the lockdb is free to marshal this structure in any form that
  ** it likes.
  **
  ** For a "partial" lock, the <rectype> and <locktoken> fields must be filled
  ** in. All other (user) fields should be zeroed. The lock provider will
  ** usually fill in the <info> field, and the <next> field may be used to
  ** construct a list of partial locks.
  **
  ** The lock provider MUST use the info field to store a value such that a
  ** dav_lock structure can locate itself in the underlying lock database.
  ** This requirement is needed for refreshing: when an indirect dav_lock is
  ** refreshed, its reference to the direct lock does not specify the direct's
  ** resource, so the only way to locate the (refreshed, direct) lock in the
  ** database is to use the info field.
  **
  ** Note that <is_locknull> only refers to the resource where this lock was
  ** found.
  ** ### hrm. that says the abstraction is wrong. is_locknull may disappear.
  */
  typedef struct dav_lock
  {
      dav_lock_rectype rectype;	/* type of lock record */
      int is_locknull;		/* lock establishes a locknull resource */
  
      /* ### put the resource in here? */
  
      dav_lock_scope scope;	/* scope of the lock */
      dav_lock_type type;		/* type of lock */
      int depth;			/* depth of the lock */
      time_t timeout;		/* when the lock will timeout */
  
      const dav_locktoken *locktoken;	/* the token that was issued */
  
      const char *owner;		/* (XML) owner of the lock */
      const char *auth_user;	/* auth'd username owning lock */
  
      dav_lock_private *info;	/* private to the lockdb */
  
      struct dav_lock *next;	/* for managing a list of locks */
  } dav_lock;
  
  /* Property-related public lock functions */
  const char *dav_lock_get_activelock(request_rec *r, dav_lock *locks,
  				    dav_buffer *pbuf);
  
  /* LockDB-related public lock functions */
  const char *dav_get_lockdb_path(const request_rec *r);
  dav_error * dav_lock_parse_lockinfo(request_rec *r,
  				    const dav_resource *resrouce,
  				    dav_lockdb *lockdb,
  				    const dav_xml_doc *doc,
  				    dav_lock **lock_request);
  int dav_unlock(request_rec *r, const dav_resource *resource,
  	       const dav_locktoken *locktoken);
  dav_error * dav_add_lock(request_rec *r, const dav_resource *resource,
  			 dav_lockdb *lockdb, dav_lock *request,
  			 dav_response **response);
  dav_error * dav_notify_created(request_rec *r,
  			       dav_lockdb *lockdb,
  			       const dav_resource *resource,
  			       int resource_state,
  			       int depth);
  
  dav_error * dav_lock_query(dav_lockdb *lockdb, const dav_resource *resource,
  			   dav_lock **locks);
  
  dav_error * dav_validate_request(request_rec *r, dav_resource *resource,
  				 int depth, dav_locktoken *locktoken,
  				 dav_response **response, int flags,
                                   dav_lockdb *lockdb);
  /*
  ** flags:
  **    0x0F -- reserved for <dav_lock_scope> values
  **
  **    other flags, detailed below
  */
  #define DAV_VALIDATE_RESOURCE   0x0010  /* validate just the resource */
  #define DAV_VALIDATE_PARENT     0x0020  /* validate resource AND its parent */
  #define DAV_VALIDATE_ADD_LD     0x0040  /* add DAV:lockdiscovery into
                                             the 424 DAV:response */
  #define DAV_VALIDATE_USE_424    0x0080  /* return 424 status, not 207 */
  #define DAV_VALIDATE_IS_PARENT  0x0100  /* for internal use */
  
  /* Lock-null related public lock functions */
  int dav_get_resource_state(request_rec *r, const dav_resource *resource);
  
  /* Lock provider hooks. Locking is optional, so there may be no
   * lock provider for a given repository.
   */
  struct dav_hooks_locks
  {
      /* Return the supportedlock property for this provider */
      /* ### maybe this should take a resource argument? */
      const char * (*get_supportedlock)(void);
  
      /* Parse a lock token URI, returning a lock token object allocated
       * in the given pool.
       */
      dav_error * (*parse_locktoken)(
          pool *p,
          const char *char_token,
          dav_locktoken **locktoken_p
      );
  
      /* Format a lock token object into a URI string, allocated in
       * the given pool.
       *
       * Always returns non-NULL.
       */
      const char * (*format_locktoken)(
          pool *p,
          const dav_locktoken *locktoken
      );
  
      /* Compare two lock tokens.
       *
       * Result < 0  => lt1 < lt2
       * Result == 0 => lt1 == lt2
       * Result > 0  => lt1 > lt2
       */
      int (*compare_locktoken)(
          const dav_locktoken *lt1,
          const dav_locktoken *lt2
      );
  
      /* Open the provider's lock database.
       *
       * The provider may or may not use a "real" database for locks
       * (a lock could be an attribute on a resource, for example).
       *
       * The provider may choose to use the value of the DAVLockDB directive
       * (as returned by dav_get_lockdb_path()) to decide where to place
       * any storage it may need.
       *
       * The request storage pool should be associated with the lockdb,
       * so it can be used in subsequent operations.
       *
       * If ro != 0, only readonly operations will be performed.
       * If force == 0, the open can be "lazy"; no subsequent locking operations
       * may occur.
       * If force != 0, locking operations will definitely occur.
       */
      dav_error * (*open_lockdb)(
          request_rec *r,
          int ro,
          int force,
          dav_lockdb **lockdb
      );
  
      /* Indicates completion of locking operations */
      void (*close_lockdb)(
          dav_lockdb *lockdb
      );
  
      /* Take a resource out of the lock-null state. */
      dav_error * (*remove_locknull_state)(
          dav_lockdb *lockdb,
          const dav_resource *resource
      );
  
      /*
      ** Create a (direct) lock structure for the given resource. A locktoken
      ** will be created.
      **
      ** The lock provider may store private information into lock->info.
      */
      dav_error * (*create_lock)(dav_lockdb *lockdb,
  			       const dav_resource *resource,
  			       dav_lock **lock);
  
      /*
      ** Get the locks associated with the specified resource.
      **
      ** If resolve_locks is true (non-zero), then any indirect locks are
      ** resolved to their actual, direct lock (i.e. the reference to followed
      ** to the original lock).
      **
      ** The locks, if any, are returned as a linked list in no particular
      ** order. If no locks are present, then *locks will be NULL.
      */
      dav_error * (*get_locks)(dav_lockdb *lockdb,
  			     const dav_resource *resource,
  			     int calltype,
  			     dav_lock **locks);
  
  #define DAV_GETLOCKS_RESOLVED	0	/* resolve indirects to directs */
  #define DAV_GETLOCKS_PARTIAL	1	/* leave indirects partially filled */
  #define DAV_GETLOCKS_COMPLETE	2	/* fill out indirect locks */
  
      /*
      ** Find a particular lock on a resource (specified by its locktoken).
      **
      ** *lock will be set to NULL if the lock is not found.
      **
      ** Note that the provider can optimize the unmarshalling -- only one
      ** lock (or none) must be constructed and returned.
      **
      ** If partial_ok is true (non-zero), then an indirect lock can be
      ** partially filled in. Otherwise, another lookup is done and the
      ** lock structure will be filled out as a DAV_LOCKREC_INDIRECT.
      */
      dav_error * (*find_lock)(dav_lockdb *lockdb,
  			     const dav_resource *resource,
  			     const dav_locktoken *locktoken,
  			     int partial_ok,
  			     dav_lock **lock);
  
      /*
      ** Quick test to see if the resource has *any* locks on it.
      **
      ** This is typically used to determine if a non-existent resource
      ** has a lock and is (therefore) a locknull resource.
      **
      ** WARNING: this function may return TRUE even when timed-out locks
      **          exist (i.e. it may not perform timeout checks).
      */
      dav_error * (*has_locks)(dav_lockdb *lockdb,
  			     const dav_resource *resource,
  			     int *locks_present);
  
      /*
      ** Append the specified lock(s) to the set of locks on this resource.
      **
      ** If "make_indirect" is true (non-zero), then the specified lock(s)
      ** should be converted to an indirect lock (if it is a direct lock)
      ** before appending. Note that the conversion to an indirect lock does
      ** not alter the passed-in lock -- the change is internal the
      ** append_locks function.
      **
      ** Multiple locks are specified using the lock->next links.
      */
      dav_error * (*append_locks)(dav_lockdb *lockdb,
  				const dav_resource *resource,
  				int make_indirect,
  				const dav_lock *lock);
  
      /*
      ** Remove any lock that has the specified locktoken.
      **
      ** If locktoken == NULL, then ALL locks are removed.
      */
      dav_error * (*remove_lock)(dav_lockdb *lockdb,
  			       const dav_resource *resource,
  			       const dav_locktoken *locktoken);
  
      /*
      ** Refresh all locks, found on the specified resource, which has a
      ** locktoken in the provided list.
      **
      ** If the lock is indirect, then the direct lock is referenced and
      ** refreshed.
      **
      ** Each lock that is updated is returned in the <locks> argument.
      ** Note that the locks will be fully resolved.
      */
      dav_error * (*refresh_locks)(dav_lockdb *lockdb,
  				 const dav_resource *resource,
  				 const dav_locktoken_list *ltl,
  				 time_t new_time,
  				 dav_lock **locks);
  
      /*
      ** Look up the resource associated with a particular locktoken.
      **
      ** The search begins at the specified <start_resource> and the lock
      ** specified by <locktoken>.
      **
      ** If the resource/token specifies an indirect lock, then the direct
      ** lock will be looked up, and THAT resource will be returned. In other
      ** words, this function always returns the resource where a particular
      ** lock (token) was asserted.
      **
      ** NOTE: this function pointer is allowed to be NULL, indicating that
      **       the provider does not support this type of functionality. The
      **       caller should then traverse up the repository hierarchy looking
      **       for the resource defining a lock with this locktoken.
      */
      dav_error * (*lookup_resource)(dav_lockdb *lockdb,
  				   const dav_locktoken *locktoken,
  				   const dav_resource *start_resource,
  				   const dav_resource **resource);
  };
  
  /* what types of resources can be discovered by dav_get_resource_state() */
  #define DAV_RESOURCE_LOCK_NULL	10	/* resource lock-null */
  #define DAV_RESOURCE_NULL	11	/* resource null */
  #define DAV_RESOURCE_EXISTS	12	/* resource exists */
  #define DAV_RESOURCE_ERROR	13	/* an error occurred */
  
  
  /* --------------------------------------------------------------------
  **
  ** PROPERTY HANDLING
  */
  
  typedef struct dav_propdb dav_propdb;
  
  
  dav_error *dav_open_propdb(
      request_rec *r,
      dav_lockdb *lockdb,
      dav_resource *resource,
      int ro,
      array_header *ns_xlate,
      dav_propdb **propdb);
  
  void dav_close_propdb(dav_propdb *db);
  
  dav_get_props_result dav_get_props(
      dav_propdb *db,
      dav_xml_doc *doc);
  
  dav_get_props_result dav_get_allprops(
      dav_propdb *db,
      int getvals);
  
  /*
  ** 3-phase property modification.
  **
  **   1) validate props. readable? unlocked? ACLs allow access?
  **   2) execute operation (set/delete)
  **   3) commit or rollback
  **
  ** ### eventually, auth must be available. a ref to the request_rec (which
  ** ### contains the auth info) should be in the shared context struct.
  **
  ** Each function may alter the error values and information contained within
  ** the context record. This should be done as an "increasing" level of
  ** error, rather than overwriting any previous error.
  **
  ** Note that commit() cannot generate errors. It should simply free the
  ** rollback information.
  **
  ** rollback() may generate additional errors because the rollback operation
  ** can sometimes fail(!).
  **
  ** The caller should allocate an array of these, one per operation. It should
  ** be zero-initialized, then the db, operation, and prop fields should be
  ** filled in before calling dav_prop_validate. Note that the set/delete
  ** operations are order-dependent. For a given (logical) context, the same
  ** pointer must be passed to each phase.
  **
  ** error_type is an internal value, but will have the same numeric value
  ** for each possible "desc" value. This allows the caller to group the
  ** descriptions via the error_type variable, rather than through string
  ** comparisons. Note that "status" does not provide enough granularity to
  ** differentiate/group the "desc" values.
  **
  ** Note that the propdb will maintain some (global) context across all
  ** of the property change contexts. This implies that you can have only
  ** one open transaction per propdb.
  */
  typedef struct dav_prop_ctx
  {
      dav_propdb *propdb;
  
      int operation;
  #define DAV_PROP_OP_SET		1	/* set a property value */
  #define DAV_PROP_OP_DELETE	2	/* delete a prop value */
  /* ### add a GET? */
  
      dav_xml_elem *prop;			/* property to affect */
  
      dav_error *err;			/* error (if any) */
  
      /* private items to the propdb */
      int is_liveprop;
      void *liveprop_ctx;
      struct dav_rollback_item *rollback;	/* optional rollback info */
  
      /* private to mod_dav.c */
      request_rec *r;
  
  } dav_prop_ctx;
  
  void dav_prop_validate(dav_prop_ctx *ctx);
  void dav_prop_exec(dav_prop_ctx *ctx);
  void dav_prop_commit(dav_prop_ctx *ctx);
  void dav_prop_rollback(dav_prop_ctx *ctx);
  
  #define DAV_PROP_CTX_HAS_ERR(dpc)	((dpc).err && (dpc).err->status >= 300)
  
  
  /* --------------------------------------------------------------------
  **
  ** WALKER STRUCTURE
  */
  
  /* private, opaque info structure for repository walking context */
  typedef struct dav_walker_private dav_walker_private;
  
  /* directory tree walking context */
  typedef struct dav_walker_ctx
  {
      int walk_type;
  #define DAV_WALKTYPE_AUTH	1	/* limit to authorized files */
  #define DAV_WALKTYPE_ALL	2	/* walk normal files */
  #define DAV_WALKTYPE_HIDDEN	4	/* walk hidden files */
  #define DAV_WALKTYPE_LOCKNULL	8	/* walk locknull resources */
  
      int postfix;		/* call func for dirs after files */
  
      dav_error * (*func)(struct dav_walker_ctx *ctx, int calltype);
  #define DAV_CALLTYPE_MEMBER	1	/* called for a member resource */
  #define DAV_CALLTYPE_COLLECTION	2	/* called for a collection */
  #define DAV_CALLTYPE_LOCKNULL	3	/* called for a locknull resource */
  #define DAV_CALLTYPE_POSTFIX	4	/* postfix call for a collection */
  
      struct pool *pool;
  
      request_rec *r;			/* original request */
      dav_buffer uri;			/* current URI */
      const dav_resource *resource;	/* current resource */
      const dav_resource *res2;		/* optional secondary resource */
  
      const dav_resource *root;		/* RO: root resource of the walk */
  
      dav_lockdb *lockdb;
  
      dav_response *response;		/* OUT: multistatus responses */
  
      /* for PROPFIND operations */
      dav_xml_doc *doc;
      int propfind_type;
  #define DAV_PROPFIND_IS_ALLPROP		1
  #define DAV_PROPFIND_IS_PROPNAME	2
  #define DAV_PROPFIND_IS_PROP		3
  
      dav_text *propstat_404;	/* (cached) propstat giving a 404 error */
  
      /* for COPY and MOVE operations */
      int is_move;
      dav_buffer work_buf;
  
      const dav_if_header *if_header;	/* for validation */
      const dav_locktoken *locktoken;	/* for UNLOCK */
      const dav_lock *lock;		/* for LOCK */
      int skip_root;			/* for dav_inherit_locks() */
  
      int flags;
  
      dav_walker_private *info;           /* for use by repository manager */
  
  } dav_walker_ctx;
  
  void dav_add_response(dav_walker_ctx *ctx, const char *href, int status,
  		      dav_get_props_result *propstats);
  
  
  /* --------------------------------------------------------------------
  **
  ** "STREAM" STRUCTURE
  **
  ** mod_dav uses this abstraction for interacting with the repository
  ** while fetching/storing resources. mod_dav views resources as a stream
  ** of bytes.
  **
  ** Note that the structure is opaque -- it is private to the repository
  ** that created the stream in the repository's "open" function.
  */
  
  typedef struct dav_stream dav_stream;
  
  typedef enum {
      DAV_MODE_READ,		/* open for reading */
      DAV_MODE_READ_SEEKABLE,	/* open for random access reading */
      DAV_MODE_WRITE_TRUNC,	/* truncate and open for writing */
      DAV_MODE_WRITE_SEEKABLE	/* open for writing; random access */
  } dav_stream_mode;
  
  /* --------------------------------------------------------------------
  **
  ** REPOSITORY FUNCTIONS
  */
  
  /* Repository provider hooks */
  struct dav_hooks_repository
  {
      /* Flag for whether repository requires special GET handling.
       * If resources in the repository are not visible in the
       * filesystem location which URLs map to, then special handling
       * is required to first fetch a resource from the repository,
       * respond to the GET request, then free the resource copy.
       */
      int handle_get;
  
      /* Get a resource descriptor for the URI in a request.
       * A descriptor is returned even if the resource does not exist.
       * The return value should only be NULL for some kind of fatal error.
       *
       * The root_dir is the root of the directory for which this repository
       * is configured.
       * The workspace is the value of any Target-Selector header, or NULL
       * if there is none.
       *
       * The provider may associate the request storage pool with the resource,
       * to use in other operations on that resource.
       */
      dav_resource * (*get_resource)(
          request_rec *r,
          const char *root_dir,
          const char *workspace
      );
  
      /* Get a resource descriptor for the parent of the given resource.
       * The resources need not exist.  NULL is returned if the resource 
       * is the root collection.
       */
      dav_resource * (*get_parent_resource)(
          const dav_resource *resource
      );
  
      /* Determine whether two resource descriptors refer to the same resource.
      *
       * Result != 0 => the resources are the same.
       */
      int (*is_same_resource)(
          const dav_resource *res1,
          const dav_resource *res2
      );
  
      /* Determine whether one resource is a parent (immediate or otherwise)
       * of another.
       *
       * Result != 0 => res1 is a parent of res2.
       */
      int (*is_parent_resource)(
          const dav_resource *res1,
          const dav_resource *res2
      );
  
      /*
      ** Open a stream for this resource, using the specified mode. The
      ** stream will be returned in *stream.
      */
      dav_error * (*open_stream)(const dav_resource *resource,
  			       dav_stream_mode mode,
  			       dav_stream **stream);
  
      /*
      ** Close the specified stream.
      **
      ** mod_dav will (ideally) make sure to call this. For safety purposes,
      ** a provider should (ideally) register a cleanup function with the
      ** request pool to get this closed and cleaned up.
      **
      ** Note the possibility of an error from the close -- it is entirely
      ** feasible that the close does a "commit" of some kind, which can
      ** produce an error.
      **
      ** commit should be TRUE (non-zero) or FALSE (0) if the stream was
      ** opened for writing. This flag states whether to retain the file
      ** or not.
      ** Note: the commit flag is ignored for streams opened for reading.
      */
      dav_error * (*close_stream)(dav_stream *stream, int commit);
  
      /*
      ** Read data from the stream.
      **
      ** The size of the buffer is passed in *bufsize, and the amount read
      ** is returned in *bufsize.
      **
      ** *bufsize should be set to zero when the end of file is reached.
      ** As a corollary, this function should always read at least one byte
      ** on each call, until the EOF condition is met.
      */
      dav_error * (*read_stream)(dav_stream *stream,
  			       void *buf, size_t *bufsize);
  
      /*
      ** Write data to the stream.
      **
      ** All of the bytes must be written, or an error should be returned.
      */
      dav_error * (*write_stream)(dav_stream *stream,
  				const void *buf, size_t bufsize);
  
      /*
      ** Seek to an absolute position in the stream. This is used to support
      ** Content-Range in a GET/PUT.
      **
      ** NOTE: if this function is NULL (which is allowed), then any
      **       operations using Content-Range will be refused.
      */
      dav_error * (*seek_stream)(dav_stream *stream, off_t abs_position);
  
      /*
      ** If a GET is processed using a stream (open_stream, read_stream)
      ** rather than via a sub-request (on get_pathname), then this function
      ** is used to provide the repository with a way to set the headers
      ** in the response.
      **
      ** It may be NULL if get_pathname is provided.
      */
      dav_error * (*set_headers)(request_rec *r,
  			       const dav_resource *resource);
  
      /* Get a pathname for the file represented by the resource descriptor.
       * A provider may need to create a temporary copy of the file, if it is
       * not directly accessible in a filesystem. free_handle_p will be set by
       * the provider to point to information needed to clean up any temporary
       * storage used.
       *
       * Returns NULL if the file could not be made accessible.
       */
      const char * (*get_pathname)(
          const dav_resource *resource,
          void **free_handle_p
      );
  
      /* Free any temporary storage associated with a file made accessible by
       * get_pathname().
       */
      void (*free_file)(
          void *free_handle
      );
  
      /* Create a collection resource. The resource must not already exist.
       *
       * Result == NULL if the collection was created successfully. Also, the
       * resource object is updated to reflect that the resource exists, and
       * is a collection.
       */
      dav_error * (*create_collection)(
          pool *p, dav_resource *resource
      );
  
      /* Copy one resource to another. The destination must not exist.
       * Handles both files and collections. Properties are copied as well.
       * The depth argument is ignored for a file, and can be either 0 or
       * DAV_INFINITY for a collection.
       * If an error occurs in a child resource, then the return value is
       * non-NULL, and *response is set to a multistatus response.
       * If the copy is successful, the dst resource object is
       * updated to reflect that the resource exists.
       */
      dav_error * (*copy_resource)(
          const dav_resource *src,
          dav_resource *dst,
  	int depth,
          dav_response **response
      );
  
      /* Move one resource to another. The destination must not exist.
       * Handles both files and collections. Properties are moved as well.
       * If an error occurs in a child resource, then the return value is
       * non-NULL, and *response is set to a multistatus response.
       * If the move is successful, the src and dst resource objects are
       * updated to reflect that the source no longer exists, and the
       * destination does.
       */
      dav_error * (*move_resource)(
          dav_resource *src,
          dav_resource *dst,
          dav_response **response
      );
  
      /* Remove a resource. Handles both files and collections.
       * Removes any associated properties as well.
       * If an error occurs in a child resource, then the return value is
       * non-NULL, and *response is set to a multistatus response.
       * If the delete is successful, the resource object is updated to
       * reflect that the resource no longer exists.
       */
      dav_error * (*remove_resource)(
          dav_resource *resource,
          dav_response **response
      );
  
      /* Walk a resource hierarchy.
       *
       * Iterates over the resource hierarchy specified by wctx->resource.
       * Parameter for control of the walk and the callback are specified
       * by wctx.
       *
       * An HTTP_* status code is returned if an error occurs during the
       * walk or the callback indicates an error. OK is returned on success.
       */
      dav_error * (*walk)(dav_walker_ctx *wctx, int depth);
  
      /* Get the entity tag for a resource */
      const char * (*getetag)(const dav_resource *resource);
  };
  
  
  /* --------------------------------------------------------------------
  **
  ** VERSIONING FUNCTIONS
  */
  
  /* dav_get_target_selector:
   *
   * Returns any Target-Selector header in a request
   * (used by versioning clients)
   */
  const char *dav_get_target_selector(request_rec *r);
  
  /* Ensure that a resource is writable. If there is no versioning
   * provider, then this is essentially a no-op. Versioning repositories
   * require explicit resource creation and checkout before they can
   * be written to. If a new resource is to be created, or an existing
   * resource deleted, the parent collection must be checked out as well.
   *
   * Set the parent_only flag to only make the parent collection writable.
   * Otherwise, both parent and child are made writable as needed. If the
   * child does not exist, then a new versioned resource is created and
   * checked out.
   *
   * The parent_resource and parent_was_writable arguments are optional
   * (i.e. they may be NULL). If parent_only is set, then the
   * resource_existed and resource_was_writable arguments are ignored.
   *
   * The previous states of the resources are returned, so they can be
   * restored after the operation completes (see
   * dav_revert_resource_writability())
   */
  dav_error *dav_ensure_resource_writable(request_rec *r,
  					dav_resource *resource,
                                          int parent_only,
  					dav_resource **parent_resource,
  					int *resource_existed,
  					int *resource_was_writable,
  					int *parent_was_writable);
  
  /* Revert the writability of resources back to what they were
   * before they were modified. If undo == 0, then the resource
   * modifications are maintained (i.e. they are checked in).
   * If undo != 0, then resource modifications are discarded
   * (i.e. they are unchecked out).
   *
   * The resource and parent_resource arguments are optional
   * (i.e. they may be NULL).
   */
  dav_error *dav_revert_resource_writability(request_rec *r,
  					   dav_resource *resource,
  					   dav_resource *parent_resource,
  					   int undo,
  					   int resource_existed,
  					   int resource_was_writable,
  					   int parent_was_writable);
  
  /* Versioning provider hooks */
  struct dav_hooks_vsn
  {
      /* Return supported versioning level
       * for the Versioning header
       */
      const char * (*get_vsn_header)(void);
  
      /* Create a new (empty) resource. If successful,
       * the resource object state is updated appropriately.
       */
      dav_error * (*mkresource)(dav_resource *resource);
  
      /* Checkout a resource. If successful, the resource
       * object state is updated appropriately.
       */
      dav_error * (*checkout)(dav_resource *resource);
  
      /* Uncheckout a resource. If successful, the resource
       * object state is updated appropriately.
       */
      dav_error * (*uncheckout)(dav_resource *resource);
  
      /* Checkin a working resource. If successful, the resource
       * object state is updated appropriately.
       */
      dav_error * (*checkin)(dav_resource *resource);
  
      /* Determine whether a non-versioned (or non-existent) resource
       * is versionable. Returns != 0 if resource can be versioned.
       */
      int (*versionable)(const dav_resource *resource);
  
      /* Determine whether auto-versioning is enabled for a resource
       * (which may not exist, or may not be versioned).
       * Returns != 0 if auto-versioning is enabled.
       */
      int (*auto_version_enabled)(const dav_resource *resource);
  };
  
  
  /* --------------------------------------------------------------------
  **
  ** MISCELLANEOUS STUFF
  */
  
  /* allow providers access to the per-directory parameters */
  table *dav_get_dir_params(const request_rec *r);
  
  /* fetch the "LimitXMLRequestBody" in force for this resource */
  size_t dav_get_limit_xml_body(const request_rec *r);
  
  /* manage an array of unique URIs: dav_insert_uri() and DAV_GET_URI_ITEM() */
  
  /* return the URI's (existing) index, or insert it and return a new index */
  int dav_insert_uri(array_header *uri_array, const char *uri);
  #define DAV_GET_URI_ITEM(ary, i)    (((const char * const *)(ary)->elts)[i])
  
  
  #ifdef __cplusplus
  }
  #endif
  
  #endif /* _MOD_DAV_H_ */
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/opaquelock.c
  
  Index: opaquelock.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV opaquelocktoken scheme implementation
  **
  ** Written 5/99 by Keith Wannamaker, wannamak@us.ibm.com
  ** Adapted from ISO/DCE RPC spec and a former Internet Draft
  ** by Leach and Salz:
  ** http://www.ics.uci.edu/pub/ietf/webdav/uuid-guid/draft-leach-uuids-guids-01
  **
  ** Portions of the code are covered by the following license:
  **
  ** Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc.
  ** Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. &
  ** Digital Equipment Corporation, Maynard, Mass.
  ** Copyright (c) 1998 Microsoft.
  ** To anyone who acknowledges that this file is provided "AS IS"
  ** without any express or implied warranty: permission to use, copy,
  ** modify, and distribute this file for any purpose is hereby
  ** granted without fee, provided that the above copyright notices and
  ** this notice appears in all source code copies, and that none of
  ** the names of Open Software Foundation, Inc., Hewlett-Packard
  ** Company, or Digital Equipment Corporation be used in advertising
  ** or publicity pertaining to distribution of the software without
  ** specific, written prior permission.  Neither Open Software
  ** Foundation, Inc., Hewlett-Packard Company, Microsoft, nor Digital Equipment
  ** Corporation makes any representations about the suitability of
  ** this software for any purpose.
  */
  
  #include <string.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <time.h>
  
  #include "httpd.h"
  #include "ap_md5.h"
  #include "ap_ctype.h"
  
  #include "mod_dav.h"
  #include "dav_opaquelock.h"
  
  #ifdef WIN32
  #include <windows.h>
  #else
  #include <sys/types.h>
  #include <sys/time.h>
  #endif
  
  /* set the following to the number of 100ns ticks of the actual resolution of
     your system's clock */
  #define UUIDS_PER_TICK 1024
  
  /* Set this to what your compiler uses for 64 bit data type */
  #ifdef WIN32
  #define unsigned64_t unsigned __int64
  #else
  #define unsigned64_t unsigned long long
  #endif
  
  typedef unsigned64_t uuid_time_t;
  
  static void format_uuid_v1(uuid_t * uuid, unsigned16 clockseq, uuid_time_t timestamp, uuid_node_t node);
  static void get_current_time(uuid_time_t * timestamp);
  static unsigned16 true_random(void);
  static void get_pseudo_node_identifier(uuid_node_t *node);
  static void get_system_time(uuid_time_t *uuid_time);
  static void get_random_info(unsigned char seed[16]);
  
  
  /* dav_create_opaquelocktoken - generates a UUID version 1 token.
   *   Clock_sequence and node_address set to pseudo-random
   *   numbers during init.  
   *
   *   Should postpend pid to account for non-seralized creation?
   */
  void dav_create_opaquelocktoken(uuid_state *st, uuid_t *u)
  {
      uuid_time_t timestamp;
      
      get_current_time(&timestamp);
      format_uuid_v1(u, st->cs, timestamp, st->node);
  }
  
  /*
   * dav_create_uuid_state - seed UUID state with pseudorandom data
   */
  void dav_create_uuid_state(uuid_state *st)
  {
      st->cs = true_random();
      get_pseudo_node_identifier(&st->node);
  }
  
  /*
   * dav_format_opaquelocktoken - generates a text representation
   *    of an opaquelocktoken
   */
  const char *dav_format_opaquelocktoken(pool *p, const uuid_t *u)
  {
      return ap_psprintf(p, "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
  		       u->time_low, u->time_mid, u->time_hi_and_version,
  		       u->clock_seq_hi_and_reserved, u->clock_seq_low,
  		       u->node[0], u->node[1], u->node[2],
  		       u->node[3], u->node[4], u->node[5]);
  }
  
  /* convert a pair of hex digits to an integer value [0,255] */
  static int dav_parse_hexpair(const char *s)
  {
      int result;
      int temp;
  
      result = s[0] - '0';
      if (result > 48)
  	result = (result - 39) << 4;
      else if (result > 16)
  	result = (result - 7) << 4;
      else
  	result = result << 4;
  
      temp = s[1] - '0';
      if (temp > 48)
  	result |= temp - 39;
      else if (temp > 16)
  	result |= temp - 7;
      else
  	result |= temp;
  
      return result;
  }
  
  /* dav_parse_opaquelocktoken:  Parses string produced from
   *    dav_format_opaquelocktoken back into a uuid_t
   *    structure.
   *
   * Returns 0 on success, 1 on failure.
   */
  int dav_parse_opaquelocktoken(const char *char_token, uuid_t *bin_token)
  {
      int i;
  
      for (i = 0; i < 36; ++i) {
  	char c = char_token[i];
  	if (!ap_isxdigit(c) &&
  	    !(c == '-' && (i == 8 || i == 13 || i == 18 || i == 23)))
  	    return 1; /* failure */
      }
      if (char_token[36] != '\0')
  	return 1; /* failure */
  
      bin_token->time_low =
  	(dav_parse_hexpair(&char_token[0]) << 24) |
  	(dav_parse_hexpair(&char_token[2]) << 16) |
  	(dav_parse_hexpair(&char_token[4]) << 8) |
  	dav_parse_hexpair(&char_token[6]);
  
      bin_token->time_mid =
  	(dav_parse_hexpair(&char_token[9]) << 8) |
  	dav_parse_hexpair(&char_token[11]);
  
      bin_token->time_hi_and_version =
  	(dav_parse_hexpair(&char_token[14]) << 8) |
  	dav_parse_hexpair(&char_token[16]);
  
      bin_token->clock_seq_hi_and_reserved = dav_parse_hexpair(&char_token[19]);
      bin_token->clock_seq_low = dav_parse_hexpair(&char_token[21]);
  
      for (i = 6; i--;)
  	bin_token->node[i] = dav_parse_hexpair(&char_token[i*2+24]);
  
      return 0;
  }
  
  /* dav_compare_opaquelocktoken:
   *    < 0 : a < b
   *   == 0 : a = b
   *    > 0 : a > b
   */
  int dav_compare_opaquelocktoken(const uuid_t a, const uuid_t b)
  {
      return memcmp(&a, &b, sizeof(uuid_t));
  }
  
  /* format_uuid_v1 -- make a UUID from the timestamp, clockseq, and node ID */
  static void format_uuid_v1(uuid_t * uuid, unsigned16 clock_seq,
  			   uuid_time_t timestamp, uuid_node_t node)
  {
      /* Construct a version 1 uuid with the information we've gathered
       * plus a few constants. */
      uuid->time_low = (unsigned long)(timestamp & 0xFFFFFFFF);
      uuid->time_mid = (unsigned short)((timestamp >> 32) & 0xFFFF);
      uuid->time_hi_and_version = (unsigned short)((timestamp >> 48) & 0x0FFF);
      uuid->time_hi_and_version |= (1 << 12);
      uuid->clock_seq_low = clock_seq & 0xFF;
      uuid->clock_seq_hi_and_reserved = (clock_seq & 0x3F00) >> 8;
      uuid->clock_seq_hi_and_reserved |= 0x80;
      memcpy(&uuid->node, &node, sizeof uuid->node);
  }
  
  /* get-current_time -- get time as 60 bit 100ns ticks since whenever.
     Compensate for the fact that real clock resolution is less than 100ns. */
  static void get_current_time(uuid_time_t * timestamp)
  {
      uuid_time_t         time_now;
      static uuid_time_t  time_last;
      static unsigned16   uuids_this_tick;
      static int          inited = 0;
      
      if (!inited) {
          get_system_time(&time_now);
          uuids_this_tick = UUIDS_PER_TICK;
          inited = 1;
      };
  	
      while (1) {
  	get_system_time(&time_now);
          
  	/* if clock reading changed since last UUID generated... */
  	if (time_last != time_now) {
  	    /* reset count of uuids gen'd with this clock reading */
  	    uuids_this_tick = 0;
  	    break;
  	};
  	if (uuids_this_tick < UUIDS_PER_TICK) {
  	    uuids_this_tick++;
  	    break;
  	};        /* going too fast for our clock; spin */
      };    /* add the count of uuids to low order bits of the clock reading */
  
      *timestamp = time_now + uuids_this_tick;
  }
  
  /* true_random -- generate a crypto-quality random number.
     This sample doesn't do that. */
  static unsigned16 true_random(void)
  {
      uuid_time_t time_now;
  
      get_system_time(&time_now);
      time_now = time_now/UUIDS_PER_TICK;
      srand((unsigned int)(((time_now >> 32) ^ time_now)&0xffffffff));
  
      return rand();
  }
  
  /* This sample implementation generates a random node ID   *
   * in lieu of a system dependent call to get IEEE node ID. */
  static void get_pseudo_node_identifier(uuid_node_t *node)
  {
      unsigned char seed[16];
  
      get_random_info(seed);
      seed[0] |= 0x80;
      memcpy(node, seed, sizeof(*node));
  }
  
  /* system dependent call to get the current system time.
     Returned as 100ns ticks since Oct 15, 1582, but resolution may be
     less than 100ns.  */
  
  #ifdef WIN32
  
  static void get_system_time(uuid_time_t *uuid_time)
  {
      ULARGE_INTEGER time;
  
      GetSystemTimeAsFileTime((FILETIME *)&time);
  
      /* NT keeps time in FILETIME format which is 100ns ticks since
         Jan 1, 1601.  UUIDs use time in 100ns ticks since Oct 15, 1582.
         The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec)
         + 18 years and 5 leap days.    */
  	
      time.QuadPart +=
  	(unsigned __int64) (1000*1000*10)		/* seconds */
  	* (unsigned __int64) (60 * 60 * 24)		/* days */
  	* (unsigned __int64) (17+30+31+365*18+5);	/* # of days */
      *uuid_time = time.QuadPart;
  }
  
  static void get_random_info(unsigned char seed[16])
  {
      AP_MD5_CTX c;
      struct {
          MEMORYSTATUS m;
          SYSTEM_INFO s;
          FILETIME t;
          LARGE_INTEGER pc;
          DWORD tc;
          DWORD l;
          char hostname[MAX_COMPUTERNAME_LENGTH + 1];
  
      } r;
      
      ap_MD5Init(&c);    /* memory usage stats */
      GlobalMemoryStatus(&r.m);    /* random system stats */
      GetSystemInfo(&r.s);    /* 100ns resolution (nominally) time of day */
      GetSystemTimeAsFileTime(&r.t);    /* high resolution performance counter */
      QueryPerformanceCounter(&r.pc);    /* milliseconds since last boot */
      r.tc = GetTickCount();
      r.l = MAX_COMPUTERNAME_LENGTH + 1;
  
      GetComputerName(r.hostname, &r.l );
      ap_MD5Update(&c, (const unsigned char *) &r, sizeof(r));
      ap_MD5Final(seed, &c);
  }
  
  #else /* WIN32 */
  
  static void get_system_time(uuid_time_t *uuid_time)
  {
      struct timeval tp;
  
      gettimeofday(&tp, (struct timezone *)0);
  
      /* Offset between UUID formatted times and Unix formatted times.
         UUID UTC base time is October 15, 1582.
         Unix base time is January 1, 1970.      */
      *uuid_time = (tp.tv_sec * 10000000) + (tp.tv_usec * 10) +
          0x01B21DD213814000LL;
  }
   
  static void get_random_info(unsigned char seed[16])
  {
      AP_MD5_CTX c;
      /* Leech & Salz use Linux-specific struct sysinfo;
       * replace with pid/tid for portability (in the spirit of mod_unique_id) */
      struct {
  	/* Add thread id here, if applicable, when we get to pthread or apr */
  	pid_t pid;
          struct timeval t;
          char hostname[257];
  
      } r;
  
      ap_MD5Init(&c);
      r.pid = getpid();
      gettimeofday(&r.t, (struct timezone *)0);
      gethostname(r.hostname, 256);
      ap_MD5Update(&c, (const unsigned char *)&r, sizeof(r));
      ap_MD5Final(seed, &c);
  }
  
  #endif /* WIN32 */
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/props.c
  
  Index: props.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV extension module for Apache 1.3.*
  **  - Property database handling (repository-independent)
  **
  ** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
  **
  ** NOTES:
  **
  **   PROPERTY DATABASE
  **
  **   This version assumes that there is a per-resource database provider
  **   to record properties. The database provider decides how and where to
  **   store these databases.
  **
  **   The DBM keys for the properties have the following form:
  **
  **     namespace ":" propname
  **
  **   For example: 5:author
  **
  **   The namespace provides an integer index into the namespace table
  **   (see below). propname is simply the property name, without a namespace
  **   prefix.
  **
  **   A special case exists for properties that had a prefix starting with
  **   "xml". The XML Specification reserves these for future use. mod_dav
  **   stores and retrieves them unchanged. The keys for these properties
  **   have the form:
  **
  **     ":" propname
  **
  **   The propname will contain the prefix and the property name. For
  **   example, a key might be ":xmlfoo:name"
  **
  **   The ":name" style will also be used for properties that do not
  **   exist within a namespace.
  **
  **   The DBM values consist of two null-terminated strings, appended
  **   together (the null-terms are retained and stored in the database).
  **   The first string is the xml:lang value for the property. An empty
  **   string signifies that a lang value was not in context for the value.
  **   The second string is the property value itself.
  **
  **
  **   NAMESPACE TABLE
  **
  **   The namespace table is an array that lists each of the namespaces
  **   that are in use by the properties in the given propdb. Each entry
  **   in the array is a simple URI.
  **
  **   For example: http://www.foo.bar/standards/props/
  **
  **   The prefix used for the property is stripped and the URI for it
  **   is entered into the namespace table. Also, any namespaces used
  **   within the property value will be entered into the table (and
  **   stripped from the child elements).
  **
  **   The namespaces are stored in the DBM database under the "METADATA" key.
  **
  **
  **   STRIPPING NAMESPACES
  **
  **   Within the property values, the namespace declarations (xmlns...)
  **   are stripped. Each element and attribute will have its prefix removed
  **   and a new prefix inserted.
  **
  **   This must be done so that we can return multiple properties in a
  **   PROPFIND which may have (originally) used conflicting prefixes. For
  **   that case, we must bind all property value elements to new namespace
  **   values.
  **
  **   This implies that clients must NOT be sensitive to the namespace
  **   prefix used for their properties. It WILL change when the properties
  **   are returned (we return them as "ns<index>", e.g. "ns5"). Also, the
  **   property value can contain ONLY XML elements and CDATA. PI and comment
  **   elements will be stripped. CDATA whitespace will be preserved, but
  **   whitespace within element tags will be altered. Attribute ordering
  **   may be altered. Element and CDATA ordering will be preserved.
  **
  **
  **   ATTRIBUTES ON PROPERTY NAME ELEMENTS
  **
  **   When getting/setting properties, the XML used looks like:
  **
  **     <prop>
  **       <propname1>value</propname1>
  **       <propname2>value</propname1>
  **     </prop>
  **
  **   This implementation (mod_dav) DOES NOT save any attributes that are
  **   associated with the <propname1> element. The property value is deemed
  **   to be only the contents ("value" in the above example).
  **
  **   We do store the xml:lang value (if any) that applies to the context
  **   of the <propname1> element. Whether the xml:lang attribute is on
  **   <propname1> itself, or from a higher level element, we will store it
  **   with the property value.
  **
  **
  **   VERSIONING
  **
  **   The DBM db contains a key named "METADATA" that holds database-level
  **   information, such as the namespace table. The record also contains the
  **   db's version number as the very first 16-bit value. This first number
  **   is actually stored as two single bytes: the first byte is a "major"
  **   version number. The second byte is a "minor" number.
  **
  **   If the major number is not what mod_dav expects, then the db is closed
  **   immediately and an error is returned. A minor number change is
  **   acceptable -- it is presumed that old/new dav_props.c can deal with
  **   the database format. For example, a newer dav_props might update the
  **   minor value and append information to the end of the metadata record
  **   (which would be ignored by previous versions).
  **
  **
  ** ISSUES:
  **
  **   At the moment, for the dav_get_allprops() and dav_get_props() functions,
  **   we must return a set of xmlns: declarations for ALL known namespaces
  **   in the file. There isn't a way to filter this because we don't know
  **   which are going to be used or not. Examining property names is not
  **   sufficient because the property values could use entirely different
  **   namespaces.
  **
  **   ==> we must devise a scheme where we can "garbage collect" the namespace
  **       entries from the property database.
  */
  
  #include "mod_dav.h"
  
  #include "http_log.h"
  #include "http_request.h"
  
  /*
  ** There is some rough support for writeable DAV:getcontenttype and
  ** DAV:getcontentlanguage properties. If this #define is (1), then
  ** this support is disabled.
  **
  ** We are disabling it because of a lack of support in GET and PUT
  ** operations. For GET, it would be "expensive" to look for a propdb,
  ** open it, and attempt to extract the Content-Type and Content-Language
  ** values for the response.
  ** (Handling the PUT would not be difficult, though)
  */
  #define DAV_DISABLE_WRITEABLE_PROPS	1
  
  #define DAV_GDBM_NS_KEY		"METADATA"
  #define DAV_GDBM_NS_KEY_LEN	8
  
  #define DAV_EMPTY_VALUE		"\0"	/* TWO null terms */
  
  /* the namespace URI was not found; no ID is available */
  #define DAV_NS_ERROR_NOT_FOUND	(DAV_NS_ERROR_BASE)
  
  typedef struct {
      unsigned char major;
  #define DAV_DBVSN_MAJOR		4
      /*
      ** V4 -- 0.9.9 ..
      **       Prior versions could have keys or values with invalid
      **       namespace prefixes as a result of the xmlns="" form not
      **       resetting the default namespace to be "no namespace". The
      **       namespace would be set to "" which is invalid; it should
      **       be set to "no namespace".
      **
      ** V3 -- 0.9.8
      **       Prior versions could have values with invalid namespace
      **       prefixes due to an incorrect mapping of input to propdb
      **       namespace indices. Version bumped to obsolete the old
      **       values.
      **
      ** V2 -- 0.9.7
      **       This introduced the xml:lang value into the property value's
      **       record in the propdb.
      **
      ** V1 -- .. 0.9.6
      **       Initial version.
      */
  
  
      unsigned char minor;
  #define DAV_DBVSN_MINOR		0
  
      short ns_count;
  
  } dav_propdb_metadata;
  
  struct dav_propdb {
      int version;		/* *minor* version of this db */
  
      pool *p;			/* the pool we should use */
      request_rec *r;		/* the request record */
  
      dav_resource *resource;	/* the target resource */
  
      int deferred;		/* open of db has been deferred */
      dav_db *db;			/* underlying database containing props */
  
      dav_buffer ns_table;	/* table of namespace URIs */
      short ns_count;		/* number of entries in table */
      int ns_table_dirty;		/* ns_table was modified */
  
      array_header *ns_xlate;	/* translation of an elem->ns to URI */
      int *ns_map;		/* map elem->ns to propdb ns values */
      int incomplete_map;		/* some mappings do not exist */
  
      dav_lockdb *lockdb;		/* the lock database */
  
      dav_buffer wb_key;		/* work buffer for dav_gdbm_key */
      dav_buffer wb_lock;		/* work buffer for lockdiscovery property */
  
      /* if we ever run a GET subreq, it will be stored here */
      request_rec *subreq;
  
      /* hooks we should use for processing (based on the target resource) */
      const dav_hooks_db *db_hooks;
      const dav_hooks_vsn *vsn_hooks;
  
      const dav_dyn_hooks *liveprop;	/* head of list */
  };
  
  /* ### move these into a "core" liveprop provider? */
  static const char * const dav_core_props[] =
  {
      "getcontenttype",
      "getcontentlanguage",
      "lockdiscovery",
      "resourcetype",
      "supportedlock",
  
      NULL	/* sentinel */
  };
  enum {
      DAV_PROPID_CORE_getcontenttype = DAV_PROPID_CORE,
      DAV_PROPID_CORE_getcontentlanguage,
      DAV_PROPID_CORE_lockdiscovery,
      DAV_PROPID_CORE_resourcetype,
      DAV_PROPID_CORE_supportedlock,
  
      DAV_PROPID_CORE_UNKNOWN
  };
  #define DAV_IS_CORE_PROP(propid)	((propid) >= DAV_PROPID_CORE && \
  					 (propid) <= DAV_PROPID_CORE_UNKNOWN)
  
  /*
  ** This structure is used to track information needed for a rollback.
  ** If a SET was performed and no prior value existed, then value.dptr
  ** will be NULL.
  */
  typedef struct dav_rollback_item {
      dav_datum key;		/* key for the item being saved */
      dav_datum value;		/* value before set/replace/delete */
  
      /* or use the following (choice selected by dav_prop_ctx.is_liveprop) */
      struct dav_liveprop_rollback *liveprop;	/* liveprop rollback ctx */
  
  } dav_rollback_item;
  
  
  #if 0
  /* ### unused */
  static const char *dav_get_ns_table_uri(dav_propdb *propdb, int ns)
  {
      const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
  
      while (ns--)
  	p += strlen(p) + 1;
  
      return p;
  }
  #endif
  
  static void dav_find_liveprop(dav_propdb *propdb, dav_xml_elem *elem)
  {
      int propid;
      const char *ns_uri;
      const dav_dyn_hooks *ddh;
  
      if (elem->ns == DAV_NS_DAV_ID) {
  	const char * const *p = dav_core_props;
  
  	for (propid = DAV_PROPID_CORE; *p != NULL; ++p, ++propid)
  	    if (strcmp(elem->name, *p) == 0) {
  		elem->propid = propid;
  		return;
  	    }
  
  	/* didn't find it. fall thru. a provider can define DAV: props */
      }
      else if (elem->ns == DAV_NS_NONE) {
  	/* policy: liveprop providers cannot define no-namespace properties */
  	elem->propid = DAV_PROPID_CORE_UNKNOWN;
  	return;
      }
  
      ns_uri = DAV_GET_URI_ITEM(propdb->ns_xlate, elem->ns);
  
      for (ddh = propdb->liveprop; ddh != NULL; ddh = ddh->next) {
  	propid = (*DAV_AS_HOOKS_LIVEPROP(ddh)->find_prop)(ns_uri, elem->name);
  	if (propid != 0) {
  	    elem->propid = propid;
  	    elem->provider = DAV_AS_HOOKS_LIVEPROP(ddh);
  	    elem->ns_map = ddh->ctx.ns_map;
  	    return;
  	}
      }
  
      elem->propid = DAV_PROPID_CORE_UNKNOWN;
  }
  
  /* is the live property read/write? */
  static int dav_rw_liveprop(dav_propdb *propdb, int propid)
  {
      dav_prop_rw rw;
      const dav_dyn_hooks *ddh;
  
      /* these are defined as read-only */
      if (propid == DAV_PROPID_CORE_lockdiscovery
  	|| propid == DAV_PROPID_CORE_resourcetype
  #if DAV_DISABLE_WRITEABLE_PROPS
  	|| propid == DAV_PROPID_CORE_getcontenttype
  	|| propid == DAV_PROPID_CORE_getcontentlanguage
  #endif
  	|| propid == DAV_PROPID_CORE_supportedlock) {
  
  	return 0;
      }
  
      /* these are defined as read/write */
      if (propid == DAV_PROPID_CORE_getcontenttype
  	|| propid == DAV_PROPID_CORE_getcontentlanguage
  	|| propid == DAV_PROPID_CORE_UNKNOWN) {
  
  	return 1;
      }
  
      /*
      ** Check the liveprop providers
      */
      for (ddh = propdb->liveprop; ddh != NULL; ddh = ddh->next) {
  	rw = (*DAV_AS_HOOKS_LIVEPROP(ddh)->is_writeable)(propdb->resource,
  							 propid);
  	if (rw == DAV_PROP_RW_YES)
  	    return 1;
  	if (rw == DAV_PROP_RW_NO)
  	    return 0;
      }
  
      /*
      ** No provider recognized the property, so it must be dead (and writable)
      */
      return 1;
  }
  
  /* do a sub-request to fetch properties for the target resource's URI. */
  static void dav_do_prop_subreq(dav_propdb *propdb)
  {
      /* perform a "GET" on the resource's URI (note that the resource
         may not correspond to the current request!). */
      propdb->subreq = ap_sub_req_lookup_uri(propdb->resource->uri, propdb->r);
  }
  
  static dav_error * dav_insert_coreprop(dav_propdb *propdb,
  				       int propid, const char *name,
  				       int getvals,
  				       dav_text_header *phdr,
  				       int *inserted)
  {
      const char *value = NULL;
  
      *inserted = 0;
  
      /* fast-path the common case */
      if (propid == DAV_PROPID_CORE_UNKNOWN)
  	return NULL;
  
      switch (propid) {
  
      case DAV_PROPID_CORE_resourcetype:
          switch (propdb->resource->type) {
          case DAV_RESOURCE_TYPE_REGULAR:
              if (propdb->resource->collection) {
  	        value = "<D:collection/>";
              }
  	    else {
  		/* ### should we denote lock-null resources? */
  
  		value = "";	/* becomes: <D:resourcetype/> */
  	    }
              break;
          case DAV_RESOURCE_TYPE_HISTORY:
  	    value = "<D:history/>";
              break;
          case DAV_RESOURCE_TYPE_WORKSPACE:
  	    value = "<D:workspace/>";
              break;
          case DAV_RESOURCE_TYPE_ACTIVITY:
  	    value = "<D:activity/>";
              break;
          case DAV_RESOURCE_TYPE_CONFIGURATION:
  	    value = "<D:configuration/>";
              break;
  	case DAV_RESOURCE_TYPE_REVISION:
  	    value = "<D:revision/>";
  	    break;
  
  	default:
  	    /* ### bad juju */
  	    break;
          }
  	break;
  
      case DAV_PROPID_CORE_lockdiscovery:
          if (propdb->lockdb != NULL) {
  	    dav_error *err;
  	    dav_lock *locks;
  
  	    if ((err = dav_lock_query(propdb->lockdb, propdb->resource,
  				      &locks)) != NULL) {
  		return dav_push_error(propdb->p, err->status, 0,
  				      "DAV:lockdiscovery could not be "
  				      "determined due to a problem fetching "
  				      "the locks for this resource.",
  				      err);
  	    }
  
  	    /* fast-path the no-locks case */
  	    if (locks == NULL) {
  		value = "";
  	    }
  	    else {
  		/*
  		** This may modify the buffer. value may point to
  		** wb_lock.pbuf or a string constant.
  		*/
  		value = dav_lock_get_activelock(propdb->r, locks,
  						&propdb->wb_lock);
  
  		/* make a copy to isolate it from changes to wb_lock */
  		value = ap_pstrdup(propdb->p, propdb->wb_lock.buf);
  	    }
          }
  	break;
  
      case DAV_PROPID_CORE_supportedlock:
          if (propdb->lockdb != NULL) {
  	    value = (*propdb->lockdb->hooks->get_supportedlock)();
          }
  	break;
  
      case DAV_PROPID_CORE_getcontenttype:
  	if (propdb->subreq == NULL) {
  	    dav_do_prop_subreq(propdb);
  	}
  	if (propdb->subreq->content_type != NULL) {
  	    value = propdb->subreq->content_type;
  	}
  	break;
  
      case DAV_PROPID_CORE_getcontentlanguage:
      {
  	const char *lang;
  
  	if (propdb->subreq == NULL) {
  	    dav_do_prop_subreq(propdb);
  	}
  	if ((lang = ap_table_get(propdb->subreq->headers_out,
  				 "Content-Language")) != NULL) {
  	    value = lang;
  	}
  	break;
      }
  
      case DAV_PROPID_CORE_UNKNOWN:
      default:
  	/* fall through to interpret as a dead property */
  	break;
      }
  
      /* if something was supplied, then insert it */
      if (value != NULL) {
  	const char *s;
  
  	if (getvals && *value != '\0') {
  	    /* use D: prefix to refer to the DAV: namespace URI */
  	    s = ap_psprintf(propdb->p, "<D:%s>%s</D:%s>" DEBUG_CR,
  			    name, value, name);
  	}
  	else {
  	    /* use D: prefix to refer to the DAV: namespace URI */
  	    s = ap_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name);
  	}
  	dav_text_append(propdb->p, phdr, s);
  
  	*inserted = 1;
      }
  
      return NULL;
  }
  
  static dav_error * dav_insert_liveprop(dav_propdb *propdb,
  				       const dav_xml_elem *elem,
  				       int getvals,
  				       dav_text_header *phdr,
  				       int *inserted)
  {
      dav_prop_insert pi;
  
      *inserted = 0;
  
      if (DAV_IS_CORE_PROP(elem->propid))
  	return dav_insert_coreprop(propdb, elem->propid, elem->name,
  				   getvals, phdr, inserted);
  
      /* ask the provider (that defined this prop) to insert the prop */
      pi = (*elem->provider->insert_prop)(propdb->resource, elem->propid,
  					getvals, elem->ns_map, phdr);
  #if DAV_DEBUG
      if (pi == DAV_PROP_INSERT_NOTME) {
  	/* ### the provider should have returned NOTDEF, at least */
  	return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "DESIGN ERROR: a liveprop provider defined "
  			     "a property, but did not respond to the "
  			     "insert_prop hook for it.");
      }
  #endif
  
      if (pi != DAV_PROP_INSERT_NOTDEF)
  	*inserted = 1;
  
      return NULL;
  }
  
  static void dav_append_prop(dav_propdb *propdb,
  			    const char *name, const char *value,
  			    dav_text_header *phdr)
  {
      const char *s;
      const char *lang = value;
  
      /* skip past the xml:lang value */
      value += strlen(lang) + 1;
  
      if (*value == '\0') {
  	/* the property is an empty value */
  	if (*name == ':') {
  	    /* "no namespace" case */
  	    s = ap_psprintf(propdb->p, "<%s/>" DEBUG_CR, name+1);
  	}
  	else {
  	    s = ap_psprintf(propdb->p, "<ns%s/>" DEBUG_CR, name);
  	}
      }
      else if (*lang != '\0') {
  	if (*name == ':') {
  	    /* "no namespace" case */
  	    s = ap_psprintf(propdb->p, "<%s xml:lang=\"%s\">%s</%s>" DEBUG_CR,
  			    name+1, lang, value, name+1);
  	}
  	else {
  	    s = ap_psprintf(propdb->p, "<ns%s xml:lang=\"%s\">%s</ns%s>" DEBUG_CR,
  			    name, lang, value, name);
  	}
      }
      else if (*name == ':') {
  	/* "no namespace" case */
  	s = ap_psprintf(propdb->p, "<%s>%s</%s>" DEBUG_CR, name+1, value, name+1);
      }
      else {
  	s = ap_psprintf(propdb->p, "<ns%s>%s</ns%s>" DEBUG_CR, name, value, name);
      }
      dav_text_append(propdb->p, phdr, s);
  }
  
  /*
  ** Prepare the ns_map variable in the propdb structure. This entails copying
  ** all URIs from the "input" namespace list (in propdb->ns_xlate) into the
  ** propdb's list of namespaces. As each URI is copied (or pre-existing
  ** URI looked up), the index mapping is stored into the ns_map variable.
  **
  ** Note: we must copy all declared namespaces because we cannot easily
  **   determine which input namespaces were actually used within the property
  **   values that are being stored within the propdb. Theoretically, we can
  **   determine this at the point where we serialize the property values
  **   back into strings. This would require a bit more work, and will be
  **   left to future optimizations.
  **
  ** ### we should always initialize the propdb namespace array with "DAV:"
  ** ### since we know it will be entered anyhow (by virtue of it always
  ** ### occurring in the ns_xlate array). That will allow us to use
  ** ### DAV_NS_DAV_ID for propdb ns values, too.
  */
  static void dav_prep_ns_map(dav_propdb *propdb, int add_ns)
  {
      int i;
      const char **puri;
      const int orig_count = propdb->ns_count;
      int *pmap;
      int updating = 0;	/* are we updating an existing ns_map? */
  
      if (propdb->ns_map) {
  	if (add_ns && propdb->incomplete_map) {
  	    /* we must revisit the map and insert new entries */
  	    updating = 1;
  	    propdb->incomplete_map = 0;
  	}
  	else {
  	    /* nothing to do: we have a proper ns_map */
  	    return;
  	}
      }
      else {
  	propdb->ns_map = ap_palloc(propdb->p, propdb->ns_xlate->nelts * sizeof(*propdb->ns_map));
      }
  
      pmap = propdb->ns_map;
  
      /* ### stupid O(n * orig_count) algorithm */
      for (i = propdb->ns_xlate->nelts, puri = (const char **)propdb->ns_xlate->elts;
  	 i-- > 0;
  	 ++puri, ++pmap) {
  
  	const char *uri = *puri;
  	const size_t uri_len = strlen(uri);
  
  	if (updating) {
  	    /* updating an existing mapping... we can skip a lot of stuff */
  
  	    if (*pmap != DAV_NS_ERROR_NOT_FOUND) {
  		/* This entry has been filled in, so we can skip it */
  		continue;
  	    }
  	}
  	else {
  	    int j;
  	    size_t len;
  	    const char *p;
  
  	    /*
  	    ** GIVEN: uri (a namespace URI from the request input)
  	    **
  	    ** FIND: an equivalent URI in the propdb namespace table
  	    */
  
  	    /* only scan original entries (we may have added some in here) */
  	    for (p = propdb->ns_table.buf + sizeof(dav_propdb_metadata),
  		     j = 0;
  		 j < orig_count;
  		 ++j, p += len + 1) {
  
  		len = strlen(p);
  
  		if (uri_len != len)
  		    continue;
  		if (memcmp(uri, p, len) == 0) {
  		    *pmap = j;
  		    goto next_input_uri;
  		}
  	    }
  
  	    if (!add_ns) {
  		*pmap = DAV_NS_ERROR_NOT_FOUND;
  
  		/*
  		** This flag indicates that we have an ns_map with missing
  		** entries. If dav_prep_ns_map() is called with add_ns==1 AND
  		** this flag is set, then we zip thru the array and add those
  		** URIs (effectively updating the ns_map as if add_ns=1 was
  		** passed when the initial prep was called).
  		*/
  		propdb->incomplete_map = 1;
  
  		continue;
  	    }
  	}
  
  	/*
  	** The input URI was not found in the propdb namespace table, and
  	** we are supposed to add it. Append it to the table and store
  	** the index into the ns_map.
  	*/
  	dav_check_bufsize(propdb->p, &propdb->ns_table, uri_len + 1);
  	memcpy(propdb->ns_table.buf + propdb->ns_table.cur_len, uri, uri_len + 1);
  	propdb->ns_table.cur_len += uri_len + 1;
  
  	propdb->ns_table_dirty = 1;
  
  	*pmap = propdb->ns_count++;
  
     next_input_uri:
  	;
      }
  }
  
  /* find the "DAV:" namespace in our table and return its ID. */
  static int dav_find_dav_id(dav_propdb *propdb)
  {
      const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
      int ns;
  
      for (ns = 0; ns < propdb->ns_count; ++ns) {
  	size_t len = strlen(p);
  
  	if (len == 4 && memcmp(p, "DAV:", 5) == 0)
  	    return ns;
  	p += len + 1;
      }
  
      /* the "DAV:" namespace is not present */
      return -1;
  }
  
  static void dav_insert_xmlns(pool *p, const char *pre_prefix, int ns,
  			     const char *ns_uri, dav_text_header *phdr)
  {
      const char *s;
  
      s = ap_psprintf(p, " xmlns:%s%d=\"%s\"", pre_prefix, ns, ns_uri);
      dav_text_append(p, phdr, s);
  }
  
  /* return all known namespaces (in this propdb) */
  static void dav_get_propdb_xmlns(dav_propdb *propdb, dav_text_header *phdr)
  {
      int i;
      const char *p = propdb->ns_table.buf + sizeof(dav_propdb_metadata);
      size_t len;
  
      /* note: ns_count == 0 when we have no propdb file */
      for (i = 0; i < propdb->ns_count; ++i, p += len + 1) {
  
  	len = strlen(p);
  
  	dav_insert_xmlns(propdb->p, "ns", i, p, phdr);
      }
  }
  
  /* add a namespace decl from one of the namespace tables */
  static void dav_add_marked_xmlns(dav_propdb *propdb, char *marks, int ns,
  				 array_header *ns_table,
  				 const char *pre_prefix,
  				 dav_text_header *phdr)
  {
      if (marks[ns])
  	return;
      marks[ns] = 1;
  
      dav_insert_xmlns(propdb->p,
  		     pre_prefix, ns, DAV_GET_URI_ITEM(ns_table, ns),
  		     phdr);
  }
  
  /*
  ** Internal function to build a key
  **
  ** WARNING: returns a pointer to a "static" buffer holding the key. The
  **          value must be copied or no longer used if this function is
  **          called again.
  */
  static dav_datum dav_gdbm_key(dav_propdb *propdb, const dav_xml_elem *elem)
  {
      int ns;
      char nsbuf[20];
      size_t l_ns;
      size_t l_name = strlen(elem->name);
      dav_datum key = { 0 };
  
      /*
       * Convert namespace ID to a string. "no namespace" is an empty string,
       * so the keys will have the form ":name". Otherwise, the keys will
       * have the form "#:name".
       */
      if (elem->ns == DAV_NS_NONE) {
  	nsbuf[0] = '\0';
  	l_ns = 0;
      }
      else {
  	if (propdb->ns_map == NULL) {
  	    /*
  	     * Note that we prep the map and do NOT add namespaces. If that
  	     * is required, then the caller should have called prep
  	     * beforehand, passing the correct values.
  	     */
  	    dav_prep_ns_map(propdb, 0);
  	}
  
  	ns = propdb->ns_map[elem->ns];
  	if (DAV_NS_IS_ERROR(ns))
  	    return key;		/* zeroed */
  
  	l_ns = sprintf(nsbuf, "%d", ns);
      }
  
      /* assemble: #:name */
      dav_set_bufsize(propdb->p, &propdb->wb_key, l_ns + 1 + l_name + 1);
      memcpy(propdb->wb_key.buf, nsbuf, l_ns);
      propdb->wb_key.buf[l_ns] = ':';
      memcpy(&propdb->wb_key.buf[l_ns + 1], elem->name, l_name + 1);
  
      /* build the database key */
      key.dsize = l_ns + 1 + l_name + 1;
      key.dptr = propdb->wb_key.buf;
  
      return key;
  }
  
  dav_error *dav_really_open_db(dav_propdb *propdb, int ro)
  {
      dav_error *err;
      dav_datum key;
      dav_datum value = { 0 };
  
      /* we're trying to open the db; turn off the 'deferred' flag */
      propdb->deferred = 0;
  
      /* ask the DB provider to open the thing */
      err = (*propdb->db_hooks->open)(propdb->p, propdb->resource, ro,
  				    &propdb->db);
      if (err != NULL) {
  	return dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
  			      DAV_ERR_PROP_OPENING,
  			      "Could not open the property database.",
  			      err);
      }
  
      /*
      ** NOTE: propdb->db could be NULL if we attempted to open a readonly
      **       database that doesn't exist. If we require read/write
      **       access, then a database was created and opened.
      */
  
      if (propdb->db != NULL) {
  	key.dptr = DAV_GDBM_NS_KEY;
  	key.dsize = DAV_GDBM_NS_KEY_LEN;
  	if ((err = (*propdb->db_hooks->fetch)(propdb->db, key,
  					      &value)) != NULL) {
  	    /* ### push a higher-level description? */
  	    return err;
  	}
      }
      if (value.dptr == NULL) {
  	dav_propdb_metadata m = {
  	    DAV_DBVSN_MAJOR, DAV_DBVSN_MINOR, 0
  	};
  
  	if (propdb->db != NULL) {
  	    /*
  	     * If there is no METADATA key, then the database may be
  	     * from versions 0.9.0 .. 0.9.4 (which would be incompatible).
  	     * These can be identified by the presence of an NS_TABLE entry.
  	     */
  	    key.dptr = "NS_TABLE";
  	    key.dsize = 8;
  	    if ((*propdb->db_hooks->exists)(propdb->db, key)) {
  		(*propdb->db_hooks->close)(propdb->db);
  
  		/* call it a major version error */
  		return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
  				     DAV_ERR_PROP_BAD_MAJOR,
  				     "Prop database has the wrong major "
  				     "version number and cannot be used.");
  	    }
  	}
  
  	/* initialize a new metadata structure */
  	dav_set_bufsize(propdb->p, &propdb->ns_table, sizeof(m));
  	memcpy(propdb->ns_table.buf, &m, sizeof(m));
      }
      else {
  	dav_propdb_metadata m;
  
  	dav_set_bufsize(propdb->p, &propdb->ns_table, value.dsize);
  	memcpy(propdb->ns_table.buf, value.dptr, value.dsize);
  
  	memcpy(&m, value.dptr, sizeof(m));
  	if (m.major != DAV_DBVSN_MAJOR) {
  	    (*propdb->db_hooks->close)(propdb->db);
  
  	    return dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
  				 DAV_ERR_PROP_BAD_MAJOR,
  				 "Prop database has the wrong major "
  				 "version number and cannot be used.");
  	}
  	propdb->version = m.minor;
  	propdb->ns_count = ntohs(m.ns_count);
  
  	(*propdb->db_hooks->freedatum)(propdb->db, value);
      }
  
      return NULL;
  }
  
  dav_error *dav_open_propdb(request_rec *r, dav_lockdb *lockdb,
  			   dav_resource *resource,
  			   int ro,
  			   array_header * ns_xlate,
  			   dav_propdb **p_propdb)
  {
      dav_propdb *propdb = ap_pcalloc(r->pool, sizeof(*propdb));
      dav_error *err;
  
      *p_propdb = NULL;
  
  #if DAV_DEBUG
      if (resource->uri == NULL) {
  	return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "INTERNAL DESIGN ERROR: resource must define "
  			     "its URI.");
      }
  #endif
  
      propdb->version = DAV_DBVSN_MINOR;
      propdb->r = r;
      propdb->p = r->pool; /* ### get rid of this */
      propdb->resource = resource;
      propdb->ns_xlate = ns_xlate;
  
      propdb->db_hooks = DAV_GET_HOOKS_PROPDB(r);
      propdb->vsn_hooks = DAV_GET_HOOKS_VSN(r);
  
      propdb->liveprop = dav_get_provider_hooks(r, DAV_DYN_TYPE_LIVEPROP);
  
      propdb->lockdb = lockdb;
  
      if (!ro) {
  	propdb->deferred = 1;
      }
      else if ((err = dav_really_open_db(propdb, 1 /* ro */)) != NULL) {
  	return err;
      }
  
      /* ### what to do about closing the propdb on server failure? */
  
      *p_propdb = propdb;
      return NULL;
  }
  
  void dav_close_propdb(dav_propdb *propdb)
  {
      if (propdb->db == NULL)
  	return;
  
      if (propdb->ns_table_dirty) {
  	dav_propdb_metadata m;
  	dav_datum key;
  	dav_datum value;
  	dav_error *err;
  
  	key.dptr = DAV_GDBM_NS_KEY;
  	key.dsize = DAV_GDBM_NS_KEY_LEN;
  
  	value.dptr = propdb->ns_table.buf;
  	value.dsize = propdb->ns_table.cur_len;
  
  	/* fill in the metadata that we store into the prop db. */
  	m.major = DAV_DBVSN_MAJOR;
  	m.minor = propdb->version;	/* ### keep current minor version? */
  	m.ns_count = htons(propdb->ns_count);
  
  	memcpy(propdb->ns_table.buf, &m, sizeof(m));
  
  	err = (*propdb->db_hooks->store)(propdb->db, key, value);
  	/* ### what to do with the error? */
      }
  
      (*propdb->db_hooks->close)(propdb->db);
  }
  
  dav_get_props_result dav_get_allprops(dav_propdb *propdb, int getvals)
  {
      const dav_hooks_db *db_hooks = propdb->db_hooks;
      dav_text_header hdr = { 0 };
      dav_text_header hdr_ns = { 0 };
      dav_get_props_result result = { 0 };
      int found_resourcetype = 0;
      int found_contenttype = 0;
      int found_contentlang = 0;
      int unused_inserted;
      int i;
      const char * const * scan_uri;
      const dav_dyn_hooks *ddh;
  
      /* generate all the namespaces that are in the propdb */
      dav_get_propdb_xmlns(propdb, &hdr_ns);
  
      /* initialize the result with some start tags... */
      dav_text_append(propdb->p, &hdr,
  		    "<D:propstat>" DEBUG_CR
  		    "<D:prop>" DEBUG_CR);
  
      /* if there ARE properties, then scan them */
      if (propdb->db != NULL) {
  	dav_datum key;
  	int dav_id = dav_find_dav_id(propdb);
  
  	(void) (*db_hooks->firstkey)(propdb->db, &key);
  	while (key.dptr) {
  	    dav_datum prevkey;
  
  	    /* any keys with leading capital letters should be skipped
  	       (real keys start with a number or a colon) */
  	    if (*key.dptr >= 'A' && *key.dptr <= 'Z')
  		goto next_key;
  
  	    /*
  	    ** See if this is the <DAV:resourcetype> property. We need to
  	    ** know whether it was found (and therefore, whether to supply
  	    ** a default later).
  	    **
  	    ** We also look for <DAV:getcontenttype> and
  	    ** <DAV:getcontentlanguage>. If they are not stored as dead
  	    ** properties, then we need to perform a subrequest to get
  	    ** their values (if any).
  	    */
  	    if (dav_id != -1
  		&& *key.dptr != ':'
  		&& dav_id == atoi(key.dptr)) {
  
  		const char *colon;
  
  		/* find the colon */
  		if ( key.dptr[1] == ':' ) {
  		    colon = key.dptr + 1;
  		}
  		else {
  		    colon = strchr(key.dptr + 2, ':');
  		}
  
  		if (colon[1] == 'r'
  		    && strcmp(colon + 1, "resourcetype") == 0) {
  
  		    found_resourcetype = 1;
  		}
  		else if (colon[1] == 'g') {
  		    if (strcmp(colon + 1, "getcontenttype") == 0) {
  			found_contenttype = 1;
  		    }
  		    else if (strcmp(colon + 1, "getcontentlanguage") == 0) {
  			found_contentlang = 1;
  		    }
  		}
  	    }
  
  	    if (getvals) {
  		dav_datum value;
  
  		(void) (*db_hooks->fetch)(propdb->db, key, &value);
  		if (value.dptr == NULL) {
  		    /* ### anything better to do? */
  		    /* ### probably should enter a 500 error */
  		    goto next_key;
  		}
  
  		/* put the prop name and value into the result */
  		dav_append_prop(propdb, key.dptr, value.dptr, &hdr);
  
  		(*db_hooks->freedatum)(propdb->db, value);
  	    }
  	    else {
  		/* simple, empty element if a value isn't needed */
  		dav_append_prop(propdb, key.dptr, DAV_EMPTY_VALUE, &hdr);
  	    }
  
  	  next_key:
  	    prevkey = key;
  	    (void) (*db_hooks->nextkey)(propdb->db, &key);
  	    (*db_hooks->freedatum)(propdb->db, prevkey);
  	}
      }
  
      /* add namespaces for all the liveprop providers */
      for (i = 0, scan_uri = (const char * const *)dav_liveprop_uris->elts;
  	 i < dav_liveprop_uris->nelts;
  	 ++i, ++scan_uri)
  	dav_insert_xmlns(propdb->p, "lp", i, *scan_uri, &hdr_ns);
      
      /* ask the liveprop providers to insert their properties */
      for (ddh = propdb->liveprop; ddh != NULL; ddh = ddh->next) {
  	(*DAV_AS_HOOKS_LIVEPROP(ddh)->insert_all)(propdb->resource, getvals,
  						  ddh->ctx.ns_map,
  						  &hdr);
      }
  
      /* insert the standard properties */
      /* ### should be handling the return errors here */
      (void)dav_insert_coreprop(propdb,
  			      DAV_PROPID_CORE_supportedlock, "supportedlock",
  			      getvals, &hdr, &unused_inserted);
      (void)dav_insert_coreprop(propdb,
  			      DAV_PROPID_CORE_lockdiscovery, "lockdiscovery",
  			      getvals, &hdr, &unused_inserted);
  
      /* if the resourcetype wasn't stored, then prepare one */
      if (!found_resourcetype) {
  	/* ### should be handling the return error here */
  	(void)dav_insert_coreprop(propdb,
  				  DAV_PROPID_CORE_resourcetype, "resourcetype",
  				  getvals, &hdr, &unused_inserted);
      }
  
      /* if we didn't find these, then do the whole subreq thing. */
      if (!found_contenttype) {
  	/* ### should be handling the return error here */
  	(void)dav_insert_coreprop(propdb,
  				  DAV_PROPID_CORE_getcontenttype,
  				  "getcontenttype",
  				  getvals, &hdr, &unused_inserted);
      }
      if (!found_contentlang) {
  	/* ### should be handling the return error here */
  	(void)dav_insert_coreprop(propdb,
  				  DAV_PROPID_CORE_getcontentlanguage,
  				  "getcontentlanguage",
  				  getvals, &hdr, &unused_inserted);
      }
  
      /* terminate the result */
      dav_text_append(propdb->p, &hdr,
  		    "</D:prop>" DEBUG_CR
  		    "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
  		    "</D:propstat>" DEBUG_CR);
  
      result.propstats = hdr.first;
      result.xmlns = hdr_ns.first;
      return result;
  }
  
  dav_get_props_result dav_get_props(dav_propdb *propdb, dav_xml_doc *doc)
  {
      const dav_hooks_db *db_hooks = propdb->db_hooks;
      dav_xml_elem *elem = dav_find_child(doc->root, "prop");
      dav_text_header hdr_good = { 0 };
      dav_text_header hdr_bad = { 0 };
      dav_text_header hdr_ns = { 0 };
      int have_good = 0;
      dav_get_props_result result = { 0 };
      char *marks_input;
      char *marks_liveprop;
  
      /* ### NOTE: we should pass in TWO buffers -- one for keys, one for
         the marks */
  
      /* we will ALWAYS provide a "good" result, even if it is EMPTY */
      dav_text_append(propdb->p, &hdr_good,
  		    "<D:propstat>" DEBUG_CR
  		    "<D:prop>" DEBUG_CR);
  
      /* generate all the namespaces that are in the propdb */
      dav_get_propdb_xmlns(propdb, &hdr_ns);
  
      /* ### the marks should be in a buffer! */
      /* allocate zeroed-memory for the marks. These marks indicate which
         input namespaces we've generated into the output xmlns buffer */
      marks_input = ap_pcalloc(propdb->p, propdb->ns_xlate->nelts);
  
      /* same for the liveprops */
      marks_liveprop = ap_pcalloc(propdb->p, dav_liveprop_uris->nelts);
  
      for (elem = elem->first_child; elem; elem = elem->next) {
  	dav_datum key;
  	dav_datum value = { 0 };
  
  	/*
  	** Note: the key may be NULL if we have no properties that are in
  	** a namespace that matches the requested prop's namespace.
  	*/
  	key = dav_gdbm_key(propdb, elem);
  
  	/* fetch IF we have a db and a key. otherwise, value is NULL */
  	if (propdb->db != NULL && key.dptr != NULL) {
  	    (void) (*db_hooks->fetch)(propdb->db, key, &value);
  	}
  
  	/*
  	** If we did not find the property in the database, then it may
  	** be a liveprop that we can handle specially.
  	*/
  	if (value.dptr == NULL) {
  	    dav_error *err;
  	    int inserted;
  
  	    /* cache the propid; dav_get_props() could be called many times */
  	    if (elem->propid == 0)
  		dav_find_liveprop(propdb, elem);
  
  	    /* insert the property. returns 1 if an insertion was done. */
  	    if ((err = dav_insert_liveprop(propdb, elem, 1, &hdr_good,
  					   &inserted)) != NULL) {
  		/* ### need to propagate the error to the caller... */
  		/* ### skip it for now, as if nothing was inserted */
  	    }
  	    if (inserted) {
  		have_good = 1;
  
  		/*
  		** Add the liveprop's namespace URIs. Note that provider==NULL
  		** for core properties.
  		*/
  		if (elem->provider != NULL) {
  		    const char * const * scan_ns_uri;
  		    const int * scan_ns;
  
  		    for (scan_ns_uri = elem->provider->namespace_uris,
  			     scan_ns = elem->ns_map;
  			 *scan_ns_uri != NULL;
  			 ++scan_ns_uri, ++scan_ns) {
  
  			dav_add_marked_xmlns(propdb, marks_liveprop, *scan_ns,
  					     dav_liveprop_uris, "lp", &hdr_ns);
  		    }
  		}
  
  		continue;
  	    }
  	}
  
  	if (value.dptr == NULL) {
  	    /* not found. add a record to the "bad" propstats */
  
  	    /* make sure we've started our "bad" propstat */
  	    if (hdr_bad.first == NULL) {
  		dav_text_append(propdb->p, &hdr_bad,
  				"<D:propstat>" DEBUG_CR
  				"<D:prop>" DEBUG_CR);
  	    }
  
  	    /* note: key.dptr may be NULL if the propdb doesn't have an
  	       equivalent namespace stored */
  	    if (key.dptr == NULL) {
  		const char *s;
  
  		if (elem->ns == DAV_NS_NONE) {
  		    /*
  		     * elem has a prefix already (xml...:name) or the elem
  		     * simply has no namespace.
  		     */
  		    s = ap_psprintf(propdb->p, "<%s/>" DEBUG_CR, elem->name);
  		}
  		else {
  		    /* ensure that an xmlns is generated for the
  		       input namespace */
  		    dav_add_marked_xmlns(propdb, marks_input, elem->ns,
  					 propdb->ns_xlate, "i", &hdr_ns);
  		    s = ap_psprintf(propdb->p, "<i%d:%s/>" DEBUG_CR,
  				    elem->ns, elem->name);
  		}
  		dav_text_append(propdb->p, &hdr_bad, s);
  	    }
  	    else {
  		/* add in the bad prop using our namespace decl */
  		dav_append_prop(propdb, key.dptr, DAV_EMPTY_VALUE, &hdr_bad);
  	    }
  	}
  	else {
  	    /* found it. put the value into the "good" propstats */
  
  	    have_good = 1;
  
  	    dav_append_prop(propdb, key.dptr, value.dptr, &hdr_good);
  
  	    (*db_hooks->freedatum)(propdb->db, value);
  	}
      }
  
      dav_text_append(propdb->p, &hdr_good,
  		    "</D:prop>" DEBUG_CR
  		    "<D:status>HTTP/1.1 200 OK</D:status>" DEBUG_CR
  		    "</D:propstat>" DEBUG_CR);
  
      /* default to start with the good */
      result.propstats = hdr_good.first;
  
      /* we may not have any "bad" results */
      if (hdr_bad.first != NULL) {
  	dav_text_append(propdb->p, &hdr_bad,
  			"</D:prop>" DEBUG_CR
  			"<D:status>HTTP/1.1 404 Not Found</D:status>" DEBUG_CR
  			"</D:propstat>" DEBUG_CR);
  
  	/* if there are no good props, then just return the bad */
  	if (!have_good) {
  	    result.propstats = hdr_bad.first;
  	}
  	else {
  	    /* hook the bad propstat to the end of the good one */
  	    hdr_good.last->next = hdr_bad.first;
  	}
      }
  
      result.xmlns = hdr_ns.first;
      return result;
  }
  
  void dav_prop_validate(dav_prop_ctx *ctx)
  {
      dav_propdb *propdb = ctx->propdb;
      dav_xml_elem *prop = ctx->prop;
  
      /*
      ** Check to see if this is a live property, and fill the fields
      ** in the XML elem, as appropriate.
      **
      ** Verify that the property is read/write. If not, then it cannot
      ** be SET or DELETEd.
      */
      if (prop->propid == 0) {
  	dav_find_liveprop(propdb, prop);
  
  	/* it's a liveprop if a provider was found */
  	/* ### actually the "core" props should really be liveprops, but
  	   ### there is no "provider" for those and the r/w props are
  	   ### treated as dead props anyhow */
  	ctx->is_liveprop = prop->provider != NULL;
      }
  
      if (!dav_rw_liveprop(propdb, prop->propid)) {
  	ctx->err = dav_new_error(propdb->p, HTTP_CONFLICT,
  				 DAV_ERR_PROP_READONLY,
  				 "Property is read-only.");
  	return;
      }
  
      if (ctx->is_liveprop) {
  	int defer_to_dead = 0;
  
  	ctx->err = (*prop->provider->patch_validate)(propdb->resource,
  						     prop, ctx->operation,
  						     &ctx->liveprop_ctx,
  						     &defer_to_dead);
  	if (ctx->err != NULL || !defer_to_dead)
  	    return;
  
  	/* clear is_liveprop -- act as a dead prop now */
  	ctx->is_liveprop = 0;
      }
  
      /*
      ** The property is supposed to be stored into the dead-property
      ** database. Make sure the thing is truly open (and writeable).
      */
      if (propdb->deferred
  	&& (ctx->err = dav_really_open_db(propdb, 0 /* ro */)) != NULL) {
  	return;
      }
  
      /*
      ** There should be an open, writable database in here!
      **
      ** Note: the database would be NULL if it was opened readonly and it
      **       did not exist.
      */
      if (propdb->db == NULL) {
  	ctx->err = dav_new_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
  				 DAV_ERR_PROP_NO_DATABASE,
  				 "Attempted to set/remove a property "
  				 "without a valid, open, read/write "
  				 "property database.");
  	return;
      }
  
      if (ctx->operation == DAV_PROP_OP_SET) {
  	/*
  	** Prep the element => propdb namespace index mapping, inserting
  	** namespace URIs into the propdb that don't exist.
  	*/
  	dav_prep_ns_map(propdb, 1);
      }
      else if (ctx->operation == DAV_PROP_OP_DELETE) {
  	/*
  	** There are no checks to perform here. If a property exists, then
  	** we will delete it. If it does not exist, then it does not matter
  	** (see S12.13.1).
  	**
  	** Note that if a property does not exist, that does not rule out
  	** that a SET will occur during this PROPPATCH (thusly creating it).
  	*/
      }
  }
  
  void dav_prop_exec(dav_prop_ctx *ctx)
  {
      dav_propdb *propdb = ctx->propdb;
      dav_error *err = NULL;
      dav_rollback_item *rollback;
  
      rollback = ap_pcalloc(propdb->p, sizeof(*rollback));
      ctx->rollback = rollback;
  
      if (ctx->is_liveprop) {
  	err = (*ctx->prop->provider->patch_exec)(propdb->resource,
  						 ctx->prop, ctx->operation,
  						 ctx->liveprop_ctx,
  						 &ctx->rollback->liveprop);
      }
      else {
  	dav_datum key;
  
  	/* we're going to need the key for all operations */
  	key = dav_gdbm_key(propdb, ctx->prop);
  
  	/* save the old value so that we can do a rollback. */
  	rollback->key = key;
  	if ((err = (*propdb->db_hooks->fetch)(propdb->db, key,
  					      &rollback->value)) != NULL)
  	    goto error;
  
  	if (ctx->operation == DAV_PROP_OP_SET) {
  
  	    dav_datum value;
  
  	    /* Note: propdb->ns_map was set in dav_prop_validate() */
  
  	    /* quote all the values in the element */
  	    dav_quote_xml_elem(propdb->p, ctx->prop);
  
  	    /* generate a text blob for the xml:lang plus the contents */
  	    dav_xml2text(propdb->p, ctx->prop, DAV_X2T_LANG_INNER, NULL,
  			 propdb->ns_map,
  			 (const char **)&value.dptr, &value.dsize);
  
  	    err = (*propdb->db_hooks->store)(propdb->db, key, value);
  
  	    /*
  	    ** If an error occurred, then assume that we didn't change the
  	    ** value. Remove the rollback item so that we don't try to set
  	    ** its value during the rollback.
  	    */
  	}
  	else if (ctx->operation == DAV_PROP_OP_DELETE) {
  
  	    /*
  	    ** Delete the property. Ignore errors -- the property is there, or
  	    ** we are deleting it for a second time.
  	    */
  	    /* ### but what about other errors? */
  	    (void) (*propdb->db_hooks->remove)(propdb->db, key);
  	}
      }
  
    error:
      /* push a more specific error here */
      if (err != NULL) {
  	/*
  	** Use HTTP_INTERNAL_SERVER_ERROR because we shouldn't have seen
  	** any errors at this point.
  	*/
  	ctx->err = dav_push_error(propdb->p, HTTP_INTERNAL_SERVER_ERROR,
  				  DAV_ERR_PROP_EXEC,
  				  "Could not execute PROPPATCH.", err);
      }
  }
  
  void dav_prop_commit(dav_prop_ctx *ctx)
  {
      /*
      ** Note that a commit implies ctx->err is NULL. The caller should assume
      ** a status of HTTP_OK for this case.
      */
  
      if (ctx->is_liveprop) {
  	(*ctx->prop->provider->patch_commit)(ctx->propdb->resource,
  					     ctx->operation,
  					     ctx->liveprop_ctx,
  					     ctx->rollback->liveprop);
      }
  }
  
  void dav_prop_rollback(dav_prop_ctx *ctx)
  {
      dav_error *err = NULL;
  
      /* do nothing if there is no rollback information. */
      if (ctx->rollback == NULL)
  	return;
  
      /*
      ** ### if we have an error, and a rollback occurs, then the namespace
      ** ### mods should not happen at all. Basically, the namespace management
      ** ### is simply a bitch.
      */
  
      if (ctx->is_liveprop) {
  	err = (*ctx->prop->provider->patch_rollback)(ctx->propdb->resource,
  						     ctx->operation,
  						     ctx->liveprop_ctx,
  						     ctx->rollback->liveprop);
      }
      else if (ctx->rollback->value.dptr == NULL) {
  	/* don't fail if the thing isn't really there */
          /* ### but what about other errors? */
  	(void) (*ctx->propdb->db_hooks->remove)(ctx->propdb->db,
  						ctx->rollback->key);
      }
      else {
  	err = (*ctx->propdb->db_hooks->store)(ctx->propdb->db,
  					      ctx->rollback->key,
  					      ctx->rollback->value);
      }
  
      if (err != NULL) {
  	if (ctx->err == NULL)
  	    ctx->err = err;
  	else {
  	    dav_error *scan = err;
  
  	    /* hook previous errors at the end of the rollback error */
  	    while (scan->prev != NULL)
  		scan = scan->prev;
  	    scan->prev = ctx->err;
  	    ctx->err = err;
  	}
      }
  }
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/util.c
  
  Index: util.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV extension module for Apache 1.3.*
  **  - various utilities, repository-independent
  **
  ** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
  */
  
  #include "mod_dav.h"
  
  #include "http_request.h"
  #include "http_config.h"
  #include "http_vhost.h"
  #include "http_log.h"
  #include "http_protocol.h"
  
  
  dav_error *dav_new_error(pool *p, int status, int error_id, const char *desc)
  {
      int save_errno = errno;
      dav_error *err = ap_pcalloc(p, sizeof(*err));
  
      /* DBG3("dav_new_error: %d %d %s", status, error_id, desc ? desc : "(no desc)"); */
  
      err->status = status;
      err->error_id = error_id;
      err->desc = desc;
      err->save_errno = save_errno;
  
      return err;
  }
  
  dav_error *dav_push_error(pool *p, int status, int error_id, const char *desc,
  			  dav_error *prev)
  {
      dav_error *err = ap_pcalloc(p, sizeof(*err));
  
      err->status = status;
      err->error_id = error_id;
      err->desc = desc;
      err->prev = prev;
  
      return err;
  }
  
  void dav_text_append(pool * p, dav_text_header *hdr, const char *text)
  {
      dav_text *t = ap_palloc(p, sizeof(*t));
  
      t->text = text;
      t->next = NULL;
  
      if (hdr->first == NULL) {
  	/* no text elements yet */
  	hdr->first = hdr->last = t;
      }
      else {
  	/* append to the last text element */
  	hdr->last->next = t;
  	hdr->last = t;
      }
  }
  
  void dav_check_bufsize(pool * p, dav_buffer *pbuf, size_t extra_needed)
  {
      /* grow the buffer if necessary */
      if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
  	char *newbuf;
  
  	pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD;
  	newbuf = ap_palloc(p, pbuf->alloc_len);
  	memcpy(newbuf, pbuf->buf, pbuf->cur_len);
  	pbuf->buf = newbuf;
      }
  }
  
  void dav_set_bufsize(pool * p, dav_buffer *pbuf, size_t size)
  {
      /* NOTE: this does not retain prior contents */
  
      /* NOTE: this function is used to init the first pointer, too, since
         the PAD will be larger than alloc_len (0) for zeroed structures */
  
      /* grow if we don't have enough for the requested size plus padding */
      if (size + DAV_BUFFER_PAD > pbuf->alloc_len) {
  	/* set the new length; min of MINSIZE */
  	pbuf->alloc_len = size + DAV_BUFFER_PAD;
  	if (pbuf->alloc_len < DAV_BUFFER_MINSIZE)
  	    pbuf->alloc_len = DAV_BUFFER_MINSIZE;
  
  	pbuf->buf = ap_palloc(p, pbuf->alloc_len);
      }
      pbuf->cur_len = size;
  }
  
  
  /* initialize a buffer and copy the specified (null-term'd) string into it */
  void dav_buffer_init(pool *p, dav_buffer *pbuf, const char *str)
  {
      dav_set_bufsize(p, pbuf, strlen(str));
      memcpy(pbuf->buf, str, pbuf->cur_len + 1);
  }
  
  /* append a string to the end of the buffer, adjust length */
  void dav_buffer_append(pool *p, dav_buffer *pbuf, const char *str)
  {
      size_t len = strlen(str);
  
      dav_check_bufsize(p, pbuf, len + 1);
      memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
      pbuf->cur_len += len;
  }
  
  /* place a string on the end of the buffer, do NOT adjust length */
  void dav_buffer_place(pool *p, dav_buffer *pbuf, const char *str)
  {
      size_t len = strlen(str);
  
      dav_check_bufsize(p, pbuf, len + 1);
      memcpy(pbuf->buf + pbuf->cur_len, str, len + 1);
  }
  
  /* place some memory on the end of a buffer; do NOT adjust length */
  void dav_buffer_place_mem(pool *p, dav_buffer *pbuf, const void *mem,
                            size_t amt, size_t pad)
  {
      dav_check_bufsize(p, pbuf, amt + pad);
      memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
  }
  
  
  #if APACHE_RELEASE == 10304100
  /* ### this code can be used for 1.3.4 installations. use this function
   * ### instead of ap_sub_req_method_uri()
   */
  /*
   * ### don't look at this code.
   * ### it is a Crime Against All That is Right and Good
   */
  #include "http_core.h"		/* for SATISFY_* */
  static request_rec *gross_hack(const char *new_file, const request_rec * r)
  {
      request_rec *rnew = ap_sub_req_lookup_uri(new_file, r);
      int res;
  
      /* ### these aren't exported properly from the headers */
      extern int ap_check_access(request_rec *);	/* check access on non-auth basis */
      extern int ap_check_user_id(request_rec *);		/* obtain valid username from client auth */
      extern int ap_check_auth(request_rec *);	/* check (validated) user is authorized here */
  
      if (rnew->status != HTTP_OK)
  	return rnew;
  
      /* re-run portions with a modified method */
      rnew->method = r->method;
      rnew->method_number = r->method_number;
  
      if ((ap_satisfies(rnew) == SATISFY_ALL
  	 || ap_satisfies(rnew) == SATISFY_NOSPEC)
  	? ((res = ap_check_access(rnew))
  	   || (ap_some_auth_required(rnew)
  	       && ((res = ap_check_user_id(rnew))
  		   || (res = ap_check_auth(rnew)))))
  	: ((res = ap_check_access(rnew))
  	   && (!ap_some_auth_required(rnew)
  	       || ((res = ap_check_user_id(rnew))
  		   || (res = ap_check_auth(rnew)))))
  	) {
  	rnew->status = res;
      }
  
      return rnew;
  }
  #endif /* APACHE_RELEASE == 10304100 */
  
  /*
  ** dav_lookup_uri()
  **
  ** Extension for ap_sub_req_lookup_uri() which can't handle absolute
  ** URIs properly.
  **
  ** If NULL is returned, then an error occurred with parsing the URI or
  ** the URI does not match the current server.
  */
  dav_lookup_result dav_lookup_uri(const char *uri, request_rec * r)
  {
      dav_lookup_result result = { 0 };
      const char *scheme;
      unsigned short port = ntohs(r->connection->local_addr.sin_port);
      uri_components comp;
      char *new_file;
      const char *domain;
  
      /* first thing to do is parse the URI into various components */
      if (ap_parse_uri_components(r->pool, uri, &comp) != HTTP_OK) {
  	result.err.status = HTTP_BAD_REQUEST;
  	result.err.desc = "Invalid syntax in Destination URI.";
  	return result;
      }
  
      /* the URI must be an absoluteURI (WEBDAV S9.3) */
      if (comp.scheme == NULL) {
  	result.err.status = HTTP_BAD_REQUEST;
  	result.err.desc = "Destination URI must be an absolute URI.";
  	return result;
      }
  
      /* ### not sure this works if the current request came in via https: */
      scheme = r->parsed_uri.scheme;
      if (scheme == NULL)
  	scheme = ap_http_method(r);
  
      /* insert a port if the URI did not contain one */
      if (comp.port == 0)
  	comp.port = ap_default_port_for_scheme(comp.scheme);
  
      /* now, verify that the URI uses the same scheme as the current request.
         the port, must match our port.
         the URI must not have a query (args) or a fragment
       */
      if (strcasecmp(comp.scheme, scheme) != 0 ||
  	comp.port != port) {
  	result.err.status = HTTP_BAD_GATEWAY;
  	result.err.desc = ap_psprintf(r->pool,
  				      "Destination URI refers to different "
  				      "scheme or port (%s://hostname:%d)\n"
  				      "(want: %s://hostname:%d)",
  				      comp.scheme ? comp.scheme : scheme,
  				      comp.port ? comp.port : port,
  				      scheme, port);
  	return result;
      }
      if (comp.query != NULL || comp.fragment != NULL) {
  	result.err.status = HTTP_BAD_REQUEST;
  	result.err.desc =
  	    "Destination URI contains invalid components "
  	    "(a query or a fragment).";
  	return result;
      }
  
      /* we have verified the scheme, port, and general structure */
  
      /*
      ** Hrm.  IE5 will pass unqualified hostnames for both the 
      ** Host: and Destination: headers.  This breaks the
      ** http_vhost.c::matches_aliases function.
      **
      ** For now, qualify unqualified comp.hostnames with
      ** r->server->server_hostname.
      **
      ** ### this is a big hack. Apache should provide a better way.
      ** ### maybe the admin should list the unqualified hosts in a
      ** ### <ServerAlias> block?
      */
      if (strrchr(comp.hostname, '.') == NULL &&
  	(domain = strchr(r->server->server_hostname, '.')) != NULL) {
  	comp.hostname = ap_pstrcat(r->pool, comp.hostname, domain, NULL);
      }
  
      /* now, if a hostname was provided, then verify that it represents the
         same server as the current connection. note that we just use our
         port, since we've verified the URI matches ours */
      if (comp.hostname != NULL &&
  	!ap_matches_request_vhost(r, comp.hostname, port)) {
  	result.err.status = HTTP_BAD_GATEWAY;
  	result.err.desc = "Destination URI refers to a different server.";
  	return result;
      }
  
      /* we have verified that the requested URI denotes the same server as
         the current request. Therefore, we can use ap_sub_req_lookup_uri() */
  
      /* reconstruct a URI as just the path */
      new_file = ap_unparse_uri_components(r->pool, &comp, UNP_OMITSITEPART);
  
      /*
       * Lookup the URI and return the sub-request. Note that we use the
       * same HTTP method on the destination. This allows the destination
       * to apply appropriate restrictions (e.g. readonly).
       */
  #if APACHE_RELEASE == 10304100
      result.rnew = gross_hack(new_file, r);
  #else
      result.rnew = ap_sub_req_method_uri(r->method, new_file, r);
  #endif
  
      return result;
  }
  
  /* ---------------------------------------------------------------
  **
  ** XML UTILITY FUNCTIONS
  */
  
  /* validate that the root element uses a given DAV: tagname (TRUE==valid) */
  int dav_validate_root(const dav_xml_doc *doc, const char *tagname)
  {
      return doc->root &&
  	doc->root->ns == DAV_NS_DAV_ID &&
  	strcmp(doc->root->name, tagname) == 0;
  }
  
  /* find and return the (unique) child with a given DAV: tagname */
  dav_xml_elem *dav_find_child(const dav_xml_elem *elem, const char *tagname)
  {
      dav_xml_elem *child = elem->first_child;
  
      for (; child; child = child->next)
  	if (child->ns == DAV_NS_DAV_ID && !strcmp(child->name, tagname))
  	    return child;
      return NULL;
  }
  
  
  /* how many characters for the given integer? */
  #define DAV_NS_LEN(ns)	((ns) < 10 ? 1 : (ns) < 100 ? 2 : (ns) < 1000 ? 3 : \
  			 (ns) < 10000 ? 4 : (ns) < 100000 ? 5 : \
  			 (ns) < 1000000 ? 6 : (ns) < 10000000 ? 7 : \
  			 (ns) < 100000000 ? 8 : (ns) < 1000000000 ? 9 : 10)
  
  static int dav_text_size(const dav_text *t)
  {
      int size = 0;
  
      for (; t; t = t->next)
  	size += strlen(t->text);
      return size;
  }
  
  static size_t dav_elem_size(const dav_xml_elem *elem, int style,
                              array_header *namespaces, int *ns_map)
  {
      size_t size;
  
      if (style == DAV_X2T_FULL || style == DAV_X2T_FULL_NS_LANG) {
  	const dav_xml_attr *attr;
  
  	size = 0;
  
  	if (style == DAV_X2T_FULL_NS_LANG) {
  	    int i;
  
  	    /*
  	    ** The outer element will contain xmlns:ns%d="%s" attributes
  	    ** and an xml:lang attribute, if applicable.
  	    */
  
  	    for (i = namespaces->nelts; i--;) {
  		/* compute size of: ' xmlns:ns%d="%s"' */
  		size += (9 + DAV_NS_LEN(i) + 2 +
  			 strlen(DAV_GET_URI_ITEM(namespaces, i)) + 1);
  	    }
  
  	    if (elem->lang != NULL) {
  		/* compute size of: ' xml:lang="%s"' */
  		size += 11 + strlen(elem->lang) + 1;
  	    }
  	}
  
  	if (elem->ns == DAV_NS_NONE) {
  	    /* compute size of: <%s> */
  	    size += 1 + strlen(elem->name) + 1;
  	}
  	else {
  	    int ns = ns_map ? ns_map[elem->ns] : elem->ns;
  
  	    /* compute size of: <ns%d:%s> */
  	    size += 3 + DAV_NS_LEN(ns) + 1 + strlen(elem->name) + 1;
  	}
  
  	if (DAV_ELEM_IS_EMPTY(elem)) {
  	    /* insert a closing "/" */
  	    size += 1;
  	}
  	else {
  	    /*
  	     * two of above plus "/":
  	     *     <ns%d:%s> ... </ns%d:%s>
  	     * OR  <%s> ... </%s>
  	     */
  	    size = 2 * size + 1;
  	}
  
  	for (attr = elem->attr; attr; attr = attr->next) {
  	    if (attr->ns == DAV_NS_NONE) {
  		/* compute size of: ' %s="%s"' */
  		size += 1 + strlen(attr->name) + 2 + strlen(attr->value) + 1;
  	    }
  	    else {
  		/* compute size of: ' ns%d:%s="%s"' */
  		size += 3 + DAV_NS_LEN(attr->ns) + 1 + strlen(attr->name) + 2 + strlen(attr->value) + 1;
  	    }
  	}
  
  	/*
  	** If the element has an xml:lang value that is *different* from
  	** its parent, then add the thing in: ' xml:lang="%s"'.
  	**
  	** NOTE: we take advantage of the pointer equality established by
  	** the parsing for "inheriting" the xml:lang values from parents.
  	*/
  	if (elem->lang != NULL &&
  	    (elem->parent == NULL || elem->lang != elem->parent->lang)) {
  	    size += 11 + strlen(elem->lang) + 1;
  	}
      }
      else if (style == DAV_X2T_LANG_INNER) {
  	/*
  	 * This style prepends the xml:lang value plus a null terminator.
  	 * If a lang value is not present, then we insert a null term.
  	 */
  	size = elem->lang ? strlen(elem->lang) + 1 : 1;
      }
      else
  	size = 0;
  
      size += dav_text_size(elem->first_cdata.first);
  
      for (elem = elem->first_child; elem; elem = elem->next) {
  	/* the size of the child element plus the CDATA that follows it */
  	size += (dav_elem_size(elem, DAV_X2T_FULL, NULL, ns_map) +
  		 dav_text_size(elem->following_cdata.first));
      }
  
      return size;
  }
  
  static char *dav_write_text(char *s, const dav_text *t)
  {
      for (; t; t = t->next) {
  	size_t len = strlen(t->text);
  	memcpy(s, t->text, len);
  	s += len;
      }
      return s;
  }
  
  static char *dav_write_elem(char *s, const dav_xml_elem *elem, int style,
  			    array_header *namespaces, int *ns_map)
  {
      const dav_xml_elem *child;
      size_t len;
      int ns;
  
      if (style == DAV_X2T_FULL || style == DAV_X2T_FULL_NS_LANG) {
  	int empty = DAV_ELEM_IS_EMPTY(elem);
  	const dav_xml_attr *attr;
  
  	if (elem->ns == DAV_NS_NONE) {
  	    len = sprintf(s, "<%s", elem->name);
  	}
  	else {
  	    ns = ns_map ? ns_map[elem->ns] : elem->ns;
  	    len = sprintf(s, "<ns%d:%s", ns, elem->name);
  	}
  	s += len;
  
  	for (attr = elem->attr; attr; attr = attr->next) {
  	    if (attr->ns == DAV_NS_NONE)
  		len = sprintf(s, " %s=\"%s\"", attr->name, attr->value);
  	    else
  		len = sprintf(s, " ns%d:%s=\"%s\"", attr->ns, attr->name, attr->value);
  	    s += len;
  	}
  
  	/* add the xml:lang value if necessary */
  	if (elem->lang != NULL &&
  	    (style == DAV_X2T_FULL_NS_LANG ||
  	     elem->parent == NULL ||
  	     elem->lang != elem->parent->lang)) {
  	    len = sprintf(s, " xml:lang=\"%s\"", elem->lang);
  	    s += len;
  	}
  
  	/* add namespace definitions, if required */
  	if (style == DAV_X2T_FULL_NS_LANG) {
  	    int i;
  
  	    for (i = namespaces->nelts; i--;) {
  		len = sprintf(s, " xmlns:ns%d=\"%s\"", i,
  			      DAV_GET_URI_ITEM(namespaces, i));
  		s += len;
  	    }
  	}
  
  	/* no more to do. close it up and go. */
  	if (empty) {
  	    *s++ = '/';
  	    *s++ = '>';
  	    return s;
  	}
  
  	/* just close it */
  	*s++ = '>';
      }
      else if (style == DAV_X2T_LANG_INNER) {
  	/* prepend the xml:lang value */
  	if (elem->lang != NULL) {
  	    len = strlen(elem->lang);
  	    memcpy(s, elem->lang, len);
  	    s += len;
  	}
  	*s++ = '\0';
      }
  
      s = dav_write_text(s, elem->first_cdata.first);
  
      for (child = elem->first_child; child; child = child->next) {
  	s = dav_write_elem(s, child, DAV_X2T_FULL, NULL, ns_map);
  	s = dav_write_text(s, child->following_cdata.first);
      }
  
      if (style == DAV_X2T_FULL || style == DAV_X2T_FULL_NS_LANG) {
  	if (elem->ns == DAV_NS_NONE) {
  	    len = sprintf(s, "</%s>", elem->name);
  	}
  	else {
  	    ns = ns_map ? ns_map[elem->ns] : elem->ns;
  	    len = sprintf(s, "</ns%d:%s>", ns, elem->name);
  	}
  	s += len;
      }
  
      return s;
  }
  
  /* convert an element to a text string */
  void dav_xml2text(pool * p,
  		  const dav_xml_elem *elem,
  		  int style,
  		  array_header *namespaces,
  		  int *ns_map,
  		  const char **pbuf,
  		  size_t *psize)
  {
      /* get the exact size, plus a null terminator */
      size_t size = dav_elem_size(elem, style, namespaces, ns_map) + 1;
      char *s = ap_palloc(p, size);
  
      (void) dav_write_elem(s, elem, style, namespaces, ns_map);
      s[size - 1] = '\0';
  
      *pbuf = s;
      if (psize)
  	*psize = size;
  }
  
  const char *dav_empty_elem(pool * p, const dav_xml_elem *elem)
  {
      if (elem->ns == DAV_NS_NONE) {
  	/*
  	 * The prefix (xml...) is already within the prop name, or
  	 * the element simply has no prefix.
  	 */
  	return ap_psprintf(p, "<%s/>" DEBUG_CR, elem->name);
      }
  
      return ap_psprintf(p, "<ns%d:%s/>" DEBUG_CR, elem->ns, elem->name);
  }
  
  /*
  ** dav_quote_string: quote an XML string
  **
  ** Replace '<', '>', and '&' with '&lt;', '&gt;', and '&amp;'.
  ** If quotes is true, then replace '"' with '&quot;'.
  **
  ** quotes is typically set to true for XML strings that will occur within
  ** double quotes -- attribute values.
  */
  const char * dav_quote_string(pool *p, const char *s, int quotes)
  {
      const char *scan;
      int len = 0;
      int extra = 0;
      char *qstr;
      char *qscan;
      char c;
  
      for (scan = s; (c = *scan) != '\0'; ++scan, ++len) {
  	if (c == '<' || c == '>')
  	    extra += 3;		/* &lt; or &gt; */
  	else if (c == '&')
  	    extra += 4;		/* &amp; */
  	else if (quotes && c == '"')
  	    extra += 5;		/* &quot; */
      }
  
      /* nothing to do? */
      if (extra == 0)
  	return s;
  
      qstr = ap_palloc(p, len + extra + 1);
      for (scan = s, qscan = qstr; (c = *scan) != '\0'; ++scan) {
  	if (c == '<') {
  	    *qscan++ = '&';
  	    *qscan++ = 'l';
  	    *qscan++ = 't';
  	    *qscan++ = ';';
  	}
  	else if (c == '>') {
  	    *qscan++ = '&';
  	    *qscan++ = 'g';
  	    *qscan++ = 't';
  	    *qscan++ = ';';
  	}
  	else if (c == '&') {
  	    *qscan++ = '&';
  	    *qscan++ = 'a';
  	    *qscan++ = 'm';
  	    *qscan++ = 'p';
  	    *qscan++ = ';';
  	}
  	else if (quotes && c == '"') {
  	    *qscan++ = '&';
  	    *qscan++ = 'q';
  	    *qscan++ = 'u';
  	    *qscan++ = 'o';
  	    *qscan++ = 't';
  	    *qscan++ = ';';
  	}
  	else {
  	    *qscan++ = c;
  	}
      }
  
      *qscan = '\0';
      return qstr;
  }
  
  void dav_quote_xml_elem(pool *p, dav_xml_elem *elem)
  {
      dav_text *scan_txt;
      dav_xml_attr *scan_attr;
      dav_xml_elem *scan_elem;
  
      /* convert the element's text */
      for (scan_txt = elem->first_cdata.first;
  	 scan_txt != NULL;
  	 scan_txt = scan_txt->next) {
  	scan_txt->text = dav_quote_string(p, scan_txt->text, 0);
      }
      for (scan_txt = elem->following_cdata.first;
  	 scan_txt != NULL;
  	 scan_txt = scan_txt->next) {
  	scan_txt->text = dav_quote_string(p, scan_txt->text, 0);
      }
  
      /* convert the attribute values */
      for (scan_attr = elem->attr;
  	 scan_attr != NULL;
  	 scan_attr = scan_attr->next) {
  	scan_attr->value = dav_quote_string(p, scan_attr->value, 1);
      }
  
      /* convert the child elements */
      for (scan_elem = elem->first_child;
  	 scan_elem != NULL;
  	 scan_elem = scan_elem->next) {
  	dav_quote_xml_elem(p, scan_elem);
      }
  }
  
  /* ---------------------------------------------------------------
  **
  ** Timeout header processing
  **
  */
  
  /* dav_get_timeout:  If the Timeout: header exists, return a time_t
   *    when this lock is expected to expire.  Otherwise, return
   *    a time_t of DAV_TIMEOUT_INFINITE.
   *
   *    It's unclear if DAV clients are required to understand
   *    Seconds-xxx and Infinity time values.  We assume that they do.
   *    In addition, for now, that's all we understand, too.
   */
  time_t dav_get_timeout(request_rec *r)
  {
      time_t now, expires = DAV_TIMEOUT_INFINITE;
  
      const char *timeout_const = ap_table_get(r->headers_in, "Timeout");
      const char *timeout = ap_pstrdup(r->pool, timeout_const), *val;
  
      if (timeout == NULL)
  	return DAV_TIMEOUT_INFINITE;
  
      /* Use the first thing we understand, or infinity if
       * we don't understand anything.
       */
  
      while ((val = ap_getword_white(r->pool, &timeout)) && strlen(val)) {
  	if (!strncmp(val, "Infinite", 8)) {
  	    return DAV_TIMEOUT_INFINITE;
  	}
  
  	if (!strncmp(val, "Second-", 7)) {
  	    val += 7;
  	    /* ### We need to handle overflow better:
  	     * ### timeout will be <= 2^32 - 1
  	     */
  	    expires = atol(val);
  	    now     = time(NULL);
  	    return now + expires;
  	}
      }
  
      return DAV_TIMEOUT_INFINITE;
  }
  
  /* ---------------------------------------------------------------
  **
  ** If Header processing
  **
  */
  
  /* add_if_resource returns a new if_header, linking it to next_ih.
   */
  static dav_if_header *dav_add_if_resource(pool *p, dav_if_header *next_ih,
  					  const char *uri, size_t uri_len)
  {
      dav_if_header *ih;
  
      if ((ih = ap_pcalloc(p, sizeof(*ih))) == NULL)
  	return NULL;
  
      ih->uri = uri;
      ih->uri_len = uri_len;
      ih->next = next_ih;
  
      return ih;
  }
  
  /* add_if_state adds a condition to an if_header.
   */
  static dav_error * dav_add_if_state(pool *p, dav_if_header *ih,
  				    const char *state_token,
  				    dav_if_state_type t, int condition,
  				    const dav_hooks_locks *locks_hooks)
  {
      dav_if_state_list *new_sl;
  
      new_sl = ap_pcalloc(p, sizeof(*new_sl));
  
      new_sl->condition = condition;
      new_sl->type      = t;
      
      if (t == dav_if_opaquelock) {
  	dav_error *err;
  
  	if ((err = (*locks_hooks->parse_locktoken)(p, state_token,
  						   &new_sl->locktoken)) != NULL) {
  	    /* ### maybe add a higher-level description */
  	    return err;
  	}
      }
      else
  	new_sl->etag = state_token;
  
      new_sl->next = ih->state;
      ih->state = new_sl;
  
      return NULL;
  }
  
  /* fetch_next_token returns the substring from str+1
   * to the next occurence of char term, or \0, whichever
   * occurs first.  Leading whitespace is ignored.
   */
  static char *dav_fetch_next_token(char **str, char term)
  {
      char *sp;
      char *token;
  	
      token = *str + 1;
  
      while (*token && (*token == ' ' || *token == '\t'))
  	token++;
  
      if ((sp = strchr(token, term)) == NULL)
  	return NULL;
  
      *sp = '\0';
      *str = sp;
      return token;
  }
  
  /* dav_process_if_header:
   *
   *   If NULL (no error) is returned, then **if_header points to the
   *   "If" productions structure (or NULL if "If" is not present).
   *
   *   ### this part is bogus:
   *   If an error is encountered, the error is logged.  Parent should
   *   return err->status.
   */
  static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih)
  {
      dav_error *err;
      char *str;
      char *list;
      const char *state_token;
      const char *uri = NULL;	/* scope of current production; NULL=no-tag */
      size_t uri_len;
      dav_if_header *ih = NULL;
      uri_components parsed_uri;
      const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
      enum {no_tagged, tagged, unknown} list_type = unknown;
      int condition;
  	
      *p_ih = NULL;
  
      if ((str = ap_pstrdup(r->pool, ap_table_get(r->headers_in, "If"))) == NULL)
  	return NULL;
  
      while (*str) {
  	switch(*str) {
  	case '<':
  	    /* Tagged-list production - following states apply to this uri */
  	    if (list_type == no_tagged
  		|| ((uri = dav_fetch_next_token(&str, '>')) == NULL)) {
  		return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  				     DAV_ERR_IF_TAGGED,
  				     "Invalid If-header: unclosed \"<\" or "
  				     "unexpected tagged-list production.");
  	    }
              
              /* 2518 specifies this must be an absolute URI; just take the
               * relative part for later comparison against r->uri */
              if (ap_parse_uri_components(r->pool, uri, &parsed_uri) != HTTP_OK) {
                  return dav_new_error(r->pool, HTTP_BAD_REQUEST,
                                       DAV_ERR_IF_TAGGED,
                                       "Invalid URI in tagged If-header.");
              }
  	    /* note that parsed_uri.path is allocated; we can trash it */
  
  	    /* clean up the URI a bit */
  	    ap_getparents(parsed_uri.path);
  	    uri_len = strlen(parsed_uri.path);
  	    if (uri_len > 1 && parsed_uri.path[uri_len - 1] == '/')
  		parsed_uri.path[--uri_len] = '\0';
  
  	    uri = parsed_uri.path;
  	    list_type = tagged;
  	    break;
  
  	case '(':
  	    /* List production */
  
  	    /* If a uri has not been encountered, this is a No-Tagged-List */
  	    if (list_type == unknown)
  		list_type = no_tagged;
  
  	    if ((list = dav_fetch_next_token(&str, ')')) == NULL) {
  		return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  				     DAV_ERR_IF_UNCLOSED_PAREN,
  				     "Invalid If-header: unclosed \"(\".");
  	    }
  
  	    if ((ih = dav_add_if_resource(r->pool, ih, uri, uri_len)) == NULL) {
  		/* ### dav_add_if_resource() should return an error for us! */
  		return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  				     DAV_ERR_IF_PARSE,
  				     "Internal server error parsing \"If:\" "
  				     "header.");
  	    }
  
  	    condition = DAV_IF_COND_NORMAL;
  
  	    while (*list) {
  		/* List is the entire production (in a uri scope) */
  
  		switch (*list) {
  		case '<':
  		    if ((state_token = dav_fetch_next_token(&list, '>')) == NULL) {
  			/* ### add a description to this error */
  			return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  					     DAV_ERR_IF_PARSE, NULL);
  		    }
  
  		    if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_opaquelock,
  						condition, locks_hooks)) != NULL) {
  			/* ### maybe add a higher level description */
  			return err;
  		    }
  		    condition = DAV_IF_COND_NORMAL;
  		    break;
  
  		case '[':
  		    if ((state_token = dav_fetch_next_token(&list, ']')) == NULL) {
  			/* ### add a description to this error */
  			return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  					     DAV_ERR_IF_PARSE, NULL);
  		    }
  
  		    if ((err = dav_add_if_state(r->pool, ih, state_token, dav_if_etag,
  						condition, locks_hooks)) != NULL) {
  			/* ### maybe add a higher level description */
  			return err;
  		    }
  		    condition = DAV_IF_COND_NORMAL;
  		    break;
  
  		case 'N':
  		    if (list[1] == 'o' && list[2] == 't') {
  			if (condition != DAV_IF_COND_NORMAL) {
  			    return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  						 DAV_ERR_IF_MULTIPLE_NOT,
  						 "Invalid \"If:\" header: "
  						 "Multiple \"not\" entries "
  						 "for the same state.");
  			}
  			condition = DAV_IF_COND_NOT;
  		    }
  		    list += 2;
  		    break;
  
  		case ' ':
  		case '\t':
  		    break;
  
  		default:
  		    return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  					 DAV_ERR_IF_UNK_CHAR,
                                           ap_psprintf(r->pool,
                                                       "Invalid \"If:\" "
                                                       "header: Unexpected "
                                                       "character encountered "
                                                       "(0x%02x, '%c').",
                                                       *list, *list));
  		}
  
  		list++;
  	    }
  	    break;
  
  	case ' ':
  	case '\t':
  	    break;
  
  	default:
  	    return dav_new_error(r->pool, HTTP_BAD_REQUEST,
  				 DAV_ERR_IF_UNK_CHAR,
                                   ap_psprintf(r->pool,
                                               "Invalid \"If:\" header: "
                                               "Unexpected character "
                                               "encountered (0x%02x, '%c').",
                                               *str, *str));
  	}
  
  	str++;
      }
  
      *p_ih = ih;
      return NULL;
  }
  
  static int dav_find_submitted_locktoken(const dav_if_header *if_header,
  					const dav_lock *lock_list,
  					const dav_hooks_locks *locks_hooks)
  {
      for (; if_header != NULL; if_header = if_header->next) {
  	const dav_if_state_list *state_list;
  
  	for (state_list = if_header->state;
  	     state_list != NULL;
  	     state_list = state_list->next) {
  
  	    if (state_list->type == dav_if_opaquelock) {
  		const dav_lock *lock;
  
  		/* given state_list->locktoken, match it */
  
  		/*
  		** The resource will have one or more lock tokens. We only
  		** need to match one of them against any token in the
  		** If: header.
  		**
  		** One token case: It is an exclusive or shared lock. Either
  		**                 way, we must find it.
  		**
  		** N token case: They are shared locks. By policy, we need
  		**               to match only one. The resource's other
  		**               tokens may belong to somebody else (so we
  		**               shouldn't see them in the If: header anyway)
  		*/
  		for (lock = lock_list; lock != NULL; lock = lock->next) {
  
  		    if (!(*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
  			return 1;
  		    }
  		}
  	    }
  	}
      }
  
      return 0;
  }
  
  /* dav_validate_resource_state:
   *    Returns NULL if path/uri meets if-header and lock requirements
   */
  static dav_error * dav_validate_resource_state(pool *p,
  					       const dav_resource *resource,
  					       dav_lockdb *lockdb,
  					       const dav_if_header *if_header,
                                                 int flags,
  					       dav_buffer *pbuf,
                                                 request_rec *r)
  {
      dav_error *err;
      const char *uri;
      const char *etag;
      const dav_hooks_locks *locks_hooks = (lockdb ? lockdb->hooks : NULL);
      const dav_if_header *ifhdr_scan;
      dav_if_state_list *state_list;
      dav_lock *lock_list;
      dav_lock *lock;
      int num_matched;
      int num_that_apply;
      int seen_locktoken;
      size_t uri_len;
      const char *reason = NULL;
  
      /* DBG1("validate: <%s>", resource->uri); */
  
      /*
      ** The resource will have one of three states:
      **
      ** 1) No locks. We have no special requirements that the user supply
      **    specific locktokens. One of the state lists must match, and
      **    we're done.
      **
      ** 2) One exclusive lock. The locktoken must appear *anywhere* in the
      **    If: header. Of course, asserting the token in a "Not" term will
      **    quickly fail that state list :-). If the locktoken appears in
      **    one of the state lists *and* one state list matches, then we're
      **    done.
      **
      ** 3) One or more shared locks. One of the locktokens must appear
      **    *anywhere* in the If: header. If one of the locktokens appears,
      **    and we match one state list, then we are done.
      **
      ** The <seen_locktoken> variable determines whether we have seen one
      ** of this resource's locktokens in the If: header.
      */
  
      /*
      ** If this is a new lock request, <flags> will contain the requested
      ** lock scope.  Three rules apply:
      **
      ** 1) Do not require a (shared) locktoken to be seen (when we are
      **    applying another shared lock)
      ** 2) If the scope is exclusive and we see any locks, fail.
      ** 3) If the scope is shared and we see an exclusive lock, fail.
      */
  
      if (lockdb == NULL) {
  	/* we're in State 1. no locks. */
  	lock_list = NULL;
      }
      else {
  	/*
  	** ### hrm... we don't need to have these fully
  	** ### resolved since we're only looking at the
  	** ### locktokens...
  	**
  	** ### use get_locks w/ calltype=PARTIAL
  	*/
  	if ((err = dav_lock_query(lockdb, resource, &lock_list)) != NULL) {
  	    return dav_push_error(p,
  				  HTTP_INTERNAL_SERVER_ERROR, 0,
  				  "The locks could not be queried for "
  				  "verification against a possible \"If:\" "
  				  "header.",
  				  err);
  	}
  
  	/* lock_list now determines whether we're in State 1, 2, or 3. */
      }
  
      /* 
      ** For a new, exclusive lock: if any locks exist, fail.
      ** For a new, shared lock:    if an exclusive lock exists, fail.
      **                            else, do not require a token to be seen.
      */
      if (flags & DAV_LOCKSCOPE_EXCLUSIVE) {
  	if (lock_list != NULL) {
  	    return dav_new_error(p, HTTP_LOCKED, 0, 
  				 "Existing lock(s) on the requested resource "
  				 "prevent an exclusive lock.");
  	}
  
  	/*
  	** There are no locks, so we can pretend that we've already met
  	** any requirement to find the resource's locks in an If: header.
  	*/
  	seen_locktoken = 1;
      }
      else if (flags & DAV_LOCKSCOPE_SHARED) {
  	/*
  	** Strictly speaking, we don't need this loop. Either the first
  	** (and only) lock will be EXCLUSIVE, or none of them will be.
  	*/
          for (lock = lock_list; lock != NULL; lock = lock->next) {
              if (lock->scope == DAV_LOCKSCOPE_EXCLUSIVE) {
  		return dav_new_error(p, HTTP_LOCKED, 0,
  				     "The requested resource is already "
  				     "locked exclusively.");
              }
          }
  
  	/*
  	** The locks on the resource (if any) are all shared. Set the
  	** <seen_locktoken> flag to indicate that we do not need to find
  	** the locks in an If: header.
  	*/
          seen_locktoken = 1;
      }
      else {
  	/*
  	** For methods other than LOCK:
  	**
  	** If we have no locks, then <seen_locktoken> can be set to true --
  	** pretending that we've already met the requirement of seeing one
  	** of the resource's locks in the If: header.
  	**
  	** Otherwise, it must be cleared and we'll look for one.
  	*/
          seen_locktoken = (lock_list == NULL);
      }
  
      /*
      ** If there is no If: header, then we can shortcut some logic:
      **
      ** 1) if we do not need to find a locktoken in the (non-existent) If:
      **    header, then we are successful.
      **
      ** 2) if we must find a locktoken in the (non-existent) If: header, then
      **    we fail.
      */
      if (if_header == NULL) {
  	if (seen_locktoken)
  	    return NULL;
  
  	return dav_new_error(p, HTTP_LOCKED, 0,
  			     "This resource is locked and an \"If:\" header "
  			     "was not supplied to allow access to the "
  			     "resource.");
      }
      /* the If: header is present */
  
      /*
      ** If a dummy header is present (because of a Lock-Token: header), then
      ** we are required to find that token in this resource's set of locks.
      ** If we have no locks, then we immediately fail.
      **
      ** This is a 400 (Bad Request) since they should only submit a locktoken
      ** that actually exists.
      **
      ** Don't issue this response if we're talking about the parent resource.
      ** It is okay for that resource to NOT have this locktoken.
      ** (in fact, it certainly will not: a dummy_header only occurs for the
      **  UNLOCK method, the parent is checked only for locknull resources,
      **  and the parent certainly does not have the (locknull's) locktoken)
      */
      if (lock_list == NULL && if_header->dummy_header) {
          if (flags & DAV_VALIDATE_IS_PARENT)
              return NULL;
  	return dav_new_error(p, HTTP_BAD_REQUEST, 0,
  			     "The locktoken specified in the \"Lock-Token:\" "
  			     "header is invalid because this resource has no "
  			     "outstanding locks.");
      }
  
      /*
      ** Prepare the input URI. We want the URI to never have a trailing slash.
      **
      ** When URIs are placed into the dav_if_header structure, they are
      ** guaranteed to never have a trailing slash. If the URIs are equivalent,
      ** then it doesn't matter if they both lack a trailing slash -- they're
      ** still equivalent.
      **
      ** Note: we could also ensure that a trailing slash is present on both
      ** URIs, but the majority of URIs provided to us via a resource walk
      ** will not contain that trailing slash.
      */
      uri = resource->uri;
      uri_len = strlen(uri);
      if (uri[uri_len - 1] == '/') {
  	dav_set_bufsize(p, pbuf, uri_len);
  	memcpy(pbuf->buf, uri, uri_len);
  	pbuf->buf[--uri_len] = '\0';
  	uri = pbuf->buf;
      }
  
      /* get the resource's etag; we may need it during the checks */
      etag = (*resource->hooks->getetag)(resource);
  
      /* how many state_lists apply to this URI? */
      num_that_apply = 0;
  
      /* If there are if-headers, fail if this resource
       * does not match at least one state_list.
       */
      for (ifhdr_scan = if_header;
  	 ifhdr_scan != NULL;
  	 ifhdr_scan = ifhdr_scan->next) {
  
  	/* DBG2("uri=<%s>  if_uri=<%s>", uri, ifhdr_scan->uri ? ifhdr_scan->uri : "(no uri)"); */
  
  	if (ifhdr_scan->uri != NULL
  	    && (uri_len != ifhdr_scan->uri_len
  		|| memcmp(uri, ifhdr_scan->uri, uri_len) != 0)) {
  	    /*
  	    ** A tagged-list's URI doesn't match this resource's URI.
  	    ** Skip to the next state_list to see if it will match.
  	    */
  	    continue;
  	}
  
  	/* this state_list applies to this resource */
  
  	/*
  	** ### only one state_list should ever apply! a no-tag, or a tagged
  	** ### where S9.4.2 states only one can match.
  	**
  	** ### revamp this code to loop thru ifhdr_scan until we find the
  	** ### matching state_list. process it. stop.
  	*/
  	++num_that_apply;
  
  	/* To succeed, resource must match *all* of the states
  	 * specified in the state_list.
  	 */
  	for (state_list = ifhdr_scan->state;
  	     state_list != NULL;
  	     state_list = state_list->next) {
  
  	    switch(state_list->type) {
  	    case dav_if_etag:
  	    {
  		int mismatch = strcmp(state_list->etag, etag);
  
  		if (state_list->condition == DAV_IF_COND_NORMAL && mismatch) {
  		    /*
  		    ** The specified entity-tag does not match the
  		    ** entity-tag on the resource. This state_list is
  		    ** not going to match. Bust outta here.
  		    */
  		    reason =
  			"an entity-tag was specified, but the resource's "
  			"actual ETag does not match.";
  		    goto state_list_failed;
  		}
  		else if (state_list->condition == DAV_IF_COND_NOT
  			 && !mismatch) {
  		    /*
  		    ** The specified entity-tag DOES match the
  		    ** entity-tag on the resource. This state_list is
  		    ** not going to match. Bust outta here.
  		    */
  		    reason =
  			"an entity-tag was specified using the \"Not\" form, "
  			"but the resource's actual ETag matches the provided "
  			"entity-tag.";
  		    goto state_list_failed;
  		}
  		break;
  	    }
  
  	    case dav_if_opaquelock:
  		if (lockdb == NULL) {
  		    if (state_list->condition == DAV_IF_COND_NOT) {
  			/* the locktoken is definitely not there! (success) */
  			continue;
  		    }
  
  		    /* condition == DAV_IF_COND_NORMAL */
  
  		    /*
  		    ** If no lockdb is provided, then validation fails for
  		    ** this state_list (NORMAL means we were supposed to
  		    ** find the token, which we obviously cannot do without
  		    ** a lock database).
  		    **
  		    ** Go and try the next state list.
  		    */
  		    reason =
  			"a State-token was supplied, but a lock database "
  			"is not available for to provide the required lock.";
  		    goto state_list_failed;
  		}
  
  		/* Resource validation 'fails' if:
  		 *    ANY  of the lock->locktokens match
  		 *         a NOT state_list->locktoken,
  		 * OR
  		 *    NONE of the lock->locktokens match
  		 *         a NORMAL state_list->locktoken.
  		 */
  		num_matched = 0;
  		for (lock = lock_list; lock != NULL; lock = lock->next) {
  
  		    /*
  		    DBG2("compare: rsrc=%s  ifhdr=%s",
  			 (*locks_hooks->format_locktoken)(p, lock->locktoken),
  			 (*locks_hooks->format_locktoken)(p, state_list->locktoken));
  		    */
  
  		    /* nothing to do if the locktokens do not match. */
  		    if ((*locks_hooks->compare_locktoken)(state_list->locktoken, lock->locktoken)) {
  			continue;
  		    }
  
  		    /*
  		    ** We have now matched up one of the resource's locktokens
  		    ** to a locktoken in a State-token in the If: header.
  		    ** Note this fact, so that we can pass the overall
  		    ** requirement of seeing at least one of the resource's
  		    ** locktokens.
  		    */
  		    seen_locktoken = 1;
  
  		    if (state_list->condition == DAV_IF_COND_NOT) {
  			/*
  			** This state requires that the specified locktoken
  			** is NOT present on the resource. But we just found
  			** it. There is no way this state-list can now
  			** succeed, so go try another one.
  			*/
  			reason =
  			    "a State-token was supplied, which used a "
  			    "\"Not\" condition. The State-token was found "
  			    "in the locks on this resource";
  			goto state_list_failed;
  		    }
  
  		    /* condition == DAV_IF_COND_NORMAL */
  
                      /* Validate auth_user:  If an authenticated user created
                      ** the lock, only the same user may submit that locktoken
                      ** to manipulate a resource.
                      */
                      if (lock->auth_user && 
                          (!r->connection->user ||
                           strcmp(lock->auth_user, r->connection->user))) {
                          const char *errmsg;
  
                          errmsg = ap_pstrcat(p, "User \"",
                                              r->connection->user, 
                                              "\" submitted a locktoken created "
                                              "by user \"",
                                              lock->auth_user, "\".", NULL);
                          return dav_new_error(p, HTTP_UNAUTHORIZED, 0, errmsg);
                      }
  
  		    /*
  		    ** We just matched a specified State-Token to one of the
  		    ** resource's locktokens.
  		    **
  		    ** Break out of the lock scan -- we only needed to find
  		    ** one match (actually, there shouldn't be any other
  		    ** matches in the lock list).
  		    */
  		    num_matched = 1;
  		    break;
  		}
  
  		if (num_matched == 0
  		    && state_list->condition == DAV_IF_COND_NORMAL) {
  		    /*
  		    ** We had a NORMAL state, meaning that we should have
  		    ** found the State-Token within the locks on this
  		    ** resource. We didn't, so this state_list must fail.
  		    */
  		    reason =
  			"a State-token was supplied, but it was not found "
  			"in the locks on this resource.";
  		    goto state_list_failed;
  		}
  
  		break;
  
  	    } /* switch */
  	} /* foreach ( state_list ) */
  
  	/*
  	** We've checked every state in this state_list and none of them
  	** have failed. Since all of them succeeded, then we have a matching
  	** state list and we may be done.
  	**
  	** The next requirement is that we have seen one of the resource's
  	** locktokens (if any). If we have, then we can just exit. If we
  	** haven't, then we need to keep looking.
  	*/
  	if (seen_locktoken) {
  	    /* woo hoo! */
  	    return NULL;
  	}
  
  	/*
  	** Haven't seen one. Let's break out of the search and just look
  	** for a matching locktoken.
  	*/
  	break;
  
  	/*
  	** This label is used when we detect that a state_list is not
  	** going to match this resource. We bust out and try the next
  	** state_list.
  	*/
        state_list_failed:
  	;
  
      } /* foreach ( ifhdr_scan ) */
  
      /*
      ** The above loop exits for one of two reasons:
      **   1) a state_list matched and seen_locktoken is false.
      **   2) all if_header structures were scanned, without (1) occurring
      */
  
      if (ifhdr_scan == NULL) {
  	/*
  	** We finished the loop without finding any matching state lists.
  	*/
  
  	/*
  	** If none of the state_lists apply to this resource, then we
  	** may have succeeded. Note that this scenario implies a
  	** tagged-list with no matching state_lists. If the If: header
  	** was a no-tag-list, then it would have applied to this resource.
  	**
  	** S9.4.2 states that when no state_lists apply, then the header
  	** should be ignored.
  	**
  	** If we saw one of the resource's locktokens, then we're done.
  	** If we did not see a locktoken, then we fail.
  	*/
  	if (num_that_apply == 0) {
  	    if (seen_locktoken)
  		return NULL;
  
  	    /*
  	    ** We may have aborted the scan before seeing the locktoken.
  	    ** Rescan the If: header to see if we can find the locktoken
  	    ** somewhere.
  	    */
  	    if (dav_find_submitted_locktoken(if_header, lock_list,
  					     locks_hooks)) {
  		/*
  		** We found a match! We're set... none of the If: header
  		** assertions apply (implicit success), and the If: header
  		** specified the locktoken somewhere. We're done.
  		*/
  		return NULL;
  	    }
  
  	    return dav_new_error(p, HTTP_LOCKED, 0 /* error_id */,
  				 "This resource is locked and the \"If:\" "
  				 "header did not specify one of the "
  				 "locktokens for this resource's lock(s).");
  	}
  	/* else: one or more state_lists were applicable, but failed. */
  
  	/*
  	** If the dummy_header did not match, then they specified an
  	** incorrect token in the Lock-Token header. Forget whether the
  	** If: statement matched or not... we'll tell them about the
  	** bad Lock-Token first. That is considered a 400 (Bad Request).
  	*/
  	if (if_header->dummy_header) {
  	    return dav_new_error(p, HTTP_BAD_REQUEST, 0,
  				 "The locktoken specified in the "
  				 "\"Lock-Token:\" header did not specify one "
  				 "of this resource's locktoken(s).");
  	}
  
  	if (reason == NULL) {
  	    return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
  				 "The preconditions specified by the \"If:\" "
  				 "header did not match this resource.");
  	}
  
  	return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
  			     ap_psprintf(p,
  					 "The precondition(s) specified by "
  					 "the \"If:\" header did not match "
  					 "this resource. At least one "
  					 "failure is because: %s", reason));
      }
  
      /* assert seen_locktoken == 0 */
  
      /*
      ** ifhdr_scan != NULL implies we found a matching state_list.
      **
      ** Since we're still here, it also means that we have not yet found
      ** one the resource's locktokens in the If: header.
      **
      ** Scan all the if_headers and states looking for one of this
      ** resource's locktokens. Note that we need to go back and scan them
      ** all -- we may have aborted a scan with a failure before we saw a
      ** matching token.
      **
      ** Note that seen_locktoken == 0 implies lock_list != NULL which implies
      ** locks_hooks != NULL.
      */
      if (dav_find_submitted_locktoken(if_header, lock_list, locks_hooks)) {
  	/*
  	** We found a match! We're set... we have a matching state list,
  	** and the If: header specified the locktoken somewhere. We're done.
  	*/
  	return NULL;
      }
  
      /*
      ** We had a matching state list, but the user agent did not specify one
      ** of this resource's locktokens. Tell them so.
      **
      ** Note that we need to special-case the message on whether a "dummy"
      ** header exists. If it exists, yet we didn't see a needed locktoken,
      ** then that implies the dummy header (Lock-Token header) did NOT
      ** specify one of this resource's locktokens. (this implies something
      ** in the real If: header matched)
      **
      ** We want to note the 400 (Bad Request) in favor of a 423 (Locked).
      */
      if (if_header->dummy_header) {
  	return dav_new_error(p, HTTP_BAD_REQUEST, 0,
  			     "The locktoken specified in the "
  			     "\"Lock-Token:\" header did not specify one "
  			     "of this resource's locktoken(s).");
      }
  
      return dav_new_error(p, HTTP_LOCKED, 1 /* error_id */,
  			 "This resource is locked and the \"If:\" header "
  			 "did not specify one of the "
  			 "locktokens for this resource's lock(s).");
  }
  
  /* dav_validate_walker:  Walker callback function to validate resource state */
  static dav_error * dav_validate_walker(dav_walker_ctx *ctx, int calltype)
  {
      dav_error *err;
  
      if ((err = dav_validate_resource_state(ctx->pool, ctx->resource,
  					   ctx->lockdb,
  					   ctx->if_header, ctx->flags,
  					   &ctx->work_buf, ctx->r)) == NULL) {
  	/* There was no error, so just bug out. */
  	return NULL;
      }
  
      /*
      ** If we have a serious server error, or if the request itself failed,
      ** then just return error (not a multistatus).
      */
      if (ap_is_HTTP_SERVER_ERROR(err->status)
          || (*ctx->resource->hooks->is_same_resource)(ctx->resource,
                                                       ctx->root)) {
  	/* ### maybe push a higher-level description? */
  	return err;
      }
  
      /* associate the error with the current URI */
      dav_add_response(ctx, ctx->uri.buf, err->status, NULL);
  
      return NULL;
  }
  
  /*
  ** dav_validate_request:  Validate if-headers (and check for locks) on:
  **    (1) r->filename @ depth;
  **    (2) Parent of r->filename if check_parent == 1
  **
  ** The check of parent should be done when it is necessary to verify that
  ** the parent collection will accept a new member (ie current resource
  ** state is null).
  **
  ** Return OK on successful validation.
  ** On error, return appropriate HTTP_* code, and log error. If a multi-stat
  ** error is necessary, response will point to it, else NULL.
  */
  dav_error * dav_validate_request(request_rec *r, dav_resource *resource,
  				 int depth, dav_locktoken *locktoken,
  				 dav_response **response, int flags,
                                   dav_lockdb *lockdb)
  {
      dav_error *err;
      int result;
      dav_if_header *if_header;
      int lock_db_opened_locally = 0;
      const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
      const dav_hooks_repository *repos_hooks = resource->hooks;
      dav_buffer work_buf = { 0 };
      dav_response *new_response;
  
  #if DAV_DEBUG
      if (depth && response == NULL) {
  	/*
  	** ### bleck. we can't return errors for other URIs unless we have
          ** ### a "response" ptr.
  	*/
  	return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "DESIGN ERROR: dav_validate_request called "
                               "with depth>0, but no response ptr.");
      }
  #endif
  
      if (response != NULL)
  	*response = NULL;
  
      /* Do the standard checks for conditional requests using 
       * If-..-Since, If-Match etc */
      if ((result = ap_meets_conditions(r)) != OK) {
  	/* ### fix this up... how? */
  	return dav_new_error(r->pool, result, 0, NULL);
      }
  
      /* always parse (and later process) the If: header */
      if ((err = dav_process_if_header(r, &if_header)) != NULL) {
  	/* ### maybe add higher-level description */
  	return err;
      }
  
      /* If a locktoken was specified, create a dummy if_header with which
       * to validate resources.  In the interim, figure out why DAV uses
       * locktokens in an if-header without a Lock-Token header to refresh
       * locks, but a Lock-Token header without an if-header to remove them.
       */
      if (locktoken != NULL) {
  	dav_if_header *ifhdr_new;
  
  	ifhdr_new = ap_pcalloc(r->pool, sizeof(*ifhdr_new));
  	ifhdr_new->uri = resource->uri;
  	ifhdr_new->uri_len = strlen(resource->uri);
  	ifhdr_new->dummy_header = 1;
  
  	ifhdr_new->state = ap_pcalloc(r->pool, sizeof(*ifhdr_new->state));
  	ifhdr_new->state->type = dav_if_opaquelock;
  	ifhdr_new->state->condition = DAV_IF_COND_NORMAL;
  	ifhdr_new->state->locktoken = locktoken;
  
  	ifhdr_new->next = if_header;
  	if_header = ifhdr_new;
      }
  
      /*
      ** If necessary, open the lock database (read-only, lazily);
      ** the validation process may need to retrieve or update lock info.
      ** Otherwise, assume provided lockdb is valid and opened rw.
      */
      if (lockdb == NULL) {
          if (locks_hooks != NULL) {
              if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) {
  	        /* ### maybe insert higher-level comment */
  	        return err;
              }
              lock_db_opened_locally = 1;
          }
          else {
              return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Resource validation failed because no "
  				 "lock hooks were found.");
          }
      }
  
      /* (1) Validate the specified resource, at the specified depth */
      if (resource->exists && depth > 0) {
  	dav_walker_ctx ctx = { 0 };
  
  	ctx.walk_type = DAV_WALKTYPE_ALL;
  	ctx.postfix = 0;
  	ctx.func = dav_validate_walker;
  	ctx.pool = r->pool;
  	ctx.if_header = if_header;
  	ctx.r = r;
          ctx.flags = flags;
          ctx.resource = resource;
  
  	if (lockdb != NULL) {
  	    ctx.lockdb = lockdb;
  	    ctx.walk_type |= DAV_WALKTYPE_LOCKNULL;
  	}
  
  	dav_buffer_init(r->pool, &ctx.uri, resource->uri);
  
  	err = (*repos_hooks->walk)(&ctx, DAV_INFINITY);
  	if (err == NULL) {
              *response = ctx.response;
  	}
          /* else: implies a 5xx status code occurred. */
      }
      else {
  	err = dav_validate_resource_state(r->pool, resource, lockdb,
  					  if_header, flags, &work_buf, r);
      }
  
      /* (2) Validate the parent resource if requested */
      if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
          dav_resource *parent_resource = (*repos_hooks->get_parent_resource)(resource);
  
  	if (parent_resource == NULL) {
  	    err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
  				"Cannot access parent of repository root.");
  	}
  	else {
  	    err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
  					      if_header,
                                                flags | DAV_VALIDATE_IS_PARENT,
                                                &work_buf, r);
  	    
  	    /*
  	    ** This error occurred on the parent resource. This implies that
  	    ** we have to create a multistatus response (to report the error
  	    ** against a URI other than the Request-URI). "Convert" this error
  	    ** into a multistatus response.
  	    */
  	    if (err != NULL) {
  		new_response = ap_pcalloc(r->pool, sizeof(*new_response));
  		
  		new_response->href = parent_resource->uri;
  		new_response->status = err->status;
  		new_response->desc =
  		    "A validation error has occurred on the parent resource, "
  		    "preventing the operation on the resource specified by "
  		    "the Request-URI.";
                  if (err->desc != NULL) {
                      new_response->desc = ap_pstrcat(r->pool,
                                                      new_response->desc,
                                                      " The error was: ",
                                                      err->desc, NULL);
                  }
  		
  		/* assert: DAV_VALIDATE_PARENT implies response != NULL */
  		new_response->next = *response;
  		*response = new_response;
  		
  		err = NULL;
  	    }
  	}
      }
  
      if (lock_db_opened_locally)
          (*locks_hooks->close_lockdb)(lockdb);
  
      /*
      ** If we don't have a (serious) error, and we have multistatus responses,
      ** then we need to construct an "error". This error will be the overall
      ** status returned, and the multistatus responses will go into its body.
      **
      ** For certain methods, the overall error will be a 424. The default is
      ** to construct a standard 207 response.
      */
      if (err == NULL && response != NULL && *response != NULL) {
          dav_text *propstat = NULL;
  
          if ((flags & DAV_VALIDATE_USE_424) != 0) {
              /* manufacture a 424 error to hold the multistatus response(s) */
              return dav_new_error(r->pool, HTTP_FAILED_DEPENDENCY, 0,
                                   "An error occurred on another resource, "
                                   "preventing the requested operation on "
                                   "this resource.");
          }
  
          /*
          ** Whatever caused the error, the Request-URI should have a 424
          ** associated with it since we cannot complete the method.
          **
          ** For a LOCK operation, insert an empty DAV:lockdiscovery property.
          ** For other methods, return a simple 424.
          */
          if ((flags & DAV_VALIDATE_ADD_LD) != 0) {
              propstat = ap_pcalloc(r->pool, sizeof(*propstat));
              propstat->text =
                  "<D:propstat>" DEBUG_CR
                  "<D:prop><D:lockdiscovery/></D:prop>" DEBUG_CR
                  "<D:status>HTTP/1.1 424 Failed Dependency</D:status>" DEBUG_CR
                  "</D:propstat>" DEBUG_CR;
          }
  
          /* create the 424 response */
          new_response = ap_pcalloc(r->pool, sizeof(*new_response));
          new_response->href = resource->uri;
          new_response->status = HTTP_FAILED_DEPENDENCY;
          new_response->propresult.propstats = propstat;
          new_response->desc =
              "An error occurred on another resource, preventing the "
              "requested operation on this resource.";
  
          new_response->next = *response;
          *response = new_response;
  
          /* manufacture a 207 error for the multistatus response(s) */
          return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
                               "Error(s) occurred on resources during the "
                               "validation process.");
      }
  
      return err;
  }
  
  /* dav_get_locktoken_list:
   *
   * Sets ltl to a locktoken_list of all positive locktokens in header,
   * else NULL if no If-header, or no positive locktokens.
   */
  dav_error * dav_get_locktoken_list(request_rec *r, dav_locktoken_list **ltl) 
  {
      dav_error *err;
      dav_if_header *if_header;
      dav_if_state_list *if_state;
      dav_locktoken_list *lock_token = NULL;		
  	
      *ltl = NULL;
  
      if ((err = dav_process_if_header(r, &if_header)) != NULL) {
  	/* ### add a higher-level description? */
  	return err;
      }
   			
      while (if_header != NULL) {
  	if_state = if_header->state;	/* Begining of the if_state linked list */
  	while (if_state != NULL)	{
  	    if (if_state->condition == DAV_IF_COND_NORMAL
  	        && if_state->type == dav_if_opaquelock) {
  		lock_token = ap_pcalloc(r->pool, sizeof(dav_locktoken_list));
  		lock_token->locktoken = if_state->locktoken;
  		lock_token->next = *ltl;
  		*ltl = lock_token;
  	    }
  	    if_state = if_state->next; 
  	}
  	if_header = if_header->next;
      }
      if (*ltl == NULL) {
  	/* No nodes added */
  	return dav_new_error(r->pool, HTTP_BAD_REQUEST, DAV_ERR_IF_ABSENT,
  			     "No locktokens were specified in the \"If:\" "
  			     "header, so the refresh could not be performed.");
      }
  
      return NULL;
  }
  
  /* dav_get_target_selector:
   *
   * Returns any Target-Selector header in a request
   * (used by versioning clients)
   */
  const char *dav_get_target_selector(request_rec *r)
  {
      return ap_table_get(r->headers_in, "Target-Selector");
  }
  
  /* Ensure that a resource is writable. If there is no versioning
   * provider, then this is essentially a no-op. Versioning repositories
   * require explicit resource creation and checkout before they can
   * be written to. If a new resource is to be created, or an existing
   * resource deleted, the parent collection must be checked out as well.
   *
   * Set the parent_only flag to only make the parent collection writable.
   * Otherwise, both parent and child are made writable as needed. If the
   * child does not exist, then a new versioned resource is created and
   * checked out.
   *
   * The parent_resource and parent_was_writable arguments are optional
   * (i.e. they may be NULL). If parent_only is set, then the
   * resource_existed and resource_was_writable arguments are ignored.
   *
   * The previous states of the resources are returned, so they can be
   * restored after the operation completes (see
   * dav_revert_resource_writability())
   */
  dav_error *dav_ensure_resource_writable(request_rec *r,
  					  dav_resource *resource,
                                            int parent_only,
  					  dav_resource **parent_resource,
  					  int *resource_existed,
  					  int *resource_was_writable,
  					  int *parent_was_writable)
  {
      const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
      dav_resource *parent = NULL;
      const char *body;
      int auto_version;
      dav_error *err;
  
      if (parent_resource != NULL)
          *parent_resource = NULL;
  
      if (!parent_only) {
          *resource_existed = resource->exists;
          *resource_was_writable = 0;
      }
  
      if (parent_was_writable != NULL)
          *parent_was_writable = 0;
  
      /* if a Target-Selector header is present, then the client knows about
       * versioning, so it should not be relying on implicit versioning
       */
      auto_version = (dav_get_target_selector(r) == NULL);
  
      /* check parent resource if requested or if resource must be created */
      if (!resource->exists || parent_only) {
  	parent = (*resource->hooks->get_parent_resource)(resource);
          if (parent == NULL || !parent->exists) {
  	    body = ap_psprintf(r->pool,
  			       "Missing one or more intermediate collections. "
  			       "Cannot create resource %s.",
  			       ap_escape_html(r->pool, resource->uri));
  	    return dav_new_error(r->pool, HTTP_CONFLICT, 0, body);
          }
  
          if (parent_resource != NULL)
  	    *parent_resource = parent;
  
  	/* if parent not versioned, assume child can be created */
  	if (!parent->versioned) {
              if (!parent_only)
  	        *resource_was_writable = 1;
  
              if (parent_was_writable != NULL)
  	        *parent_was_writable = 1;
  	    return NULL;
  	}
  
  	/* if no versioning provider, something is terribly wrong */
  	if (vsn_hooks == NULL) {
  	    return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "INTERNAL ERROR: "
                                   "versioned resource with no versioning "
  				 "provider?");
  	}
  
          /* remember whether parent was already writable */
          if (parent_was_writable != NULL)
  	    *parent_was_writable = parent->working;
  
  	/* parent must be checked out */
  	if (!parent->working) {
  	    if ((err = (*vsn_hooks->checkout)(parent)) != NULL) {
  		body = ap_psprintf(r->pool,
  				   "Unable to checkout parent collection. "
  				   "Cannot create resource %s.",
  				   ap_escape_html(r->pool, resource->uri));
  		return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
  	    }
  	}
  
  	/* if not just checking parent, create new child resource */
          if (!parent_only) {
  	    if ((err = (*vsn_hooks->mkresource)(resource)) != NULL) {
  	        body = ap_psprintf(r->pool,
  			           "Unable to create versioned resource %s.",
  			           ap_escape_html(r->pool, resource->uri));
  	        return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
  	    }
          }
      }
      else {
  	/* resource exists: if not versioned, then assume it is writable */
  	if (!resource->versioned) {
  	    *resource_was_writable = 1;
  	    return NULL;
  	}
  
  	*resource_was_writable = resource->working;
      }
  
      /* if not just checking parent, make sure child resource is checked out */
      if (!parent_only && !resource->working) {
  	if ((err = (*vsn_hooks->checkout)(resource)) != NULL) {
  	    body = ap_psprintf(r->pool,
  			       "Unable to checkout resource %s.",
  			       ap_escape_html(r->pool, resource->uri));
  	    return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
  	}
      }
  
      return NULL;
  }
  
  /* Revert the writability of resources back to what they were
   * before they were modified. If undo == 0, then the resource
   * modifications are maintained (i.e. they are checked in).
   * If undo != 0, then resource modifications are discarded
   * (i.e. they are unchecked out).
   *
   * The resource and parent_resource arguments are optional
   * (i.e. they may be NULL).
   */
  dav_error *dav_revert_resource_writability(request_rec *r,
  					   dav_resource *resource,
  					   dav_resource *parent_resource,
  					   int undo,
  					   int resource_existed,
  					   int resource_was_writable,
  					   int parent_was_writable)
  {
      const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
      const char *body;
      dav_error *err;
  
      if (resource != NULL) {
          if (!resource_was_writable
  	    && resource->versioned && resource->working) {
  
              if (undo)
                  err = (*vsn_hooks->uncheckout)(resource);
              else
                  err = (*vsn_hooks->checkin)(resource);
  
              if (err != NULL) {
  	        body = ap_psprintf(r->pool,
  			           "Unable to %s resource %s.",
                                     undo ? "uncheckout" : "checkin",
  			           ap_escape_html(r->pool, resource->uri));
                  return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  				      body, err);
              }
          }
  
          if (undo && !resource_existed && resource->exists) {
  	    dav_response *response;
  
  	    /* ### should we do anything with the response? */
              if ((err = (*resource->hooks->remove_resource)(resource,
  							   &response)) != NULL) {
  	        body = ap_psprintf(r->pool,
  			           "Unable to undo creation of resource %s.",
  			           ap_escape_html(r->pool, resource->uri));
                  return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  				      body, err);
              }
          }
      }
  
      if (parent_resource != NULL && !parent_was_writable
  	&& parent_resource->versioned && parent_resource->working) {
  
  	if (undo)
  	    err = (*vsn_hooks->uncheckout)(parent_resource);
  	else
  	    err = (*vsn_hooks->checkin)(parent_resource);
  
  	if (err != NULL) {
  	    body = ap_psprintf(r->pool,
  			       "Unable to %s parent collection of %s.",
  			       undo ? "uncheckout" : "checkin",
  			       ap_escape_html(r->pool, resource->uri));
  	    return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  				  body, err);
  	}
      }
  
      return NULL;
  }
  
  /* return the URI's (existing) index, or insert it and return a new index */
  int dav_insert_uri(array_header *uri_array, const char *uri)
  {
      int i;
      const char **pelt;
  
      for (i = uri_array->nelts; i--;) {
  	if (strcmp(uri, DAV_GET_URI_ITEM(uri_array, i)) == 0)
  	    return i;
      }
  
      pelt = ap_push_array(uri_array);
      *pelt = uri;		/* assume uri is const or in a pool */
      return uri_array->nelts - 1;
  }
  
  
  
  1.1                  apache-2.0/src/modules/dav/main/util_lock.c
  
  Index: util_lock.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV repository-independent lock functions
  **
  ** Written 06/99 by Keith Wannamaker, wannamak@us.ibm.com
  **
  ** Modified 08/99 by John Vasta, vasta@rational.com, to move filesystem-based
  ** implementation to dav_fs_lock.c
  */
  
  #include "mod_dav.h"
  #include "http_log.h"
  #include "http_config.h"
  #include "http_protocol.h"
  #include "http_core.h"
  #include "memory.h"
  
  
  /* ---------------------------------------------------------------
  **
  ** Property-related lock functions
  **
  */
  
  /*
  ** dav_lock_get_activelock:  Returns a <lockdiscovery> containing
  **    an activelock element for every item in the lock_discovery tree
  */
  const char *dav_lock_get_activelock(request_rec *r, dav_lock *lock,
  				    dav_buffer *pbuf)
  {
      dav_lock *lock_scan;
      const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
      int count = 0;
      dav_buffer work_buf = { 0 };
      pool *p = r->pool;
  
      /* If no locks or no lock provider, there are no locks */
      if (lock == NULL || hooks == NULL) {
  	/*
  	** Since resourcediscovery is defined with (activelock)*, 
  	** <D:activelock/> shouldn't be necessary for an empty lock.
  	*/
  	return "";
      }
  
      /*
      ** Note: it could be interesting to sum the lengths of the owners
      **       and locktokens during this loop. However, the buffer
      **       mechanism provides some rough padding so that we don't
      **       really need to have an exact size. Further, constructing
      **       locktoken strings could be relatively expensive.
      */
      for (lock_scan = lock; lock_scan != NULL; lock_scan = lock_scan->next)
  	count++;
  
      /* if a buffer was not provided, then use an internal buffer */
      if (pbuf == NULL)
  	pbuf = &work_buf;
  
      /* reset the length before we start appending stuff */
      pbuf->cur_len = 0;
  
      /* prep the buffer with a "good" size */
      dav_check_bufsize(p, pbuf, count * 300);
  
      for (; lock != NULL; lock = lock->next) {
  	char tmp[100];
  
  #if DAV_DEBUG
  	if (lock->rectype == DAV_LOCKREC_INDIRECT_PARTIAL) {
  	    /* ### crap. design error */
  	    dav_buffer_append(p, pbuf,
  			      "DESIGN ERROR: attempted to product an "
  			      "activelock element from a partial, indirect "
  			      "lock record. Creating an XML parsing error "
  			      "to ease detection of this situation: <");
  	}
  #endif
  
  	dav_buffer_append(p, pbuf, "<D:activelock>" DEBUG_CR "<D:locktype>");
  	switch (lock->type) {
  	case DAV_LOCKTYPE_WRITE:
  	    dav_buffer_append(p, pbuf, "<D:write/>");
  	    break;
  	default:
  	    /* ### internal error. log something? */
  	    break;
  	}
  	dav_buffer_append(p, pbuf, "</D:locktype>" DEBUG_CR "<D:lockscope>");
  	switch (lock->scope) {
  	case DAV_LOCKSCOPE_EXCLUSIVE:
  	    dav_buffer_append(p, pbuf, "<D:exclusive/>");
  	    break;
  	case DAV_LOCKSCOPE_SHARED:
  	    dav_buffer_append(p, pbuf, "<D:shared/>");
  	    break;
  	default:
  	    /* ### internal error. log something? */
  	    break;
  	}
  	dav_buffer_append(p, pbuf, "</D:lockscope>" DEBUG_CR);
  	sprintf(tmp, "<D:depth>%s</D:depth>" DEBUG_CR,
  		lock->depth == DAV_INFINITY ? "infinity" : "0");
  	dav_buffer_append(p, pbuf, tmp);
  
  	if (lock->owner) {
  	    /*
  	    ** This contains a complete, self-contained <DAV:owner> element,
  	    ** with namespace declarations and xml:lang handling. Just drop
  	    ** it in.
  	    */
  	    dav_buffer_append(p, pbuf, lock->owner);
  	}
  		
  	dav_buffer_append(p, pbuf, "<D:timeout>");
  	if (lock->timeout == DAV_TIMEOUT_INFINITE) {
  	    dav_buffer_append(p, pbuf, "Infinite");
  	}
  	else {
  	    time_t now = time(NULL);
  	    sprintf(tmp, "Second-%lu", lock->timeout - now);
  	    dav_buffer_append(p, pbuf, tmp);
  	}
  
  	dav_buffer_append(p, pbuf,
  			  "</D:timeout>" DEBUG_CR
  			  "<D:locktoken>" DEBUG_CR
  			  "<D:href>");
  	dav_buffer_append(p, pbuf,
  			  (*hooks->format_locktoken)(p, lock->locktoken));
  	dav_buffer_append(p, pbuf,
  			  "</D:href>" DEBUG_CR
  			  "</D:locktoken>" DEBUG_CR
  			  "</D:activelock>" DEBUG_CR);
      }
  
      return pbuf->buf;
  }
  
  /*
  ** dav_lock_parse_lockinfo:  Validates the given xml_doc to contain a
  **    lockinfo XML element, then populates a dav_lock structure
  **    with its contents.
  */
  dav_error * dav_lock_parse_lockinfo(request_rec *r,
  				    const dav_resource *resource,
  				    dav_lockdb *lockdb,
  				    const dav_xml_doc *doc,
  				    dav_lock **lock_request)
  {
      const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
      pool *p = r->pool;
      dav_error *err;
      dav_xml_elem *child;
      dav_lock *lock;
  	
      if (!dav_validate_root(doc, "lockinfo")) {
  	return dav_new_error(p, HTTP_BAD_REQUEST, 0,
  			     "The request body contains an unexpected "
  			     "XML root element.");
      }
  
      if ((err = (*hooks->create_lock)(lockdb, resource, &lock)) != NULL) {
  	return dav_push_error(p, err->status, 0,
  			      "Could not parse the lockinfo due to an "
  			      "internal problem creating a lock structure.",
  			      err);
      }
  
      lock->depth = dav_get_depth(r, DAV_INFINITY);
      if (lock->depth == -1) {
  	return dav_new_error(p, HTTP_BAD_REQUEST, 0,
  			     "An invalid Depth header was specified.");
      }
      lock->timeout = dav_get_timeout(r);
  
      /* Parse elements in the XML body */
      for (child = doc->root->first_child; child; child = child->next) {
  	if (strcmp(child->name, "locktype") == 0
  	    && child->first_child
  	    && lock->type == DAV_LOCKTYPE_UNKNOWN) {
  	    if (strcmp(child->first_child->name, "write") == 0) {
  		lock->type = DAV_LOCKTYPE_WRITE;
  		continue;
  	    }
  	}
  	if (strcmp(child->name, "lockscope") == 0
  	    && child->first_child
  	    && lock->scope == DAV_LOCKSCOPE_UNKNOWN) {
  	    if (strcmp(child->first_child->name, "exclusive") == 0)
  		lock->scope = DAV_LOCKSCOPE_EXCLUSIVE;
  	    else if (strcmp(child->first_child->name, "shared") == 0)
  		lock->scope = DAV_LOCKSCOPE_SHARED;
  	    if (lock->scope != DAV_LOCKSCOPE_UNKNOWN)
  		continue;
  	}
  
  	if (strcmp(child->name, "owner") == 0 && lock->owner == NULL) {
  	    const char *text;
  
  	    /* quote all the values in the <DAV:owner> element */
  	    dav_quote_xml_elem(p, child);
  
  	    /*
  	    ** Store a full <DAV:owner> element with namespace definitions
  	    ** and an xml:lang definition, if applicable.
  	    */
  	    dav_xml2text(p, child, DAV_X2T_FULL_NS_LANG, doc->namespaces, NULL,
  			 &text, NULL);
  	    lock->owner = text;
  
  	    continue;
  	}
  
  	return dav_new_error(p, HTTP_PRECONDITION_FAILED, 0,
  			     ap_psprintf(p,
  					 "The server cannot satisfy the "
  					 "LOCK request due to an unknown XML "
  					 "element (\"%s\") within the "
  					 "DAV:lockinfo element.",
  					 child->name));
      }
  
      *lock_request = lock;
      return NULL;
  }
  
  /* ---------------------------------------------------------------
  **
  ** General lock functions
  **
  */
  
  /* dav_lock_walker:  Walker callback function to record indirect locks */
  static dav_error * dav_lock_walker(dav_walker_ctx *ctx, int calltype)
  {
      dav_error *err;
  
      /* We don't want to set indirects on the target */
      if ((*ctx->resource->hooks->is_same_resource)(ctx->resource, ctx->root))
  	return NULL;
  
      if ((err = (*ctx->lockdb->hooks->append_locks)(ctx->lockdb, ctx->resource,
  						   1,
  						   ctx->lock)) != NULL) {
  	if (ap_is_HTTP_SERVER_ERROR(err->status)) {
  	    /* ### add a higher-level description? */
  	    return err;
  	}
  
  	/* add to the multistatus response */
  	dav_add_response(ctx, ctx->resource->uri, err->status, NULL);
  
  	/*
  	** ### actually, this is probably wrong: we want to fail the whole
  	** ### LOCK process if something goes bad. maybe the caller should
  	** ### do a dav_unlock() (e.g. a rollback) if any errors occurred.
  	*/
      }
  
      return NULL;
  }
  
  /*
  ** dav_add_lock:  Add a direct lock for resource, and indirect locks for
  **    all children, bounded by depth.
  **    ### assume request only contains one lock
  */
  dav_error * dav_add_lock(request_rec *r, const dav_resource *resource,
  			 dav_lockdb *lockdb, dav_lock *lock,
  			 dav_response **response)
  {
      const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
      dav_error *err;
      int depth = lock->depth;
  
      *response = NULL;
  
      /* Requested lock can be:
       *   Depth: 0   for null resource, existing resource, or existing collection
       *   Depth: Inf for existing collection
       */
  
      /*
      ** 2518 9.2 says to ignore depth if target is not a collection (it has
      **   no internal children); pretend the client gave the correct depth.
      */
      if (!resource->collection) {
  	depth = 0;
      }
  
      /* In all cases, first add direct entry in lockdb */
  
      /*
      ** Append the new (direct) lock to the resource's existing locks.
      **
      ** Note: this also handles locknull resources
      */
      if ((err = (*hooks->append_locks)(lockdb, resource, 0, lock)) != NULL) {
  	/* ### maybe add a higher-level description */
  	return err;
      }
  
      if (depth > 0) {
  	/* Walk existing collection and set indirect locks */
  	dav_walker_ctx ctx = { 0 };
  
  	ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_AUTH;
  	ctx.postfix = 0;
  	ctx.func = dav_lock_walker;
  	ctx.pool = r->pool;
  	ctx.r = r;
          ctx.resource = resource;
  	ctx.lockdb = lockdb;
  	ctx.lock = lock;
  
  	dav_buffer_init(r->pool, &ctx.uri, resource->uri);
  
  	err = (*resource->hooks->walk)(&ctx, DAV_INFINITY);
  	if (err != NULL) {
  	    /* implies a 5xx status code occurred. screw the multistatus */
  	    return err;
  	}
  
  	if (ctx.response != NULL) {
  	    /* manufacture a 207 error for the multistatus response */
  	    *response = ctx.response;
  	    return dav_new_error(r->pool, HTTP_MULTI_STATUS, 0,
  				 "Error(s) occurred on resources during the "
  				 "addition of a depth lock.");
  	}
      }
  
      return NULL;
  }
  
  /*
  ** dav_lock_query:  Opens the lock database. Returns a linked list of
  **    dav_lock structures for all direct locks on path.
  */
  dav_error * dav_lock_query(dav_lockdb *lockdb, const dav_resource *resource,
  			   dav_lock **locks)
  {
      /* If no lock database, return empty result */
      if (lockdb == NULL) {
          *locks = NULL;
          return NULL;
      }
  
      /* ### insert a higher-level description? */
      return (*lockdb->hooks->get_locks)(lockdb, resource,
  				       DAV_GETLOCKS_RESOLVED,
  				       locks);
  }
  
  /* dav_unlock_walker:  Walker callback function to remove indirect locks */
  static dav_error * dav_unlock_walker(dav_walker_ctx *ctx, int calltype)
  {
      dav_error *err;
  
      if ((err = (*ctx->lockdb->hooks->remove_lock)(ctx->lockdb, ctx->resource,
  						  ctx->locktoken)) != NULL) {
  	/* ### should we stop or return a multistatus? looks like STOP */
  	/* ### add a higher-level description? */
  	return err;
      }
  
      return NULL;
  }
  
  /*
  ** dav_get_direct_resource:
  **
  ** Find a lock on the specified resource, then return the resource the
  ** lock was applied to (in other words, given a (possibly) indirect lock,
  ** return the direct lock's corresponding resource).
  **
  ** If the lock is an indirect lock, this usually means traversing up the
  ** namespace [repository] hierarchy. Note that some lock providers may be
  ** able to return this information with a traversal.
  */
  static dav_error * dav_get_direct_resource(pool *p,
  					   dav_lockdb *lockdb,
  					   const dav_locktoken *locktoken,
  					   const dav_resource *resource,
  					   const dav_resource **direct_resource)
  {
      if (lockdb->hooks->lookup_resource != NULL) {
  	return (*lockdb->hooks->lookup_resource)(lockdb, locktoken,
  						 resource, direct_resource);
      }
  
      *direct_resource = NULL;
  
      /* Find the top of this lock-
       * If r->filename's direct   locks include locktoken, use r->filename.
       * If r->filename's indirect locks include locktoken, retry r->filename/..
       * Else fail.
       */
      while (resource != NULL) {
  	dav_error *err;
  	dav_lock *lock;
  
  	/*
  	** Find the lock specified by <locktoken> on <resource>. If it is
  	** an indirect lock, then partial results are okay. We're just
  	** trying to find the thing and know whether it is a direct or
  	** an indirect lock.
  	*/
  	if ((err = (*lockdb->hooks->find_lock)(lockdb, resource, locktoken,
  					       1, &lock)) != NULL) {
  	    /* ### add a higher-level desc? */
  	    return err;
  	}
  
  	/* not found! that's an error. */
  	if (lock == NULL) {
  	    return dav_new_error(p, HTTP_BAD_REQUEST, 0,
  				 "The specified locktoken does not correspond "
  				 "to an existing lock on this resource.");
  	}
  
  	if (lock->rectype == DAV_LOCKREC_DIRECT) {
  	    /* we found the direct lock. return this resource. */
  
  	    *direct_resource = resource;
  	    return NULL;
  	}
  
  	/* the lock was indirect. move up a level in the URL namespace */
  	resource = (*resource->hooks->get_parent_resource)(resource);
      }
  
      return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			 "The lock database is corrupt. A direct lock could "
  			 "not be found for the corresponding indirect lock "
  			 "on this resource.");
  }
  
  /*
  ** dav_unlock:  Removes all direct and indirect locks for r->filename,
  **    with given locktoken.  If locktoken == null_locktoken, all locks
  **    are removed.  If r->filename represents an indirect lock,
  **    we must unlock the appropriate direct lock.
  **    Returns OK or appropriate HTTP_* response and logs any errors.
  **
  ** ### We've already crawled the tree to ensure everything was locked
  **     by us; there should be no need to incorporate a rollback.
  */
  int dav_unlock(request_rec *r, const dav_resource *resource,
  	       const dav_locktoken *locktoken)
  {
      int result;
      dav_lockdb *lockdb;
      const dav_resource *lock_resource = resource;
      const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
      const dav_hooks_repository *repos_hooks = resource->hooks;
      dav_error *err;
  
      /* If no locks provider, we shouldn't have been called */
      if (hooks == NULL) {
  	/* ### map result to something nice; log an error */
  	return HTTP_INTERNAL_SERVER_ERROR;
      }
  
      /* 2518 requires the entire lock to be removed if resource/locktoken
       * point to an indirect lock.  We need resource of the _direct_
       * lock in order to walk down the tree and remove the locks.  So,
       * If locktoken != null_locktoken, 
       *    Walk up the resource hierarchy until we see a direct lock.
       *    Or, we could get the direct lock's db/key, pick out the URL
       *    and do a subrequest.  I think walking up is faster and will work
       *    all the time.
       * Else
       *    Just start removing all locks at and below resource.
       */
  
      if ((err = (*hooks->open_lockdb)(r, 0, 1, &lockdb)) != NULL) {
  	/* ### return err! maybe add a higher-level desc */
  	/* ### map result to something nice; log an error */
  	return HTTP_INTERNAL_SERVER_ERROR;
      }
  
      if (locktoken != NULL
  	&& (err = dav_get_direct_resource(r->pool, lockdb,
  					  locktoken, resource,
  					  &lock_resource)) != NULL) {
  	/* ### add a higher-level desc? */
  	/* ### should return err! */
  	return err->status;
      }
  
      /* At this point, lock_resource/locktoken refers to a direct lock (key), ie
       * the root of a depth > 0 lock, or locktoken is null.
       */
      if ((err = (*hooks->remove_lock)(lockdb, lock_resource,
  				     locktoken)) != NULL) {
  	/* ### add a higher-level desc? */
  	/* ### return err! */
  	return HTTP_INTERNAL_SERVER_ERROR;
      }
  
      if (lock_resource->collection) {
  	dav_walker_ctx ctx = { 0 };
  
  	ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_LOCKNULL;
  	ctx.postfix = 0;
  	ctx.func = dav_unlock_walker;
  	ctx.pool = r->pool;
          ctx.resource = lock_resource;
  	ctx.r = r;
  	ctx.lockdb = lockdb;
  	ctx.locktoken = locktoken;
  
  	dav_buffer_init(r->pool, &ctx.uri, lock_resource->uri);
  
  	err = (*repos_hooks->walk)(&ctx, DAV_INFINITY);
  
  	/* ### fix this! */
  	result = err == NULL ? OK : err->status;
      }
      else
  	result = OK;
  
      (*hooks->close_lockdb)(lockdb);
  
      return result;
  }
  
  /* dav_inherit_walker:  Walker callback function to inherit locks */
  static dav_error * dav_inherit_walker(dav_walker_ctx *ctx, int calltype)
  {
      if (ctx->skip_root
  	&& (*ctx->resource->hooks->is_same_resource)(ctx->resource,
  						     ctx->root)) {
  	return NULL;
      }
  
      /* ### maybe add a higher-level desc */
      return (*ctx->lockdb->hooks->append_locks)(ctx->lockdb, ctx->resource, 1,
  					       ctx->lock);
  }
  
  /*
  ** dav_inherit_locks:  When a resource or collection is added to a collection,
  **    locks on the collection should be inherited to the resource/collection.
  **    (MOVE, MKCOL, etc) Here we propagate any direct or indirect locks from
  **    parent of resource to resource and below.
  */
  static dav_error * dav_inherit_locks(request_rec *r, dav_lockdb *lockdb,
  				     const dav_resource *resource,
  				     int use_parent)
  {
      dav_error *err;
      const dav_resource *which_resource;
      dav_lock *locks;
      dav_lock *scan;
      dav_lock *prev;
      dav_walker_ctx ctx = { 0 };
      const dav_hooks_repository *repos_hooks = resource->hooks;
  
      if (use_parent) {
  	which_resource = (*repos_hooks->get_parent_resource)(resource);
  	if (which_resource == NULL) {
  	    /* ### map result to something nice; log an error */
  	    return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Could not fetch parent resource. Unable to "
  				 "inherit locks from the parent and apply "
  				 "them to this resource.");
  	}
      }
      else {
  	which_resource = resource;
      }
  
      if ((err = (*lockdb->hooks->get_locks)(lockdb, which_resource,
  					   DAV_GETLOCKS_PARTIAL,
  					   &locks)) != NULL) {
  	/* ### maybe add a higher-level desc */
  	return err;
      }
  
      if (locks == NULL) {
  	/* No locks to propagate, just return */
  	return NULL;
      }
  
      /*
      ** (1) Copy all indirect locks from our parent;
      ** (2) Create indirect locks for the depth infinity, direct locks
      **     in our parent.
      **
      ** The append_locks call in the walker callback will do the indirect
      ** conversion, but we need to remove any direct locks that are NOT
      ** depth "infinity".
      */
      for (scan = locks, prev = NULL;
  	 scan != NULL;
  	 prev = scan, scan = scan->next) {
  
  	if (scan->rectype == DAV_LOCKREC_DIRECT
  	    && scan->depth != DAV_INFINITY) {
  
  	    if (prev == NULL)
  		locks = scan->next;
  	    else
  		prev->next = scan->next;
  	}
      }
  
      /* <locks> has all our new locks.  Walk down and propagate them. */
  
      ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_LOCKNULL;
      ctx.postfix = 0;
      ctx.func = dav_inherit_walker;
      ctx.pool = r->pool;
      ctx.resource = resource;
      ctx.r = r;
      ctx.lockdb = lockdb;
      ctx.lock = locks;
      ctx.skip_root = !use_parent;
  
      dav_buffer_init(r->pool, &ctx.uri, resource->uri);
  
      return (*repos_hooks->walk)(&ctx, DAV_INFINITY);
  }
  
  /* ---------------------------------------------------------------
  **
  ** Functions dealing with lock-null resources
  **
  */
  
  /*
  ** dav_get_resource_state:  Returns the state of the resource
  **    r->filename:  DAV_RESOURCE_NULL, DAV_RESOURCE_LOCK_NULL,
  **    or DAV_RESOURCE_EXIST.
  **
  **    Returns DAV_RESOURCE_ERROR if an error occurs.
  */
  int dav_get_resource_state(request_rec *r, const dav_resource *resource)
  {
      const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r);
  
      if (resource->exists)
  	return DAV_RESOURCE_EXISTS;
  
      if (hooks != NULL) {
  	dav_error *err;
  	dav_lockdb *lockdb;
  	int locks_present;
  
  	/*
  	** A locknull resource has the form:
  	**
  	**   known-dir "/" locknull-file
  	**
  	** It would be nice to look into <resource> to verify this form,
  	** but it does not have enough information for us. Instead, we
  	** can look at the path_info. If the form does not match, then
  	** there is no way we could have a locknull resource -- it must
  	** be a plain, null resource.
  	**
  	** Apache sets r->filename to known-dir/unknown-file and r->path_info
  	** to "" for the "proper" case. If anything is in path_info, then
  	** it can't be a locknull resource.
  	**
  	** ### I bet this path_info hack doesn't work for repositories.
  	** ### Need input from repository implementors! What kind of
  	** ### restructure do we need? New provider APIs?
  	*/
  	if (r->path_info != NULL && *r->path_info != '\0') {
  	    return DAV_RESOURCE_NULL;
  	}
  	
          if ((err = (*hooks->open_lockdb)(r, 1, 1, &lockdb)) == NULL) {
  	    /* note that we might see some expired locks... *shrug* */
  	    err = (*hooks->has_locks)(lockdb, resource, &locks_present);
  	    (*hooks->close_lockdb)(lockdb);
          }
  
          if (err != NULL) {
  	    /* ### don't log an error. return err. add higher-level desc. */
  
  	    ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, r,
  		          "Failed to query lock-null status for %s",
  			  r->filename);
  
  	    return DAV_RESOURCE_ERROR;
          }
  
  	if (locks_present)
  	    return DAV_RESOURCE_LOCK_NULL;
      }
  
      return DAV_RESOURCE_NULL;
  }
  
  dav_error * dav_notify_created(request_rec *r,
  			       dav_lockdb *lockdb,
  			       const dav_resource *resource,
  			       int resource_state,
  			       int depth)
  {
      dav_error *err;
  
      if (resource_state == DAV_RESOURCE_LOCK_NULL) {
  
  	/*
  	** The resource is no longer a locknull resource. This will remove
  	** the special marker.
  	**
  	** Note that a locknull resource has already inherited all of the
  	** locks from the parent. We do not need to call dav_inherit_locks.
  	**
  	** NOTE: some lock providers record locks for locknull resources using
  	**       a different key than for regular resources. this will shift
  	**       the lock information between the two key types.
  	*/
  	(void)(*lockdb->hooks->remove_locknull_state)(lockdb, resource);
  
  	/*
  	** There are resources under this one, which are new. We must
  	** propagate the locks down to the new resources.
  	*/
  	if (depth > 0 &&
  	    (err = dav_inherit_locks(r, lockdb, resource, 0)) != NULL) {
  	    /* ### add a higher level desc? */
  	    return err;
  	}
      }
      else if (resource_state == DAV_RESOURCE_NULL) {
  
  	/* ### should pass depth to dav_inherit_locks so that it can
  	** ### optimize for the depth==0 case.
  	*/
  
  	/* this resource should inherit locks from its parent */
  	if ((err = dav_inherit_locks(r, lockdb, resource, 1)) != NULL) {
  
  	    err = dav_push_error(r->pool, err->status, 0,
  				 "The resource was created successfully, but "
  				 "there was a problem inheriting locks from "
  				 "the parent resource.",
  				 err);
  	    return err;
  	}
      }
      /* else the resource already exists and its locks are correct. */
  
      return NULL;
  }
  
  
  
  1.1                  apache-2.0/src/modules/dav/fs/dbm.c
  
  Index: dbm.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV extension module for Apache 1.3.*
  **  - Database support using DBM-style databases,
  **    part of the filesystem repository implementation
  **
  ** Written by Greg Stein, gstein@lyra.org, http://www.lyra.org/
  */
  
  /*
  ** This implementation uses a SDBM or GDBM database per file and directory to
  ** record the properties. These databases are kept in a subdirectory (of
  ** the directory in question or the directory that holds the file in
  ** question) named by the macro DAV_FS_STATE_DIR (.DAV). The filename of the
  ** database is equivalent to the target filename, and is
  ** DAV_FS_STATE_FILE_FOR_DIR (.state_for_dir) for the directory itself.
  */
  
  #ifdef DAV_USE_GDBM
  #include <gdbm.h>
  #else
  #include <fcntl.h>		/* for O_RDONLY, O_WRONLY */
  #include "sdbm/sdbm.h"
  #endif
  
  #include "mod_dav.h"
  #include "dav_fs_repos.h"
  
  
  #ifdef DAV_USE_GDBM
  
  typedef GDBM_FILE dav_dbm_file;
  
  #define DAV_DBM_CLOSE(f)	gdbm_close(f)
  #define DAV_DBM_FETCH(f, k)	gdbm_fetch((f), (k))
  #define DAV_DBM_STORE(f, k, v)	gdbm_store((f), (k), (v), GDBM_REPLACE)
  #define DAV_DBM_DELETE(f, k)	gdbm_delete((f), (k))
  #define DAV_DBM_FIRSTKEY(f)	gdbm_firstkey(f)
  #define DAV_DBM_NEXTKEY(f, k)	gdbm_nextkey((f), (k))
  #define DAV_DBM_CLEARERR(f)	if (0) ; else	/* stop "no effect" warning */
  #define DAV_DBM_FREEDATUM(f, d)	((d).dptr ? free((d).dptr) : 0)
  
  #else
  
  typedef DBM *dav_dbm_file;
  
  #define DAV_DBM_CLOSE(f)	sdbm_close(f)
  #define DAV_DBM_FETCH(f, k)	sdbm_fetch((f), (k))
  #define DAV_DBM_STORE(f, k, v)	sdbm_store((f), (k), (v), DBM_REPLACE)
  #define DAV_DBM_DELETE(f, k)	sdbm_delete((f), (k))
  #define DAV_DBM_FIRSTKEY(f)	sdbm_firstkey(f)
  #define DAV_DBM_NEXTKEY(f, k)	sdbm_nextkey(f)
  #define DAV_DBM_CLEARERR(f)	sdbm_clearerr(f)
  #define DAV_DBM_FREEDATUM(f, d)	if (0) ; else	/* stop "no effect" warning */
  
  #endif
  
  struct dav_db {
      pool *pool;
      dav_dbm_file file;
  };
  
  #define D2G(d)	(*(datum*)&(d))
  
  
  void dav_dbm_get_statefiles(pool *p, const char *fname,
  			    const char **state1, const char **state2)
  {
      char *work;
  
      if (fname == NULL)
  	fname = DAV_FS_STATE_FILE_FOR_DIR;
  
  #ifndef DAV_USE_GDBM
      fname = ap_pstrcat(p, fname, DIRFEXT, NULL);
  #endif
  
      *state1 = fname;
  
  #ifdef DAV_USE_GDBM
      *state2 = NULL;
  #else
      {
  	int extension;
  
  	work = ap_pstrdup(p, fname);
  
  	/* we know the extension is 4 characters -- len(DIRFEXT) */
  	extension = strlen(work) - 4;
  	memcpy(&work[extension], PAGFEXT, 4);
  	*state2 = work;
      }
  #endif
  }
  
  static dav_error * dav_fs_dbm_error(dav_db *db, pool *p)
  {
      int save_errno = errno;
      int errcode;
      const char *errstr;
      dav_error *err;
  
      p = db ? db->pool : p;
  
  #ifdef DAV_USE_GDBM
      errcode = gdbm_errno;
      errstr = gdbm_strerror(gdbm_errno);
  #else
      /* There might not be a <db> if we had problems creating it. */
      errcode = !db || sdbm_error(db->file);
      if (errcode)
  	errstr = "I/O error occurred.";
      else
  	errstr = "No error.";
  #endif
  
      err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, errcode, errstr);
      err->save_errno = save_errno;
      return err;
  }
  
  /* ensure that our state subdirectory is present */
  /* ### does this belong here or in dav_fs_repos.c ?? */
  void dav_fs_ensure_state_dir(pool * p, const char *dirname)
  {
      const char *pathname = ap_pstrcat(p, dirname, "/" DAV_FS_STATE_DIR, NULL);
  
      /* ### do we need to deal with the umask? */
  
      /* just try to make it, ignoring any resulting errors */
      mkdir(pathname, DAV_FS_MODE_DIR);
  }
  
  /* dav_dbm_open_direct:  Opens a *dbm database specified by path.
   *    ro = boolean read-only flag.
   */
  dav_error * dav_dbm_open_direct(pool *p, const char *pathname, int ro,
  				dav_db **pdb)
  {
      dav_dbm_file file;
  
      *pdb = NULL;
  
      /* NOTE: stupid cast to get rid of "const" on the pathname */
  #ifdef DAV_USE_GDBM
      file = gdbm_open((char *) pathname,
  		     0,
  		     ro ? GDBM_READER : GDBM_WRCREAT,
  		     DAV_FS_MODE_FILE,
  		     NULL);
  #else
      file = sdbm_open((char *) pathname,
  		     ro ? O_RDONLY : (O_RDWR | O_CREAT),
  		     DAV_FS_MODE_FILE);
  #endif
  
      /* we can't continue if we couldn't open the file and we need to write */
      if (file == NULL && !ro) {
  	return dav_fs_dbm_error(NULL, p);
      }
  
      /* may be NULL if we tried to open a non-existent db as read-only */
      if (file != NULL) {
  	/* we have an open database... return it */
  	*pdb = ap_pcalloc(p, sizeof(**pdb));
  	(*pdb)->pool = p;
  	(*pdb)->file = file;
      }
  
      return NULL;
  }
  
  static dav_error * dav_dbm_open(pool * p, const dav_resource *resource, int ro,
  				dav_db **pdb)
  {
      const char *dirpath;
      const char *fname;
      const char *pathname;
  
      /* Get directory and filename for resource */
      dav_fs_dir_file_name(resource, &dirpath, &fname);
  
      /* If not opening read-only, ensure the state dir exists */
      if (!ro) {
  	/* ### what are the perf implications of always checking this? */
          dav_fs_ensure_state_dir(p, dirpath);
      }
  
      pathname = ap_pstrcat(p,
  			  dirpath,
  			  "/" DAV_FS_STATE_DIR "/",
  			  fname ? fname : DAV_FS_STATE_FILE_FOR_DIR,
  			  NULL);
  
      /* ### readers cannot open while a writer has this open; we should
         ### perform a few retries with random pauses. */
  
      /* ### do we need to deal with the umask? */
  
      return dav_dbm_open_direct(p, pathname, ro, pdb);
  }
  
  static void dav_dbm_close(dav_db *db)
  {
      DAV_DBM_CLOSE(db->file);
  }
  
  static dav_error * dav_dbm_fetch(dav_db *db, dav_datum key, dav_datum *pvalue)
  {
      *(datum *) pvalue = DAV_DBM_FETCH(db->file, D2G(key));
  
      /* we don't need the error; we have *pvalue to tell */
      DAV_DBM_CLEARERR(db->file);
  
      return NULL;
  }
  
  static dav_error * dav_dbm_store(dav_db *db, dav_datum key, dav_datum value)
  {
      int rv;
  
      rv = DAV_DBM_STORE(db->file, D2G(key), D2G(value));
  
      /* ### fetch more specific error information? */
  
      /* we don't need the error; we have rv to tell */
      DAV_DBM_CLEARERR(db->file);
  
      if (rv == -1) {
  	return dav_fs_dbm_error(db, NULL);
      }
      return NULL;
  }
  
  static dav_error * dav_dbm_delete(dav_db *db, dav_datum key)
  {
      int rv;
  
      rv = DAV_DBM_DELETE(db->file, D2G(key));
  
      /* ### fetch more specific error information? */
  
      /* we don't need the error; we have rv to tell */
      DAV_DBM_CLEARERR(db->file);
  
      if (rv == -1) {
  	return dav_fs_dbm_error(db, NULL);
      }
      return NULL;
  }
  
  static int dav_dbm_exists(dav_db *db, dav_datum key)
  {
      int exists;
  
  #ifdef DAV_USE_GDBM
      exists = gdbm_exists(db->file, D2G(key)) != 0;
  #else
      {
  	datum value = sdbm_fetch(db->file, D2G(key));
  	sdbm_clearerr(db->file);	/* unneeded */
  	exists = value.dptr != NULL;
      }
  #endif
      return exists;
  }
  
  static dav_error * dav_dbm_firstkey(dav_db *db, dav_datum *pkey)
  {
      *(datum *) pkey = DAV_DBM_FIRSTKEY(db->file);
  
      /* we don't need the error; we have *pkey to tell */
      DAV_DBM_CLEARERR(db->file);
  
      return NULL;
  }
  
  static dav_error * dav_dbm_nextkey(dav_db *db, dav_datum *pkey)
  {
      *(datum *) pkey = DAV_DBM_NEXTKEY(db->file, D2G(*pkey));
  
      /* we don't need the error; we have *pkey to tell */
      DAV_DBM_CLEARERR(db->file);
  
      return NULL;
  }
  
  static void dav_dbm_freedatum(dav_db *db, dav_datum data)
  {
      DAV_DBM_FREEDATUM(db, data);
  }
  
  const dav_hooks_db dav_hooks_db_dbm =
  {
      dav_dbm_open,
      dav_dbm_close,
      dav_dbm_fetch,
      dav_dbm_store,
      dav_dbm_delete,
      dav_dbm_exists,
      dav_dbm_firstkey,
      dav_dbm_nextkey,
      dav_dbm_freedatum,
  };
  
  
  
  1.1                  apache-2.0/src/modules/dav/fs/lock.c
  
  Index: lock.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** DAV filesystem lock implementation
  **
  ** Written 06/99 by Keith Wannamaker, wannamak@us.ibm.com
  **
  ** Modified 08/99 by John Vasta, vasta@rational.com. to extract
  ** repository-dependent code from dav_lock.c
  */
  
  #include <sys/stat.h>
  
  #include "httpd.h"
  #include "http_log.h"
  
  #include "mod_dav.h"
  #include "dav_opaquelock.h"
  #include "dav_fs_repos.h"
  
  
  /* ---------------------------------------------------------------
  **
  ** Lock database primitives
  **
  */
  
  /*
  ** LOCK DATABASES
  ** 
  ** Lockdiscovery information is stored in the single lock database specified
  ** by the DAVLockDB directive.  Information about this db is stored in the
  ** global server configuration.
  **
  ** KEY
  **
  ** The database is keyed by a key_type unsigned char (DAV_TYPE_INODE or
  ** DAV_TYPE_FNAME) followed by inode and device number if possible,
  ** otherwise full path (in the case of Win32 or lock-null resources).
  **
  ** VALUE
  **
  ** The value consists of a list of elements.
  **    DIRECT LOCK:     [char  (DAV_LOCK_DIRECT),
  **			char  (dav_lock_scope),
  **			char  (dav_lock_type),
  **			int    depth,
  **			time_t expires,
  **			uuid_t locktoken,
  **			char[] owner,
  **                      char[] auth_user]
  **
  **    INDIRECT LOCK:   [char  (DAV_LOCK_INDIRECT),
  **			uuid_t locktoken,
  **			time_t expires,
  **			int    key_size,
  **			char[] key]
  **       The key is to the collection lock that resulted in this indirect lock
  */
  
  #define DAV_TRUE		1
  #define DAV_FALSE		0
  
  #define DAV_CREATE_LIST		23
  #define DAV_APPEND_LIST		24
  
  /* Stored lock_discovery prefix */
  #define DAV_LOCK_DIRECT		1
  #define DAV_LOCK_INDIRECT	2
  
  #define DAV_TYPE_INODE		10
  #define DAV_TYPE_FNAME		11
  
  
  /* ack. forward declare. */
  static dav_error * dav_fs_remove_locknull_member(pool *p,
  						 const char *filename,
  						 dav_buffer *pbuf);
  
  /*
  ** Use the opaquelock scheme for locktokens
  */
  struct dav_locktoken {
      uuid_t uuid;
  };
  
  
  /* #################################################################
  ** ### keep these structures (internal) or move fully to dav_lock?
  */
  
  /*
  ** We need to reliably size the fixed-length portion of
  ** dav_lock_discovery; best to separate it into another 
  ** struct for a convenient sizeof, unless we pack lock_discovery.
  */
  typedef struct dav_lock_discovery_fixed
  {
      char scope;
      char type;
      int depth;
      time_t timeout;
  } dav_lock_discovery_fixed;
  
  typedef struct dav_lock_discovery
  {
      struct dav_lock_discovery_fixed f;
  
      dav_locktoken *locktoken;
      const char *owner;		/* owner field from activelock */
      const char *auth_user;	/* authenticated user who created the lock */
      struct dav_lock_discovery *next;
  } dav_lock_discovery;
  
  /* Indirect locks represent locks inherited from containing collections.
   * They reference the lock token for the collection the lock is
   * inherited from. A lock provider may also define a key to the
   * inherited lock, for fast datbase lookup. The key is opaque outside
   * the lock provider.
   */
  typedef struct dav_lock_indirect
  {
      dav_locktoken *locktoken;
      dav_datum key;
      struct dav_lock_indirect *next;
      time_t timeout;
  } dav_lock_indirect;
  
  /* ################################################################# */
  
  
  /*
  ** Stored direct lock info - full lock_discovery length:  
  ** prefix + Fixed length + lock token + 2 strings + 2 nulls (one for each string)
  */
  #define dav_size_direct(a)	(1 + sizeof(dav_lock_discovery_fixed) \
  				 + sizeof(uuid_t) \
  				 + ((a)->owner ? strlen((a)->owner) : 0) \
  				 + ((a)->auth_user ? strlen((a)->auth_user) : 0) \
  				 + 2)
  
  /* Stored indirect lock info - lock token and dav_datum */
  #define dav_size_indirect(a)	(1 + sizeof(uuid_t) \
  				 + sizeof(time_t) \
  				 + sizeof(int) + (a)->key.dsize)
  
  /*
  ** The lockdb structure.
  **
  ** The <db> field may be NULL, meaning one of two things:
  ** 1) That we have not actually opened the underlying database (yet). The
  **    <opened> field should be false.
  ** 2) We opened it readonly and it wasn't present.
  **
  ** The delayed opening (determined by <opened>) makes creating a lockdb
  ** quick, while deferring the underlying I/O until it is actually required.
  **
  ** We export the notion of a lockdb, but hide the details of it. Most
  ** implementations will use a database of some kind, but it is certainly
  ** possible that alternatives could be used.
  */
  struct dav_lockdb_private
  {
      request_rec *r;			/* for accessing the uuid state */
      pool *pool;				/* a pool to use */
      const char *lockdb_path;		/* where is the lock database? */
  
      int opened;				/* we opened the database */
      dav_db *db;				/* if non-NULL, the lock database */
  };
  typedef struct
  {
      dav_lockdb pub;
      dav_lockdb_private priv;
  } dav_lockdb_combined;
  
  /*
  ** The private part of the lock structure.
  */
  struct dav_lock_private
  {
      dav_datum key;	/* key into the lock database */
  };
  typedef struct
  {
      dav_lock pub;
      dav_lock_private priv;
      dav_locktoken token;
  } dav_lock_combined;
  
  /*
  ** This must be forward-declared so the open_lockdb function can use it.
  */
  extern const dav_hooks_locks dav_hooks_locks_fs;
  
  
  /* internal function for creating locks */
  static dav_lock *dav_fs_alloc_lock(dav_lockdb *lockdb, dav_datum key,
  				   const dav_locktoken *locktoken)
  {
      dav_lock_combined *comb;
  
      comb = ap_pcalloc(lockdb->info->pool, sizeof(*comb));
      comb->pub.rectype = DAV_LOCKREC_DIRECT;
      comb->pub.info = &comb->priv;
      comb->priv.key = key;
  
      if (locktoken == NULL) {
  	comb->pub.locktoken = &comb->token;
  	dav_create_opaquelocktoken(dav_get_uuid_state(lockdb->info->r),
                                     &comb->token.uuid);
      }
      else {
  	comb->pub.locktoken = locktoken;
      }
  
      return &comb->pub;
  }
  
  /*
  ** dav_fs_parse_locktoken
  **
  ** Parse an opaquelocktoken URI into a locktoken.
  */
  static dav_error * dav_fs_parse_locktoken(
      pool *p,
      const char *char_token,
      dav_locktoken **locktoken_p)
  {
      dav_locktoken *locktoken;
  
      if (strstr(char_token, "opaquelocktoken:") != char_token) {
  	return dav_new_error(p,
  			     HTTP_BAD_REQUEST, DAV_ERR_LOCK_UNK_STATE_TOKEN,
  			     "The lock token uses an unknown State-token "
  			     "format and could not be parsed.");
      }
      char_token += 16;
  
      locktoken = ap_pcalloc(p, sizeof(*locktoken));
      if (dav_parse_opaquelocktoken(char_token, &locktoken->uuid)) {
  	return dav_new_error(p, HTTP_BAD_REQUEST, DAV_ERR_LOCK_PARSE_TOKEN,
  			     "The opaquelocktoken has an incorrect format "
  			     "and could not be parsed.");
      }
      
      *locktoken_p = locktoken;
      return NULL;
  }
  
  /*
  ** dav_fs_format_locktoken
  **
  ** Generate the URI for a locktoken
  */
  static const char *dav_fs_format_locktoken(
      pool *p,
      const dav_locktoken *locktoken)
  {
      const char *uuid_token = dav_format_opaquelocktoken(p, &locktoken->uuid);
      return ap_pstrcat(p, "opaquelocktoken:", uuid_token, NULL);
  }
  
  /*
  ** dav_fs_compare_locktoken
  **
  ** Determine whether two locktokens are the same
  */
  static int dav_fs_compare_locktoken(
      const dav_locktoken *lt1,
      const dav_locktoken *lt2)
  {
      return dav_compare_opaquelocktoken(lt1->uuid, lt2->uuid);
  }
  
  /*
  ** dav_fs_really_open_lockdb:
  **
  ** If the database hasn't been opened yet, then open the thing.
  */
  static dav_error * dav_fs_really_open_lockdb(dav_lockdb *lockdb)
  {
      dav_error *err;
  
      if (lockdb->info->opened)
  	return NULL;
  
      err = dav_dbm_open_direct(lockdb->info->pool,
  			      lockdb->info->lockdb_path,
  			      lockdb->ro,
  			      &lockdb->info->db);
      if (err != NULL) {
  	return dav_push_error(lockdb->info->pool,
  			      HTTP_INTERNAL_SERVER_ERROR,
  			      DAV_ERR_LOCK_OPENDB,
  			      "Could not open the lock database.",
  			      err);
      }
  
      /* all right. it is opened now. */
      lockdb->info->opened = 1;
  
      return NULL;
  }
  
  /*
  ** dav_fs_open_lockdb:
  **
  ** "open" the lock database, as specified in the global server configuration.
  ** If force is TRUE, then the database is opened now, rather than lazily.
  **
  ** Note that only one can be open read/write.
  */
  static dav_error * dav_fs_open_lockdb(request_rec *r, int ro, int force,
  				      dav_lockdb **lockdb)
  {
      dav_lockdb_combined *comb;
  
      comb = ap_pcalloc(r->pool, sizeof(*comb));
      comb->pub.hooks = &dav_hooks_locks_fs;
      comb->pub.ro = ro;
      comb->pub.info = &comb->priv;
      comb->priv.r = r;
      comb->priv.pool = r->pool;
  
      comb->priv.lockdb_path = dav_get_lockdb_path(r);
      if (comb->priv.lockdb_path == NULL) {
  	return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR,
  			     DAV_ERR_LOCK_NO_DB,
  			     "A lock database was not specified with the "
  			     "DAVLockDB directive. One must be specified "
  			     "to use the locking functionality.");
      }
  
      /* done initializing. return it. */
      *lockdb = &comb->pub;
  
      if (force) {
  	/* ### add a higher-level comment? */
  	return dav_fs_really_open_lockdb(*lockdb);
      }
  
      return NULL;
  }
  
  /*
  ** dav_fs_close_lockdb:
  **
  ** Close it. Duh.
  */
  static void dav_fs_close_lockdb(dav_lockdb *lockdb)
  {
      if (lockdb->info->db != NULL)
  	(*dav_hooks_db_dbm.close)(lockdb->info->db);
  }
  
  /*
  ** dav_fs_build_fname_key
  **
  ** Given a pathname, build a DAV_TYPE_FNAME lock database key.
  */
  static dav_datum dav_fs_build_fname_key(pool *p, const char *pathname)
  {
      dav_datum key;
  
      /* ### does this allocation have a proper lifetime? need to check */
      /* ### can we use a buffer for this? */
  
      /* size is TYPE + pathname + null */
      key.dsize = strlen(pathname) + 2;
      key.dptr = ap_palloc(p, key.dsize);
      *key.dptr = DAV_TYPE_FNAME;
      memcpy(key.dptr + 1, pathname, key.dsize - 1);
      if (key.dptr[key.dsize - 2] == '/')
  	key.dptr[--key.dsize - 1] = '\0';
      return key;
  }
  
  /*
  ** dav_fs_build_key:  Given a resource, return a dav_datum key
  **    to look up lock information for this file.
  **
  **    (Win32 or file is lock-null):
  **       dav_datum->dvalue = full path
  **
  **    (non-Win32 and file exists ):
  **       dav_datum->dvalue = inode, dev_major, dev_minor
  */
  static dav_datum dav_fs_build_key(pool *p, const dav_resource *resource)
  {
      const char *file = dav_fs_pathname(resource);
  #ifndef WIN32
      dav_datum key;
      struct stat finfo;
  
      /* ### use lstat() ?? */
      if (stat(file, &finfo) == 0) {
  
  	/* ### can we use a buffer for this? */
  	key.dsize = 1 + sizeof(finfo.st_ino) + sizeof(finfo.st_dev);
  	key.dptr = ap_palloc(p, key.dsize);
  	*key.dptr = DAV_TYPE_INODE;
  	memcpy(key.dptr + 1, &finfo.st_ino, sizeof(finfo.st_ino));
  	memcpy(key.dptr + 1 + sizeof(finfo.st_ino), &finfo.st_dev,
  	       sizeof(finfo.st_dev));
  
  	return key;
      }
  #endif
  
      return dav_fs_build_fname_key(p, file);
  }
  
  /*
  ** dav_fs_lock_expired:  return 1 (true) if the given timeout is in the past
  **    or present (the lock has expired), or 0 (false) if in the future
  **    (the lock has not yet expired).
  */
  static int dav_fs_lock_expired(time_t expires)
  {
      return expires != DAV_TIMEOUT_INFINITE && time(NULL) >= expires;
  }
  
  /*
  ** dav_fs_save_lock_record:  Saves the lock information specified in the
  **    direct and indirect lock lists about path into the lock database.
  **    If direct and indirect == NULL, the key is removed.
  */
  static dav_error * dav_fs_save_lock_record(dav_lockdb *lockdb, dav_datum key,
  					   dav_lock_discovery *direct,
  					   dav_lock_indirect *indirect)
  {
      dav_error *err;
      dav_datum val = { 0 };
      char *ptr;
      dav_lock_discovery *dp = direct;
      dav_lock_indirect *ip = indirect;
  
  #if DAV_DEBUG
      if (lockdb->ro) {
  	return dav_new_error(lockdb->info->pool,
  			     HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "INTERNAL DESIGN ERROR: the lockdb was opened "
  			     "readonly, but an attempt to save locks was "
  			     "performed.");
      }
  #endif
  
      if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
  	/* ### add a higher-level error? */
  	return err;
      }
  
      /* If nothing to save, delete key */
      if (dp == NULL && ip == NULL) {
          /* don't fail if the key is not present */
          /* ### but what about other errors? */
  	(void) (*dav_hooks_db_dbm.remove)(lockdb->info->db, key);
          return NULL;
      }
  		
      while(dp) {
  	val.dsize += dav_size_direct(dp);
  	dp = dp->next;
      }
      while(ip) {
  	val.dsize += dav_size_indirect(ip);
  	ip = ip->next;
      }
  
      /* ### can this be ap_palloc() ? */
      /* ### hmmm.... investigate the use of a buffer here */
      ptr = val.dptr = ap_pcalloc(lockdb->info->pool, val.dsize);
      dp  = direct;
      ip  = indirect;
  
      while(dp) {
  	*ptr++ = DAV_LOCK_DIRECT;	/* Direct lock - lock_discovery struct follows */
  	memcpy(ptr, dp, sizeof(dp->f));	/* Fixed portion of struct */
  	ptr += sizeof(dp->f);
          memcpy(ptr, dp->locktoken, sizeof(*dp->locktoken));
          ptr += sizeof(*dp->locktoken);
  	if (dp->owner == NULL) {
  	    *ptr++ = '\0';
  	}
  	else {
  	    memcpy(ptr, dp->owner, strlen(dp->owner) + 1);	
  	    ptr += strlen(dp->owner) + 1;
  	}
  	if (dp->auth_user == NULL) {
              *ptr++ = '\0';
  	}
  	else {
  	    memcpy(ptr, dp->auth_user, strlen(dp->auth_user) + 1);
  	    ptr += strlen(dp->auth_user) + 1;
  	}
  
  	dp = dp->next;
      }
  
      while(ip) {
  	*ptr++ = DAV_LOCK_INDIRECT;	/* Indirect lock prefix */
  	memcpy(ptr, ip->locktoken, sizeof(*ip->locktoken));	/* Locktoken */
  	ptr += sizeof(*ip->locktoken);
  	memcpy(ptr, &ip->timeout, sizeof(ip->timeout));		/* Expire time */
  	ptr += sizeof(ip->timeout);
  	memcpy(ptr, &ip->key.dsize, sizeof(ip->key.dsize));	/* Size of key */
  	ptr += sizeof(ip->key.dsize);
  	memcpy(ptr, ip->key.dptr, ip->key.dsize);	/* Key data */
  	ptr += ip->key.dsize;
  	ip = ip->next;
      }
  
      if ((err = (*dav_hooks_db_dbm.store)(lockdb->info->db,
  						key, val)) != NULL) {
  	/* ### more details? add an error_id? */
  	return dav_push_error(lockdb->info->pool,
  			      HTTP_INTERNAL_SERVER_ERROR,
  			      DAV_ERR_LOCK_SAVE_LOCK,
  			      "Could not save lock information.",
  			      err);
      }
  
      return NULL;
  }
  
  /*
  ** dav_load_lock_record:  Reads lock information about key from lock db;
  **    creates linked lists of the direct and indirect locks.
  **
  **    If add_method = DAV_APPEND_LIST, the result will be appended to the
  **    head of the direct and indirect lists supplied.
  **
  **    Passive lock removal:  If lock has timed out, it will not be returned.
  **    ### How much "logging" does RFC 2518 require?
  */
  static dav_error * dav_fs_load_lock_record(dav_lockdb *lockdb, dav_datum key,
  					   int add_method,
  					   dav_lock_discovery **direct,
  					   dav_lock_indirect **indirect)
  {
      dav_error *err;
      size_t offset = 0;
      int need_save = DAV_FALSE;
      dav_datum val = { 0 };
      dav_lock_discovery *dp;
      dav_lock_indirect *ip;
      dav_buffer buf = { 0 };
  
      if (add_method != DAV_APPEND_LIST) {
  	*direct = NULL;
  	*indirect = NULL;
      }
  
      if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
  	/* ### add a higher-level error? */
  	return err;
      }
  
      /*
      ** If we opened readonly and the db wasn't there, then there are no
      ** locks for this resource. Just exit.
      */
      if (lockdb->info->db == NULL)
  	return NULL;
  
      if ((err = (*dav_hooks_db_dbm.fetch)(lockdb->info->db, key, &val)) != NULL)
          return err;
  	
      if (!val.dsize)
  	return NULL;
  
      while (offset < val.dsize) {
  	switch (*(val.dptr + offset++)) {
  	case DAV_LOCK_DIRECT:
  	    /* Create and fill a dav_lock_discovery structure */
  
  	    dp = ap_pcalloc(lockdb->info->pool, sizeof(*dp));
  	    memcpy(dp, val.dptr + offset, sizeof(dp->f));
  	    offset += sizeof(dp->f);
              dp->locktoken = ap_palloc(lockdb->info->pool, sizeof(*dp->locktoken));
              memcpy(dp->locktoken, val.dptr + offset, sizeof(*dp->locktoken));
              offset += sizeof(*dp->locktoken);
  	    if (*(val.dptr + offset) == '\0') {
  		++offset;
  	    }
  	    else {
  		dp->owner = ap_pstrdup(lockdb->info->pool, val.dptr + offset);
  		offset += strlen(dp->owner) + 1;
  	    }
  
              if (*(val.dptr + offset) == '\0') {
                  ++offset;
              } 
              else {
                  dp->auth_user = ap_pstrdup(lockdb->info->pool, val.dptr + offset);
                  offset += strlen(dp->auth_user) + 1;
              }
  
  	    if (!dav_fs_lock_expired(dp->f.timeout)) {
  		dp->next = *direct;
  		*direct = dp;
  	    }
  	    else {
  		need_save = DAV_TRUE;
  
  		/* Remove timed-out locknull fm .locknull list */
  		if (*key.dptr == DAV_TYPE_FNAME) {
  		    const char *fname = key.dptr + 1;
  		    struct stat finfo;
  
  		    /* if we don't see the file, then it's a locknull */
  		    if (lstat(fname, &finfo) != 0) {
  			if ((err = dav_fs_remove_locknull_member(lockdb->info->pool, fname, &buf)) != NULL) {
                              /* ### push a higher-level description? */
                              return err;
                          }
  		    }
  		}
  	    }
  	    break;
  
  	case DAV_LOCK_INDIRECT:
  	    /* Create and fill a dav_lock_indirect structure */
  
  	    ip = ap_pcalloc(lockdb->info->pool, sizeof(*ip));
              ip->locktoken = ap_palloc(lockdb->info->pool, sizeof(*ip->locktoken));
  	    memcpy(ip->locktoken, val.dptr + offset, sizeof(*ip->locktoken));
  	    offset += sizeof(*ip->locktoken);
  	    memcpy(&ip->timeout, val.dptr + offset, sizeof(ip->timeout));
  	    offset += sizeof(ip->timeout);
  	    ip->key.dsize = *((int *) (val.dptr + offset));	/* length of datum */
  	    offset += sizeof(ip->key.dsize);
  	    ip->key.dptr = ap_palloc(lockdb->info->pool, ip->key.dsize); 
  	    memcpy(ip->key.dptr, val.dptr + offset, ip->key.dsize);
  	    offset += ip->key.dsize;
  
  	    if (!dav_fs_lock_expired(ip->timeout)) {
  		ip->next = *indirect;
  		*indirect = ip;
  	    }
  	    else {
  		need_save = DAV_TRUE;
  		/* A locknull resource will never be locked indirectly */
  	    }
  
  	    break;
  
  	default:
  	    (*dav_hooks_db_dbm.freedatum)(lockdb->info->db, val);
  
  	    /* ### should use a computed_desc and insert corrupt token data */
  	    --offset;
  	    return dav_new_error(lockdb->info->pool,
  				 HTTP_INTERNAL_SERVER_ERROR,
  				 DAV_ERR_LOCK_CORRUPT_DB,
  				 ap_psprintf(lockdb->info->pool,
  					     "The lock database was found to "
  					     "be corrupt. offset %i, c=%02x",
  					     offset, val.dptr[offset]));
  	}
      }
  
      (*dav_hooks_db_dbm.freedatum)(lockdb->info->db, val);
  
      /* Clean up this record if we found expired locks */
      /*
      ** ### shouldn't do this if we've been opened READONLY. elide the
      ** ### timed-out locks from the response, but don't save that info back
      */
      if (need_save == DAV_TRUE) {
  	return dav_fs_save_lock_record(lockdb, key, *direct, *indirect);
      }
  
      return NULL;
  }
  
  /* resolve <indirect>, returning <*direct> */
  static dav_error * dav_fs_resolve(dav_lockdb *lockdb,
  				  dav_lock_indirect *indirect,
  				  dav_lock_discovery **direct,
  				  dav_lock_discovery **ref_dp,
  				  dav_lock_indirect **ref_ip)
  {
      dav_error *err;
      dav_lock_discovery *dir;
      dav_lock_indirect *ind;
  	
      if ((err = dav_fs_load_lock_record(lockdb, indirect->key,
  				       DAV_CREATE_LIST,
  				       &dir, &ind)) != NULL) {
  	/* ### insert a higher-level description? */
  	return err;
      }
      if (ref_dp != NULL) {
  	*ref_dp = dir;
  	*ref_ip = ind;
      }
  		
      for (; dir != NULL; dir = dir->next) {
  	if (!dav_compare_opaquelocktoken(indirect->locktoken->uuid, 
  					 dir->locktoken->uuid)) {
  	    *direct = dir;
  	    return NULL;
  	}
      }
  
      /* No match found (but we should have found one!) */
  
      /* ### use a different description and/or error ID? */
      return dav_new_error(lockdb->info->pool,
  			 HTTP_INTERNAL_SERVER_ERROR,
  			 DAV_ERR_LOCK_CORRUPT_DB,
  			 "The lock database was found to be corrupt. "
  			 "An indirect lock's direct lock could not "
  			 "be found.");
  }
  
  /* ---------------------------------------------------------------
  **
  ** Property-related lock functions
  **
  */
  
  /*
  ** dav_fs_get_supportedlock:  Returns a static string for all supportedlock
  **    properties. I think we save more returning a static string than
  **    constructing it every time, though it might look cleaner.
  */
  static const char *dav_fs_get_supportedlock(void)
  {
      static const char supported[] = DEBUG_CR
  	"<D:lockentry>" DEBUG_CR
  	"<D:lockscope><D:exclusive/></D:lockscope>" DEBUG_CR
  	"<D:locktype><D:write/></D:locktype>" DEBUG_CR
  	"</D:lockentry>" DEBUG_CR
  	"<D:lockentry>" DEBUG_CR
  	"<D:lockscope><D:shared/></D:lockscope>" DEBUG_CR
  	"<D:locktype><D:write/></D:locktype>" DEBUG_CR
  	"</D:lockentry>" DEBUG_CR;
  
      return supported;
  }
  
  /* ---------------------------------------------------------------
  **
  ** General lock functions
  **
  */
  
  /* ---------------------------------------------------------------
  **
  ** Functions dealing with lock-null resources
  **
  */
  
  /*
  ** dav_fs_load_locknull_list:  Returns a dav_buffer dump of the locknull file
  **    for the given directory.
  */
  static dav_error * dav_fs_load_locknull_list(pool *p, const char *dirpath,
  					     dav_buffer *pbuf) 
  {
      struct stat finfo;
      int fd;
      dav_error *err = NULL;
  
      dav_buffer_init(p, pbuf, dirpath);
  
      if (pbuf->buf[pbuf->cur_len - 1] == '/')
  	pbuf->buf[--pbuf->cur_len] = '\0';
  
      dav_buffer_place(p, pbuf, "/" DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE);
  
      /* reset this in case we leave w/o reading into the buffer */
      pbuf->cur_len = 0;
  
      if ((fd = open(pbuf->buf, O_RDONLY | O_BINARY)) == -1) {
  	return NULL;
      }
  
      if (fstat(fd, &finfo) == -1) {
  	err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			    ap_psprintf(p,
  					"Opened but could not stat file %s",
  					pbuf->buf));
  	goto loaderror;
      }
  
      dav_set_bufsize(p, pbuf, finfo.st_size);
      if (read(fd, pbuf->buf, finfo.st_size) != finfo.st_size) {
  	err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			    ap_psprintf(p,
  					"Failure reading locknull file "
  					"for %s", dirpath));
  
  	/* just in case the caller disregards the returned error */
  	pbuf->cur_len = 0;
  	goto loaderror;
      }
  
    loaderror:
      close(fd);
      return err;
  }
  
  /*
  ** dav_fs_save_locknull_list:  Saves contents of pbuf into the
  **    locknull file for dirpath.
  */
  static dav_error * dav_fs_save_locknull_list(pool *p, const char *dirpath,
  					     dav_buffer *pbuf)
  {
      const char *pathname;
      int fd;
      dav_error *err = NULL;
  
      if (pbuf->buf == NULL)
  	return NULL;
  
      dav_fs_ensure_state_dir(p, dirpath);
      pathname = ap_pstrcat(p,
  			  dirpath,
  			  dirpath[strlen(dirpath) - 1] == '/' ? "" : "/",
  			  DAV_FS_STATE_DIR "/" DAV_FS_LOCK_NULL_FILE,
  			  NULL);
  
      if (pbuf->cur_len == 0) {
  	/* delete the file if cur_len == 0 */
  	if (remove(pathname) != 0) {
  	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 ap_psprintf(p,
  					     "Error removing %s", pathname));
  	}
  	return NULL;
      }
  
      if ((fd = open(pathname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
  		   DAV_FS_MODE_FILE)) == -1) {
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     ap_psprintf(p,
  					 "Error opening %s for writing",
  					 pathname));
      }
  
      if (write(fd, pbuf->buf, pbuf->cur_len) != pbuf->cur_len) {
  	err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			    ap_psprintf(p,
  					"Error writing %i bytes to %s",
  					pbuf->cur_len, pathname));
      }
  
      close(fd);
      return err;
  }
  
  /*
  ** dav_fs_remove_locknull_member:  Removes filename from the locknull list
  **    for directory path.
  */
  static dav_error * dav_fs_remove_locknull_member(pool *p, const char *filename,
  						 dav_buffer *pbuf)
  {
      dav_error *err;
      size_t len;
      size_t scanlen;
      char *scan;
      const char *scanend;
      char *dirpath = ap_pstrdup(p, filename);
      char *fname = strrchr(dirpath, '/');
      int dirty = 0;
  
      if (fname != NULL)
  	*fname++ = '\0';
      else
  	fname = dirpath;
      len = strlen(fname) + 1;
  
      if ((err = dav_fs_load_locknull_list(p, dirpath, pbuf)) != NULL) {
  	/* ### add a higher level description? */
  	return err;
      }
  
      for (scan = pbuf->buf, scanend = scan + pbuf->cur_len;
  	 scan < scanend;
  	 scan += scanlen) {
  	scanlen = strlen(scan) + 1;
  	if (len == scanlen && memcmp(fname, scan, scanlen) == 0) {
  	    pbuf->cur_len -= scanlen;
  	    memmove(scan, scan + scanlen, scanend - (scan + scanlen));
  	    dirty = 1;
  	    break;
  	}
      }
  
      if (dirty) {
  	if ((err = dav_fs_save_locknull_list(p, dirpath, pbuf)) != NULL) {
  	    /* ### add a higher level description? */
  	    return err;
  	}
      }
  
      return NULL;
  }
  
  /* Note: used by dav_fs_repos.c */
  dav_error * dav_fs_get_locknull_members(
      const dav_resource *resource,
      dav_buffer *pbuf)
  {
      const char *dirpath;
  
      dav_fs_dir_file_name(resource, &dirpath, NULL);
      return dav_fs_load_locknull_list(dav_fs_pool(resource), dirpath, pbuf);
  }
  
  /* ### fold into append_lock? */
  /* ### take an optional buf parameter? */
  static dav_error * dav_fs_add_locknull_state(
      dav_lockdb *lockdb,
      const dav_resource *resource)
  {
      dav_buffer buf = { 0 };
      pool *p = lockdb->info->pool;
      const char *dirpath;
      const char *fname;
      dav_error *err;
  
      dav_fs_dir_file_name(resource, &dirpath, &fname);
  
      if ((err = dav_fs_load_locknull_list(p, dirpath, &buf)) != NULL) {
          return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			      "Could not load .locknull file.", err);
      }
  
      dav_buffer_append(p, &buf, fname);
      buf.cur_len++;	/* we want the null-term here */
  
      if ((err = dav_fs_save_locknull_list(p, dirpath, &buf)) != NULL) {
          return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			      "Could not save .locknull file.", err);
      }
  
      return NULL;
  }
  
  /*
  ** dav_fs_remove_locknull_state:  Given a request, check to see if r->filename
  **    is/was a lock-null resource.  If so, return it to an existant state.
  **
  **    ### this function is broken... it doesn't check!
  **
  **    In this implementation, this involves two things:
  **    (a) remove it from the list in the appropriate .DAV/locknull file
  **    (b) on *nix, convert the key from a filename to an inode.
  */
  static dav_error * dav_fs_remove_locknull_state(
      dav_lockdb *lockdb,
      const dav_resource *resource)
  {
      dav_buffer buf = { 0 };
      dav_error *err;
      pool *p = lockdb->info->pool;
      const char *pathname = dav_fs_pathname(resource);
  
      if ((err = dav_fs_remove_locknull_member(p, pathname, &buf)) != NULL) {
  	/* ### add a higher-level description? */
  	return err;
      }
  
  #ifndef WIN32
      {
  	dav_lock_discovery *ld;
  	dav_lock_indirect  *id;
  	dav_datum key;
  
  	/*
  	** Fetch the lock(s) that made the resource lock-null. Remove
  	** them under the filename key. Obtain the new inode key, and
  	** save the same lock information under it.
  	*/
  	key = dav_fs_build_fname_key(p, pathname);
  	if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
  					   &ld, &id)) != NULL) {
  	    /* ### insert a higher-level error description */
  	    return err;
  	}
  
  	if ((err = dav_fs_save_lock_record(lockdb, key, NULL, NULL)) != NULL) {
  	    /* ### insert a higher-level error description */
  	    return err;
          }
  
  	key = dav_fs_build_key(p, resource);
  	if ((err = dav_fs_save_lock_record(lockdb, key, ld, id)) != NULL) {
  	    /* ### insert a higher-level error description */
  	    return err;
          }
      }
  #endif
  
      return NULL;
  }
  
  static dav_error * dav_fs_create_lock(dav_lockdb *lockdb,
  				      const dav_resource *resource,
  				      dav_lock **lock)
  {
      dav_datum key;
  
      key = dav_fs_build_key(lockdb->info->pool, resource);
  
      *lock = dav_fs_alloc_lock(lockdb,
  			      key,
  			      NULL);
  
      (*lock)->is_locknull = !resource->exists;
  
      return NULL;
  }
  
  static dav_error * dav_fs_get_locks(dav_lockdb *lockdb,
  				    const dav_resource *resource,
  				    int calltype,
  				    dav_lock **locks)
  {
      pool *p = lockdb->info->pool;
      dav_datum key;
      dav_error *err;
      dav_lock *lock = NULL;
      dav_lock *newlock;
      dav_lock_discovery *dp;
      dav_lock_indirect *ip;
  
  #if DAV_DEBUG
      if (calltype == DAV_GETLOCKS_COMPLETE) {
  	return dav_new_error(lockdb->info->pool,
  			     HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "INTERNAL DESIGN ERROR: DAV_GETLOCKS_COMPLETE "
  			     "is not yet supported");
      }
  #endif
  
      key = dav_fs_build_key(p, resource);
      if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
  				       &dp, &ip)) != NULL) {
  	/* ### push a higher-level desc? */
  	return err;
      }
  
      /* copy all direct locks to the result list */
      for (; dp != NULL; dp = dp->next) {
  	newlock = dav_fs_alloc_lock(lockdb, key, dp->locktoken);
  	newlock->is_locknull = !resource->exists;
  	newlock->scope = dp->f.scope;
  	newlock->type = dp->f.type;
  	newlock->depth = dp->f.depth;
  	newlock->timeout = dp->f.timeout;
  	newlock->owner = dp->owner;
          newlock->auth_user = dp->auth_user;
  
  	/* hook into the result list */
  	newlock->next = lock;
  	lock = newlock;
      }
  
      /* copy all the indirect locks to the result list. resolve as needed. */
      for (; ip != NULL; ip = ip->next) {
  	newlock = dav_fs_alloc_lock(lockdb, ip->key, ip->locktoken);
  	newlock->is_locknull = !resource->exists;
  
  	if (calltype == DAV_GETLOCKS_RESOLVED) {
  	    if ((err = dav_fs_resolve(lockdb, ip, &dp, NULL, NULL)) != NULL) {
  		/* ### push a higher-level desc? */
  		return err;
  	    }
  
  	    newlock->scope = dp->f.scope;
  	    newlock->type = dp->f.type;
  	    newlock->depth = dp->f.depth;
  	    newlock->timeout = dp->f.timeout;
  	    newlock->owner = dp->owner;
              newlock->auth_user = dp->auth_user;
  	}
  	else {
  	    /* DAV_GETLOCKS_PARTIAL */
  	    newlock->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
  	}
  
  	/* hook into the result list */
  	newlock->next = lock;
  	lock = newlock;
      }
  
      *locks = lock;
      return NULL;
  }
  
  static dav_error * dav_fs_find_lock(dav_lockdb *lockdb,
  				    const dav_resource *resource,
  				    const dav_locktoken *locktoken,
  				    int partial_ok,
  				    dav_lock **lock)
  {
      dav_error *err;
      dav_datum key;
      dav_lock_discovery *dp;
      dav_lock_indirect *ip;
  
      *lock = NULL;
  
      key = dav_fs_build_key(lockdb->info->pool, resource);
      if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
  				       &dp, &ip)) != NULL) {
  	/* ### push a higher-level desc? */
  	return err;
      }
  
      for (; dp != NULL; dp = dp->next) {
  	if (!dav_compare_opaquelocktoken(locktoken->uuid,
  					 dp->locktoken->uuid)) {
  	    *lock = dav_fs_alloc_lock(lockdb, key, locktoken);
  	    (*lock)->is_locknull = !resource->exists;
  	    (*lock)->scope = dp->f.scope;
  	    (*lock)->type = dp->f.type;
  	    (*lock)->depth = dp->f.depth;
  	    (*lock)->timeout = dp->f.timeout;
  	    (*lock)->owner = dp->owner;
              (*lock)->auth_user = dp->auth_user;
  	    return NULL;
  	}
      }
  
      for (; ip != NULL; ip = ip->next) {
  	if (!dav_compare_opaquelocktoken(locktoken->uuid,
  					 ip->locktoken->uuid)) {
  	    *lock = dav_fs_alloc_lock(lockdb, ip->key, locktoken);
  	    (*lock)->is_locknull = !resource->exists;
  
  	    /* ### nobody uses the resolving right now! */
  	    if (partial_ok) {
  		(*lock)->rectype = DAV_LOCKREC_INDIRECT_PARTIAL;
  	    }
  	    else {
  		(*lock)->rectype = DAV_LOCKREC_INDIRECT;
  		if ((err = dav_fs_resolve(lockdb, ip, &dp,
  					  NULL, NULL)) != NULL) {
  		    /* ### push a higher-level desc? */
  		    return err;
  		}
  		(*lock)->scope = dp->f.scope;
  		(*lock)->type = dp->f.type;
  		(*lock)->depth = dp->f.depth;
  		(*lock)->timeout = dp->f.timeout;
  		(*lock)->owner = dp->owner;
                  (*lock)->auth_user = dp->auth_user;
  	    }
  	    return NULL;
  	}
      }
  
      return NULL;
  }
  
  static dav_error * dav_fs_has_locks(dav_lockdb *lockdb,
  				    const dav_resource *resource,
  				    int *locks_present)
  {
      dav_error *err;
      dav_datum key;
  
      *locks_present = 0;
  
      if ((err = dav_fs_really_open_lockdb(lockdb)) != NULL) {
  	/* ### insert a higher-level error description */
  	return err;
      }
  
      /*
      ** If we opened readonly and the db wasn't there, then there are no
      ** locks for this resource. Just exit.
      */
      if (lockdb->info->db == NULL)
  	return NULL;
  
      key = dav_fs_build_key(lockdb->info->pool, resource);
  
      *locks_present = (*dav_hooks_db_dbm.exists)(lockdb->info->db, key);
  
      return NULL;
  }
  
  static dav_error * dav_fs_append_locks(dav_lockdb *lockdb,
  				       const dav_resource *resource,
  				       int make_indirect,
  				       const dav_lock *lock)
  {
      pool *p = lockdb->info->pool;
      dav_error *err;
      dav_lock_indirect *ip;
      dav_lock_discovery *dp;
      dav_datum key;
  
      key = dav_fs_build_key(lockdb->info->pool, resource);
      if ((err = dav_fs_load_lock_record(lockdb, key, 0, &dp, &ip)) != NULL) {
  	/* ### maybe add in a higher-level description */
  	return err;
      }
  
      /*
      ** ### when we store the lock more directly, we need to update
      ** ### lock->rectype and lock->is_locknull
      */
  
      if (make_indirect) {
  	for (; lock != NULL; lock = lock->next) {
  
  	    /* ### this works for any <lock> rectype */
  	    dav_lock_indirect *newi = ap_pcalloc(p, sizeof(*newi));
  
  	    /* ### shut off the const warning for now */
  	    newi->locktoken = (dav_locktoken *)lock->locktoken;
  	    newi->timeout   = lock->timeout;
  	    newi->key       = lock->info->key;
  	    newi->next      = ip;
  	    ip              = newi;
  	}
      }
      else {
  	for (; lock != NULL; lock = lock->next) {
  	    /* create and link in the right kind of lock */
  
  	    if (lock->rectype == DAV_LOCKREC_DIRECT) {
  		dav_lock_discovery *newd = ap_pcalloc(p, sizeof(*newd));
  
  		newd->f.scope = lock->scope;
  		newd->f.type = lock->type;
  		newd->f.depth = lock->depth;
  		newd->f.timeout = lock->timeout;
  		/* ### shut off the const warning for now */
  		newd->locktoken = (dav_locktoken *)lock->locktoken;
  		newd->owner = lock->owner;
                  newd->auth_user = lock->auth_user;
  		newd->next = dp;
  		dp = newd;
  	    }
  	    else {
  		/* DAV_LOCKREC_INDIRECT(_PARTIAL) */
  
  		dav_lock_indirect *newi = ap_pcalloc(p, sizeof(*newi));
  
  		/* ### shut off the const warning for now */
  		newi->locktoken = (dav_locktoken *)lock->locktoken;
  		newi->key       = lock->info->key;
  		newi->next      = ip;
  		ip              = newi;
  	    }
  	}
      }
  
      if ((err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
  	/* ### maybe add a higher-level description */
  	return err;
      }
  
      /* we have a special list for recording locknull resources */
      /* ### ack! this can add two copies to the locknull list */
      if (!resource->exists
  	&& (err = dav_fs_add_locknull_state(lockdb, resource)) != NULL) {
  	/* ### maybe add a higher-level description */
  	return err;
      }
  
      return NULL;
  }
  
  static dav_error * dav_fs_remove_lock(dav_lockdb *lockdb,
  				      const dav_resource *resource,
  				      const dav_locktoken *locktoken)
  {
      dav_error *err;
      dav_buffer buf = { 0 };
      dav_lock_discovery *dh = NULL;
      dav_lock_indirect *ih = NULL;
      dav_datum key;
  
      key = dav_fs_build_key(lockdb->info->pool, resource);
  
      if (locktoken != NULL) {
  	dav_lock_discovery *dp;
  	dav_lock_discovery *dprev = NULL;
  	dav_lock_indirect *ip;
  	dav_lock_indirect *iprev = NULL;
  
  	if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
  					   &dh, &ih)) != NULL) {
  	    /* ### maybe add a higher-level description */
  	    return err;
  	}
  
  	for (dp = dh; dp != NULL; dp = dp->next) {
  	    if (dav_compare_opaquelocktoken(locktoken->uuid,
  					    dp->locktoken->uuid) == 0) {
  		if (dprev)
  		    dprev->next = dp->next;
  		else
  		    dh = dh->next;
  	    }
  	    dprev = dp;
  	}
  
  	for (ip = ih; ip != NULL; ip = ip->next) {
  	    if (dav_compare_opaquelocktoken(locktoken->uuid,
  					    ip->locktoken->uuid) == 0) {
  		if (iprev)
  		    iprev->next = ip->next;
  		else
  		    ih = ih->next;
  	    }
  	    iprev = ip;
  	}
  
      }
  
      /* save the modified locks, or remove all locks (dh=ih=NULL). */
      if ((err = dav_fs_save_lock_record(lockdb, key, dh, ih)) != NULL) {
          /* ### maybe add a higher-level description */
          return err;
      }
  
      /*
      ** If this resource is a locknull resource AND no more locks exist,
      ** then remove the locknull member.
      **
      ** Note: remove_locknull_state() attempts to convert a locknull member
      **       to a real member. In this case, all locks are gone, so the
      **       locknull resource returns to the null state (ie. doesn't exist),
      **       so there is no need to update the lockdb (and it won't find
      **       any because a precondition is that none exist).
      */
      if (!resource->exists && dh == NULL && ih == NULL
  	&& (err = dav_fs_remove_locknull_member(lockdb->info->pool,
  						dav_fs_pathname(resource),
  						&buf)) != NULL) {
  	/* ### maybe add a higher-level description */
  	return err;
      }
  
      return NULL;
  }
  
  static int dav_fs_do_refresh(dav_lock_discovery *dp,
  			     const dav_locktoken_list *ltl,
  			     time_t new_time)
  {
      int dirty = 0;
  
      for (; ltl != NULL; ltl = ltl->next) {
  	if (dav_compare_opaquelocktoken(dp->locktoken->uuid,
  					ltl->locktoken->uuid) == 0)
  	{
  	    dp->f.timeout = new_time;
  	    dirty = 1;
  	}
      }
  
      return dirty;
  }
  
  static dav_error * dav_fs_refresh_locks(dav_lockdb *lockdb,
  					const dav_resource *resource,
  					const dav_locktoken_list *ltl,
  					time_t new_time,
  					dav_lock **locks)
  {
      dav_error *err;
      dav_datum key;
      dav_lock_discovery *dp;
      dav_lock_discovery *dp_scan;
      dav_lock_indirect *ip;
      int dirty = 0;
      dav_lock *newlock;
  
      *locks = NULL;
  
      key = dav_fs_build_key(lockdb->info->pool, resource);
      if ((err = dav_fs_load_lock_record(lockdb, key, DAV_CREATE_LIST,
  				       &dp, &ip)) != NULL) {
  	/* ### maybe add in a higher-level description */
  	return err;
      }
  
      /* ### we should be refreshing direct AND (resolved) indirect locks! */
  
      /* refresh all of the direct locks on this resource */
      for (dp_scan = dp; dp_scan != NULL; dp_scan = dp_scan->next) {
  	if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
  	    /* the lock was refreshed. return the lock. */
  	    newlock = dav_fs_alloc_lock(lockdb, key, dp_scan->locktoken);
  	    newlock->is_locknull = !resource->exists;
  	    newlock->scope = dp_scan->f.scope;
  	    newlock->type = dp_scan->f.type;
  	    newlock->depth = dp_scan->f.depth;
  	    newlock->timeout = dp_scan->f.timeout;
  	    newlock->owner = dp_scan->owner;
              newlock->auth_user = dp_scan->auth_user;
  
  	    newlock->next = *locks;
  	    *locks = newlock;
  
  	    dirty = 1;
  	}
      }
  
      /* if we refreshed any locks, then save them back. */
      if (dirty
  	&& (err = dav_fs_save_lock_record(lockdb, key, dp, ip)) != NULL) {
  	/* ### maybe add in a higher-level description */
  	return err;
      }
  
      /* for each indirect lock, find its direct lock and refresh it. */
      for (; ip != NULL; ip = ip->next) {
  	dav_lock_discovery *ref_dp;
  	dav_lock_indirect *ref_ip;
  
  	if ((err = dav_fs_resolve(lockdb, ip, &dp_scan,
  				  &ref_dp, &ref_ip)) != NULL) {
  	    /* ### push a higher-level desc? */
  	    return err;
  	}
  	if (dav_fs_do_refresh(dp_scan, ltl, new_time)) {
  	    /* the lock was refreshed. return the lock. */
  	    newlock = dav_fs_alloc_lock(lockdb, ip->key, dp->locktoken);
  	    newlock->is_locknull = !resource->exists;
  	    newlock->scope = dp->f.scope;
  	    newlock->type = dp->f.type;
  	    newlock->depth = dp->f.depth;
  	    newlock->timeout = dp->f.timeout;
  	    newlock->owner = dp->owner;
              newlock->auth_user = dp_scan->auth_user;
  
  	    newlock->next = *locks;
  	    *locks = newlock;
  
  	    /* save the (resolved) direct lock back */
  	    if ((err = dav_fs_save_lock_record(lockdb, ip->key, ref_dp,
  					       ref_ip)) != NULL) {
  		/* ### push a higher-level desc? */
  		return err;
  	    }
  	}
      }
  
      return NULL;
  }
  
  
  const dav_hooks_locks dav_hooks_locks_fs =
  {
      dav_fs_get_supportedlock,
      dav_fs_parse_locktoken,
      dav_fs_format_locktoken,
      dav_fs_compare_locktoken,
      dav_fs_open_lockdb,
      dav_fs_close_lockdb,
      dav_fs_remove_locknull_state,
      dav_fs_create_lock,
      dav_fs_get_locks,
      dav_fs_find_lock,
      dav_fs_has_locks,
      dav_fs_append_locks,
      dav_fs_remove_lock,
      dav_fs_refresh_locks,
      NULL, /* get_resource */
  };
  
  
  
  1.1                  apache-2.0/src/modules/dav/fs/repos.c
  
  Index: repos.c
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  **
  */
  
  /*
  ** DAV filesystem-based repository provider
  **
  ** Written 08/99 by John Vasta, vasta@rational.com, by separating
  ** mod_dav into repository-independent and provider modules.
  */
  
  #include <string.h>
  
  #include "httpd.h"
  #include "http_log.h"
  #include "http_protocol.h"	/* for ap_set_* (in dav_fs_set_headers) */
  #include "http_request.h"       /* for ap_update_mtime() */
  
  #include "mod_dav.h"
  #include "dav_fs_repos.h"
  
  
  /* to assist in debugging mod_dav's GET handling */
  #define DEBUG_GET_HANDLER       0
  #define DEBUG_PATHNAME_STYLE    0
  
  #define DAV_FS_COPY_BLOCKSIZE	16384	/* copy 16k at a time */
  
  /* context needed to identify a resource */
  struct dav_resource_private {
      pool *pool;             /* memory storage pool associated with request */
      const char *pathname;   /* full pathname to resource */
      struct stat finfo;      /* filesystem info */
  };
  
  /* private context for doing a filesystem walk */
  typedef struct {
      dav_walker_ctx *wctx;
  
      dav_resource res1;
      dav_resource res2;
      dav_resource_private info1;
      dav_resource_private info2;
      dav_buffer path1;
      dav_buffer path2;
  
      dav_buffer locknull_buf;
  
  } dav_fs_walker_context;
  
  /* pull this in from the other source file */
  extern const dav_hooks_locks dav_hooks_locks_fs;
  
  /* forward-declare this sucker */
  static const dav_hooks_repository dav_hooks_repository_fs;
  
  /*
  ** The Provider ID is used to differentiate "logical" providers that use
  ** the same set of hook functions. Essentially, the ID is an instance
  ** handle and the hooks are a vtable.
  **
  ** In this module, we only have a single provider for each type, so we
  ** actually ignore the Provider ID.
  */
  #define DAV_FS_PROVIDER_ID	0
  
  /*
  ** The namespace URIs that we use. This list and the enumeration must
  ** stay in sync.
  */
  static const char * const dav_fs_namespace_uris[] =
  {
      "DAV:",
      "http://apache.org/dav/props/",
  
      NULL	/* sentinel */
  };
  enum {
      DAV_FS_URI_DAV,		/* the DAV: namespace URI */
      DAV_FS_URI_MYPROPS		/* the namespace URI for our custom props */
  };
  
  /*
  ** The properties that we define.
  */
  enum {
      /* using DAV_FS_URI_DAV */
      DAV_PROPID_FS_creationdate = DAV_PROPID_FS,
      DAV_PROPID_FS_displayname,
      DAV_PROPID_FS_getcontentlength,
      DAV_PROPID_FS_getetag,
      DAV_PROPID_FS_getlastmodified,
      DAV_PROPID_FS_source,
  
      /* using DAV_FS_URI_MYPROPS */
      DAV_PROPID_FS_executable
  };
  /* NOTE: the magic "200" is derived from the ranges in mod_dav.h */
  #define DAV_PROPID_FS_OURS(id)	(DAV_PROPID_FS <= (id) && \
  				 (id) < DAV_PROPID_FS + 200)
  
  typedef struct {
      int ns;
      const char * name;
  
      int propid;
  } dav_fs_liveprop_name;
  
  static const dav_fs_liveprop_name dav_fs_props[] =
  {
      { DAV_FS_URI_DAV,     "creationdate",     DAV_PROPID_FS_creationdate },
      { DAV_FS_URI_DAV,     "getcontentlength", DAV_PROPID_FS_getcontentlength },
      { DAV_FS_URI_DAV,     "getetag",          DAV_PROPID_FS_getetag },
      { DAV_FS_URI_DAV,     "getlastmodified",  DAV_PROPID_FS_getlastmodified },
  
      { DAV_FS_URI_MYPROPS, "executable",       DAV_PROPID_FS_executable },
        
      /* ### these aren't FS specific */
      { DAV_FS_URI_DAV,     "displayname",      DAV_PROPID_FS_displayname },
      { DAV_FS_URI_DAV,     "source",           DAV_PROPID_FS_source },
  
      { 0 }	/* sentinel */
  };
  
  
  /* define the dav_stream structure for our use */
  struct dav_stream {
      pool *p;
      int fd;
      const char *pathname;	/* we may need to remove it at close time */
  };
  
  /* forward declaration for internal treewalkers */
  static dav_error * dav_fs_walk(dav_walker_ctx *wctx, int depth);
  
  /* --------------------------------------------------------------------
  **
  ** PRIVATE REPOSITORY FUNCTIONS
  */
  pool *dav_fs_pool(const dav_resource *resource)
  {
      return resource->info->pool;
  }
  
  const char *dav_fs_pathname(const dav_resource *resource)
  {
      return resource->info->pathname;
  }
  
  void dav_fs_dir_file_name(
      const dav_resource *resource,
      const char **dirpath_p,
      const char **fname_p)
  {
      dav_resource_private *ctx = resource->info;
  
      if (resource->collection) {
          *dirpath_p = ctx->pathname;
          if (fname_p != NULL)
              *fname_p = NULL;
      }
      else {
          char *dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
          size_t dirlen = strlen(dirpath);
  
          if (fname_p != NULL)
              *fname_p = ctx->pathname + dirlen;
          *dirpath_p = dirpath;
  
          /* remove trailing slash from dirpath, unless it's the root dir */
          /* ### Win32 check */
          if (dirlen > 1 && dirpath[dirlen - 1] == '/') {
              dirpath[dirlen - 1] = '\0';
          }
      }
  }
  
  /* Note: picked up from ap_gm_timestr_822() */
  /* NOTE: buf must be at least DAV_TIMEBUF_SIZE chars in size */
  static void dav_format_time(int style, time_t sec, char *buf)
  {
      struct tm *tms;
  
      tms = gmtime(&sec);
  
      if (style == DAV_STYLE_ISO8601) {
  	/* ### should we use "-00:00" instead of "Z" ?? */
  
  	/* 20 chars plus null term */
  	sprintf(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
  		tms->tm_year + 1900, tms->tm_mon + 1, tms->tm_mday,
  		tms->tm_hour, tms->tm_min, tms->tm_sec);
          return;
      }
  
      /* RFC 822 date format; as strftime '%a, %d %b %Y %T GMT' */
  
      /* 29 chars plus null term */
      sprintf(buf,
  	    "%s, %.2d %s %d %.2d:%.2d:%.2d GMT",
  	    ap_day_snames[tms->tm_wday],
  	    tms->tm_mday, ap_month_snames[tms->tm_mon],
  	    tms->tm_year + 1900,
  	    tms->tm_hour, tms->tm_min, tms->tm_sec);
  }
  
  static int dav_sync_write(int fd, const char *buf, ssize_t bufsize)
  {
      ssize_t amt;
  
      do {
  	amt = write(fd, buf, bufsize);
  	if (amt > 0) {
  	    bufsize -= amt;
  	    buf += amt;
  	}
      } while (amt > 0 && bufsize > 0);
  
      return amt < 0 ? -1 : 0;
  }
  
  static dav_error * dav_fs_copymove_file(
      int is_move,
      pool * p,
      const char *src,
      const char *dst,
      dav_buffer *pbuf)
  {
      dav_buffer work_buf = { 0 };
      int fdi;
      int fdo;
  
      if (pbuf == NULL)
  	pbuf = &work_buf;
  
      dav_set_bufsize(p, pbuf, DAV_FS_COPY_BLOCKSIZE);
  
      if ((fdi = open(src, O_RDONLY | O_BINARY)) == -1) {
  	/* ### use something besides 500? */
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "Could not open file for reading");
      }
  
      /* ### do we need to deal with the umask? */
      if ((fdo = open(dst, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY,
                      DAV_FS_MODE_FILE)) == -1) {
  	close(fdi);
  
  	/* ### use something besides 500? */
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "Could not open file for writing");
      }
  
      while (1) {
  	ssize_t len = read(fdi, pbuf->buf, DAV_FS_COPY_BLOCKSIZE);
  
  	if (len == -1) {
  	    close(fdi);
  	    close(fdo);
  
  	    if (remove(dst) != 0) {
  		/* ### ACK! Inconsistent state... */
  
  		/* ### use something besides 500? */
  		return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				     "Could not delete output after read "
  				     "failure. Server is now in an "
  				     "inconsistent state.");
  	    }
  
  	    /* ### use something besides 500? */
  	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Could not read input file");
  	}
  	if (len == 0)
  	    break;
  
          if (dav_sync_write(fdo, pbuf->buf, len) != 0) {
              int save_errno = errno;
  
  	    close(fdi);
  	    close(fdo);
  
  	    if (remove(dst) != 0) {
  		/* ### ACK! Inconsistent state... */
  
  		/* ### use something besides 500? */
  		return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				     "Could not delete output after write "
  				     "failure. Server is now in an "
  				     "inconsistent state.");
  	    }
  
  	    if (save_errno == ENOSPC) {
  		return dav_new_error(p, HTTP_INSUFFICIENT_STORAGE, 0,
  				     "There is not enough storage to write to "
  				     "this resource.");
  	    }
  
  	    /* ### use something besides 500? */
  	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Could not write output file");
  	}
      }
  
      close(fdi);
      close(fdo);
  
      if (is_move && remove(src) != 0) {
  	dav_error *err;
  	int save_errno = errno;	/* save the errno that got us here */
  
  	if (remove(dst) != 0) {
  	    /* ### ACK. this creates an inconsistency. do more!? */
  
  	    /* ### use something besides 500? */
  	    /* Note that we use the latest errno */
  	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Could not remove source or destination "
  				 "file. Server is now in an inconsistent "
  				 "state.");
  	}
  
  	/* ### use something besides 500? */
  	err = dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			    "Could not remove source file after move. "
  			    "Destination was removed to ensure consistency.");
  	err->save_errno = save_errno;
  	return err;
      }
  
      return NULL;
  }
  
  /* copy/move a file from within a state dir to another state dir */
  /* ### need more buffers to replace the pool argument */
  static dav_error * dav_fs_copymove_state(
      int is_move,
      pool * p,
      const char *src_dir, const char *src_file,
      const char *dst_dir, const char *dst_file,
      dav_buffer *pbuf)
  {
      struct stat src_finfo;	/* finfo for source file */
      struct stat dst_state_finfo;	/* finfo for STATE directory */
      const char *src;
      const char *dst;
  
      /* build the propset pathname for the source file */
      src = ap_pstrcat(p, src_dir, "/" DAV_FS_STATE_DIR "/", src_file, NULL);
  
      /* the source file doesn't exist */
      if (stat(src, &src_finfo) != 0) {
  	return NULL;
      }
  
      /* build the pathname for the destination state dir */
      dst = ap_pstrcat(p, dst_dir, "/" DAV_FS_STATE_DIR, NULL);
  
      /* ### do we need to deal with the umask? */
  
      /* ensure that it exists */
      if (mkdir(dst, DAV_FS_MODE_DIR) != 0) {
  	if (errno != EEXIST) {
  	    /* ### use something besides 500? */
  	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Could not create internal state directory");
  	}
      }
  
      /* get info about the state directory */
      if (stat(dst, &dst_state_finfo) != 0) {
  	/* Ack! Where'd it go? */
  	/* ### use something besides 500? */
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "State directory disappeared");
      }
  
      /* The mkdir() may have failed because a *file* exists there already */
      if (!S_ISDIR(dst_state_finfo.st_mode)) {
  	/* ### try to recover by deleting this file? (and mkdir again) */
  	/* ### use something besides 500? */
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "State directory is actually a file");
      }
  
      /* append the target file to the state directory pathname */
      dst = ap_pstrcat(p, dst, "/", dst_file, NULL);
  
      /* copy/move the file now */
      if (is_move && src_finfo.st_dev == dst_state_finfo.st_dev) {
  	/* simple rename is possible since it is on the same device */
  	if (rename(src, dst) != 0) {
  	    /* ### use something besides 500? */
  	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Could not move state file.");
  	}
      }
      else {
  	/* gotta copy (and delete) */
  	return dav_fs_copymove_file(is_move, p, src, dst, pbuf);
      }
  
      return NULL;
  }
  
  static dav_error *dav_fs_copymoveset(int is_move, pool *p,
  				     const dav_resource *src,
  				     const dav_resource *dst,
  				     dav_buffer *pbuf)
  {
      const char *src_dir;
      const char *src_file;
      const char *src_state1;
      const char *src_state2;
      const char *dst_dir;
      const char *dst_file;
      const char *dst_state1;
      const char *dst_state2;
      dav_error *err;
  
      /* Get directory and filename for resources */
      dav_fs_dir_file_name(src, &src_dir, &src_file);
      dav_fs_dir_file_name(dst, &dst_dir, &dst_file);
  
      /* Get the corresponding state files for each resource */
      dav_dbm_get_statefiles(p, src_file, &src_state1, &src_state2);
      dav_dbm_get_statefiles(p, dst_file, &dst_state1, &dst_state2);
  #if DAV_DEBUG
      if ((src_state2 != NULL && dst_state2 == NULL) ||
  	(src_state2 == NULL && dst_state2 != NULL)) {
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "DESIGN ERROR: dav_dbm_get_statefiles() "
  			     "returned inconsistent results.");
      }
  #endif
  
      err = dav_fs_copymove_state(is_move, p,
  				src_dir, src_state1,
  				dst_dir, dst_state1,
  				pbuf);
  
      if (err == NULL && src_state2 != NULL) {
  	err = dav_fs_copymove_state(is_move, p,
  				    src_dir, src_state2,
  				    dst_dir, dst_state2,
  				    pbuf);
  
  	if (err != NULL) {
  	    /* ### CRAP. inconsistency. */
  	    /* ### should perform some cleanup at the target if we still
  	       ### have the original files */
  
  	    /* Change the error to reflect the bad server state. */
  	    err->status = HTTP_INTERNAL_SERVER_ERROR;
  	    err->desc =
  		"Could not fully copy/move the properties. "
  		"The server is now in an inconsistent state.";
  	}
      }
  
      return err;
  }
  
  static dav_error *dav_fs_deleteset(pool *p, const dav_resource *resource)
  {
      const char *dirpath;
      const char *fname;
      const char *state1;
      const char *state2;
      const char *pathname;
  
      /* Get directory, filename, and state-file names for the resource */
      dav_fs_dir_file_name(resource, &dirpath, &fname);
      dav_dbm_get_statefiles(p, fname, &state1, &state2);
  
      /* build the propset pathname for the file */
      pathname = ap_pstrcat(p,
  			  dirpath,
  			  "/" DAV_FS_STATE_DIR "/",
  			  state1,
  			  NULL);
  
      /* note: we may get ENOENT if the state dir is not present */
      if (remove(pathname) != 0 && errno != ENOENT) {
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "Could not remove properties.");
      }
  
      if (state2 != NULL) {
  	/* build the propset pathname for the file */
  	pathname = ap_pstrcat(p,
  			      dirpath,
  			      "/" DAV_FS_STATE_DIR "/",
  			      state2,
  			      NULL);
  
  	if (remove(pathname) != 0 && errno != ENOENT) {
  	    /* ### CRAP. only removed half. */
  	    return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "Could not fully remove properties. "
  				 "The server is now in an inconsistent "
  				 "state.");
  	}
      }
  
      return NULL;
  }
  
  /* --------------------------------------------------------------------
  **
  ** REPOSITORY HOOK FUNCTIONS
  */
  
  static dav_resource * dav_fs_get_resource(
      request_rec *r,
      const char *root_dir,
      const char *workspace)
  {
      dav_resource_private *ctx;
      dav_resource *resource;
      char *s;
      char *filename;
      size_t len;
  
      /* ### optimize this into a single allocation! */
  
      /* Create private resource context descriptor */
      ctx = ap_pcalloc(r->pool, sizeof(*ctx));
      ctx->pool = r->pool;
      ctx->finfo = r->finfo;
  
      /* Preserve case on OSes which fold canonical filenames */
  #if MODULE_MAGIC_NUMBER_MAJOR > 19990320 || (MODULE_MAGIC_NUMBER_MAJOR == 19990320 && MODULE_MAGIC_NUMBER_MINOR >= 8)
      filename = r->case_preserved_filename;
  #else
      filename = r->filename;
  #endif
  
      /*
      ** If there is anything in the path_info, then this indicates that the
      ** entire path was not used to specify the file/dir. We want to append
      ** it onto the filename so that we get a "valid" pathname for null
      ** resources.
      */
      s = ap_pstrcat(r->pool, filename, r->path_info, NULL);
  
      /* make sure the pathname does not have a trailing "/" */
      len = strlen(s);
      if (len > 1 && s[len - 1] == '/') {
  	s[len - 1] = '\0';
      }
      ctx->pathname = s;
  
      /* Create resource descriptor */
      resource = ap_pcalloc(r->pool, sizeof(*resource));
      resource->type = DAV_RESOURCE_TYPE_REGULAR;
      resource->info = ctx;
      resource->hooks = &dav_hooks_repository_fs;
  
      /* make sure the URI does not have a trailing "/" */
      len = strlen(r->uri);
      if (len > 1 && r->uri[len - 1] == '/') {
  	s = ap_pstrdup(r->pool, r->uri);
  	s[len - 1] = '\0';
  	resource->uri = s;
      }
      else {
  	resource->uri = r->uri;
      }
  
      if (r->finfo.st_mode != 0) {
          resource->exists = 1;
          resource->collection = S_ISDIR(r->finfo.st_mode);
  
  	/* unused info in the URL will indicate a null resource */
  
  	if (r->path_info != NULL && *r->path_info != '\0') {
  	    if (resource->collection) {
  		/* only a trailing "/" is allowed */
  		if (*r->path_info != '/' || r->path_info[1] != '\0') {
  
  		    /*
  		    ** This URL/filename represents a locknull resource or
  		    ** possibly a destination of a MOVE/COPY
  		    */
  		    resource->exists = 0;
  		    resource->collection = 0;
  		}
  	    }
  	    else
  	    {
  		/*
  		** The base of the path refers to a file -- nothing should
  		** be in path_info. The resource is simply an error: it
  		** can't be a null or a locknull resource.
  		*/
  		return NULL;	/* becomes HTTP_NOT_FOUND */
  	    }
  
  	    /* retain proper integrity across the structures */
  	    if (!resource->exists) {
  		ctx->finfo.st_mode = 0;
  	    }
  	}
      }
  
      return resource;
  }
  
  static dav_resource * dav_fs_get_parent_resource(const dav_resource *resource)
  {
      dav_resource_private *ctx = resource->info;
      dav_resource_private *parent_ctx;
      dav_resource *parent_resource;
      char *dirpath;
  
      /* If given resource is root, then there is no parent */
      if (strcmp(resource->uri, "/") == 0 ||
  #ifdef WIN32
          (strlen(ctx->pathname) == 3 && ctx->pathname[1] == ':' && ctx->pathname[2] == '/')
  #else
          strcmp(ctx->pathname, "/") == 0
  #endif
  	)
          return NULL;
  
      /* ### optimize this into a single allocation! */
  
      /* Create private resource context descriptor */
      parent_ctx = ap_pcalloc(ctx->pool, sizeof(*parent_ctx));
      parent_ctx->pool = ctx->pool;
  
      dirpath = ap_make_dirstr_parent(ctx->pool, ctx->pathname);
      if (strlen(dirpath) > 1 && dirpath[strlen(dirpath) - 1] == '/') 
          dirpath[strlen(dirpath) - 1] = '\0';
      parent_ctx->pathname = dirpath;
  
      parent_resource = ap_pcalloc(ctx->pool, sizeof(*parent_resource));
      parent_resource->info = parent_ctx;
      parent_resource->collection = 1;
      parent_resource->hooks = &dav_hooks_repository_fs;
  
      if (resource->uri != NULL) {
          char *uri = ap_make_dirstr_parent(ctx->pool, resource->uri);
          if (strlen(uri) > 1 && uri[strlen(uri) - 1] == '/')
              uri[strlen(uri) - 1] = '\0';
  	parent_resource->uri = uri;
      }
  
      if (stat(parent_ctx->pathname, &parent_ctx->finfo) == 0) {
          parent_resource->exists = 1;
      }
  
      return parent_resource;
  }
  
  static int dav_fs_is_same_resource(
      const dav_resource *res1,
      const dav_resource *res2)
  {
      dav_resource_private *ctx1 = res1->info;
      dav_resource_private *ctx2 = res2->info;
  
      if (res1->hooks != res2->hooks)
  	return 0;
  
  #ifdef WIN32
      return stricmp(ctx1->pathname, ctx2->pathname) == 0;
  #else
      if (ctx1->finfo.st_mode != 0)
          return ctx1->finfo.st_ino == ctx2->finfo.st_ino;
      else
          return strcmp(ctx1->pathname, ctx2->pathname) == 0;
  #endif
  }
  
  static int dav_fs_is_parent_resource(
      const dav_resource *res1,
      const dav_resource *res2)
  {
      dav_resource_private *ctx1 = res1->info;
      dav_resource_private *ctx2 = res2->info;
      size_t len1 = strlen(ctx1->pathname);
      size_t len2;
  
      if (res1->hooks != res2->hooks)
  	return 0;
  
      /* it is safe to use ctx2 now */
      len2 = strlen(ctx2->pathname);
  
      return (len2 > len1
              && memcmp(ctx1->pathname, ctx2->pathname, len1) == 0
              && ctx2->pathname[len1] == '/');
  }
  
  static dav_error * dav_fs_open_stream(const dav_resource *resource,
  				      dav_stream_mode mode,
  				      dav_stream **stream)
  {
      pool *p = resource->info->pool;
      dav_stream *ds = ap_palloc(p, sizeof(*ds));
      int flags;
  
      switch (mode) {
      case DAV_MODE_READ:
      case DAV_MODE_READ_SEEKABLE:
      default:
  	flags = O_RDONLY;
  	break;
  
      case DAV_MODE_WRITE_TRUNC:
  	flags = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
  	break;
      case DAV_MODE_WRITE_SEEKABLE:
  	flags = O_WRONLY | O_CREAT | O_BINARY;
  	break;
      }
  
      ds->p = p;
      ds->pathname = resource->info->pathname;
      ds->fd = open(ds->pathname, flags, DAV_FS_MODE_FILE);
      if (ds->fd == -1) {
  	/* ### use something besides 500? */
  	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "An error occurred while opening a resource.");
      }
      ap_note_cleanups_for_fd(p, ds->fd);
  
      *stream = ds;
      return NULL;
  }
  
  static dav_error * dav_fs_close_stream(dav_stream *stream, int commit)
  {
      ap_kill_cleanups_for_fd(stream->p, stream->fd);
      close(stream->fd);
  
      if (!commit) {
  	if (remove(stream->pathname) != 0) {
  	    /* ### use a better description? */
              return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
  				 "There was a problem removing (rolling "
  				 "back) the resource "
  				 "when it was being closed.");
  	}
      }
  
      return NULL;
  }
  
  static dav_error * dav_fs_read_stream(dav_stream *stream,
  				      void *buf, size_t *bufsize)
  {
      ssize_t amt;
  
      amt = read(stream->fd, buf, *bufsize);
      if (amt == -1) {
  	/* ### use something besides 500? */
  	return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "An error occurred while reading from a "
  			     "resource.");
      }
      *bufsize = (size_t)amt;
      return NULL;
  }
  
  static dav_error * dav_fs_write_stream(dav_stream *stream,
  				       const void *buf, size_t bufsize)
  {
      if (dav_sync_write(stream->fd, buf, bufsize) != 0) {
  	if (errno == ENOSPC) {
  	    return dav_new_error(stream->p, HTTP_INSUFFICIENT_STORAGE, 0,
  				 "There is not enough storage to write to "
  				 "this resource.");
  	}
  
  	/* ### use something besides 500? */
  	return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "An error occurred while writing to a "
  			     "resource.");
      }
      return NULL;
  }
  
  static dav_error * dav_fs_seek_stream(dav_stream *stream, off_t abs_pos)
  {
      if (lseek(stream->fd, abs_pos, SEEK_SET) == (off_t)-1) {
  	/* ### use something besides 500? */
  	return dav_new_error(stream->p, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "Could not seek to specified position in the "
  			     "resource.");
      }
      return NULL;
  }
  
  static dav_error * dav_fs_set_headers(request_rec *r,
  				      const dav_resource *resource)
  {
      /* ### this function isn't really used since we have a get_pathname */
  #if DEBUG_GET_HANDLER
      if (!resource->exists)
  	return NULL;
  
      /* make sure the proper mtime is in the request record */
      ap_update_mtime(r, resource->info->finfo.st_mtime);
  
      /* ### note that these use r->filename rather than <resource> */
      ap_set_last_modified(r);
      ap_set_etag(r);
  
      /* we accept byte-ranges */
      ap_table_setn(r->headers_out, "Accept-Ranges", "bytes");
  
      /* set up the Content-Length header */
      ap_set_content_length(r, resource->info->finfo.st_size);
  
      /* ### how to set the content type? */
      /* ### until this is resolved, the Content-Type header is busted */
  
  #endif
  
      return NULL;
  }
  
  #if DEBUG_PATHNAME_STYLE
  static const char * dav_fs_get_pathname(
      const dav_resource *resource,
      void **free_handle_p)
  {
      return resource->info->pathname;
  }
  #endif
  
  static void dav_fs_free_file(void *free_handle)
  {
      /* nothing to free ... */
  }
  
  static dav_error * dav_fs_create_collection(pool *p, dav_resource *resource)
  {
      dav_resource_private *ctx = resource->info;
  
      if (mkdir(ctx->pathname, DAV_FS_MODE_DIR) != 0) {
  	if (errno == ENOSPC) 
  	    return dav_new_error(p, HTTP_INSUFFICIENT_STORAGE, 0,
  				 "There is not enough storage to create "
  				 "this collection.");
  
  	/* ### refine this error message? */
  	return dav_new_error(p, HTTP_FORBIDDEN, 0,
                               "Unable to create collection.");
      }
  
      /* update resource state to show it exists as a collection */
      resource->exists = 1;
      resource->collection = 1;
  
      return NULL;
  }
  
  static dav_error * dav_fs_copymove_walker(dav_walker_ctx *ctx, int calltype)
  {
      dav_resource_private *srcinfo = ctx->resource->info;
      dav_resource_private *dstinfo = ctx->res2->info;
      dav_error *err = NULL;
  
      if (ctx->resource->collection) {
  	if (calltype == DAV_CALLTYPE_POSTFIX) {
  	    /* Postfix call for MOVE. delete the source dir.
  	     * Note: when copying, we do not enable the postfix-traversal.
  	     */
  	    /* ### we are ignoring any error here; what should we do? */
  	    (void) rmdir(srcinfo->pathname);
  	}
          else {
  	    /* copy/move of a collection. Create the new, target collection */
              if (mkdir(dstinfo->pathname, DAV_FS_MODE_DIR) != 0) {
  		/* ### assume it was a permissions problem */
  		/* ### need a description here */
                  err = dav_new_error(ctx->pool, HTTP_FORBIDDEN, 0, NULL);
              }
  	}
      }
      else {
  	err = dav_fs_copymove_file(ctx->is_move, ctx->pool, srcinfo->pathname,
  				   dstinfo->pathname, &ctx->work_buf);
  	/* ### push a higher-level description? */
      }
  
      /*
      ** If we have a "not so bad" error, then it might need to go into a
      ** multistatus response.
      **
      ** For a MOVE, it will always go into the multistatus. It could be
      ** that everything has been moved *except* for the root. Using a
      ** multistatus (with no errors for the other resources) will signify
      ** this condition.
      **
      ** For a COPY, we are traversing in a prefix fashion. If the root fails,
      ** then we can just bail out now.
      */
      if (err != NULL
          && !ap_is_HTTP_SERVER_ERROR(err->status)
  	&& (ctx->is_move
              || !dav_fs_is_same_resource(ctx->resource, ctx->root))) {
  	/* ### use errno to generate DAV:responsedescription? */
  	dav_add_response(ctx, ctx->resource->uri, err->status, NULL);
  
          /* the error is in the multistatus now. do not stop the traversal. */
          return NULL;
      }
  
      return err;
  }
  
  static dav_error *dav_fs_copymove_resource(
      int is_move,
      const dav_resource *src,
      const dav_resource *dst,
      int depth,
      dav_response **response)
  {
      dav_error *err = NULL;
      dav_buffer work_buf = { 0 };
  
      *response = NULL;
  
      /* if a collection, recursively copy/move it and its children,
       * including the state dirs
       */
      if (src->collection) {
  	dav_walker_ctx ctx = { 0 };
  
  	ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_HIDDEN;
  	ctx.func = dav_fs_copymove_walker;
  	ctx.pool = src->info->pool;
  	ctx.resource = src;
  	ctx.res2 = dst;
  	ctx.is_move = is_move;
  	ctx.postfix = is_move;	/* needed for MOVE to delete source dirs */
  
  	/* copy over the source URI */
  	dav_buffer_init(ctx.pool, &ctx.uri, src->uri);
  
  	if ((err = dav_fs_walk(&ctx, depth)) != NULL) {
              /* on a "real" error, then just punt. nothing else to do. */
              return err;
          }
  
          if ((*response = ctx.response) != NULL) {
              /* some multistatus responses exist. wrap them in a 207 */
              return dav_new_error(src->info->pool, HTTP_MULTI_STATUS, 0,
                                   "Error(s) occurred on some resources during "
                                   "the COPY/MOVE process.");
          }
  
  	return NULL;
      }
  
      /* not a collection */
      if ((err = dav_fs_copymove_file(is_move, src->info->pool,
  				    src->info->pathname, dst->info->pathname,
  				    &work_buf)) != NULL) {
  	/* ### push a higher-level description? */
  	return err;
      }
  	
      /* copy/move properties as well */
      return dav_fs_copymoveset(is_move, src->info->pool, src, dst, &work_buf);
  }
  
  static dav_error * dav_fs_copy_resource(
      const dav_resource *src,
      dav_resource *dst,
      int depth,
      dav_response **response)
  {
      dav_error *err;
  
  #if DAV_DEBUG
      if (src->hooks != dst->hooks) {
  	/*
  	** ### strictly speaking, this is a design error; we should not
  	** ### have reached this point.
  	*/
  	return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "DESIGN ERROR: a mix of repositories "
  			     "was passed to copy_resource.");
      }
  #endif
  
      if ((err = dav_fs_copymove_resource(0, src, dst, depth,
  					response)) == NULL) {
  
          /* update state of destination resource to show it exists */
          dst->exists = 1;
          dst->collection = src->collection;
      }
  
      return err;
  }
  
  static dav_error * dav_fs_move_resource(
      dav_resource *src,
      dav_resource *dst,
      dav_response **response)
  {
      dav_resource_private *srcinfo = src->info;
      dav_resource_private *dstinfo = dst->info;
      dav_error *err;
      int can_rename = 0;
  
  #if DAV_DEBUG
      if (src->hooks != dst->hooks) {
  	/*
  	** ### strictly speaking, this is a design error; we should not
  	** ### have reached this point.
  	*/
  	return dav_new_error(src->info->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "DESIGN ERROR: a mix of repositories "
  			     "was passed to move_resource.");
      }
  #endif
  
      /* determine whether a simple rename will work.
       * Assume source exists, else we wouldn't get called.
       */
      if (dstinfo->finfo.st_mode != 0) {
  	if (dstinfo->finfo.st_dev == srcinfo->finfo.st_dev) {
  	    /* target exists and is on the same device. */
  	    can_rename = 1;
  	}
      }
      else {
  	const char *dirpath;
  	struct stat finfo;
  
  	/* destination does not exist, but the parent directory should,
  	 * so try it
  	 */
  	dirpath = ap_make_dirstr_parent(dstinfo->pool, dstinfo->pathname);
  	if (stat(dirpath, &finfo) == 0
  	    && finfo.st_dev == srcinfo->finfo.st_dev) {
  	    can_rename = 1;
  	}
      }
  
      /* if we can't simply renamed, then do it the hard way... */
      if (!can_rename) {
          if ((err = dav_fs_copymove_resource(1, src, dst, DAV_INFINITY, response)) == NULL) {
              /* update resource states */
              dst->exists = 1;
              dst->collection = src->collection;
              src->exists = 0;
              src->collection = 0;
          }
  
          return err;
      }
  
      /* a rename should work. do it, and move properties as well */
  
      /* no multistatus response */
      *response = NULL;
  
      if (rename(srcinfo->pathname, dstinfo->pathname) != 0) {
  	/* ### should have a better error than this. */
  	return dav_new_error(srcinfo->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "Could not rename resource.");
      }
  
      /* update resource states */
      dst->exists = 1;
      dst->collection = src->collection;
      src->exists = 0;
      src->collection = 0;
  
      if ((err = dav_fs_copymoveset(1, src->info->pool,
  				  src, dst, NULL)) == NULL) {
  	/* no error. we're done. go ahead and return now. */
  	return NULL;
      }
  
      /* error occurred during properties move; try to put resource back */
      if (rename(dstinfo->pathname, srcinfo->pathname) != 0) {
  	/* couldn't put it back! */
  	return dav_push_error(srcinfo->pool,
  			      HTTP_INTERNAL_SERVER_ERROR, 0,
  			      "The resource was moved, but a failure "
  			      "occurred during the move of its "
  			      "properties. The resource could not be "
  			      "restored to its original location. The "
  			      "server is now in an inconsistent state.",
  			      err);
      }
  
      /* update resource states again */
      src->exists = 1;
      src->collection = dst->collection;
      dst->exists = 0;
      dst->collection = 0;
  
      /* resource moved back, but properties may be inconsistent */
      return dav_push_error(srcinfo->pool,
  			  HTTP_INTERNAL_SERVER_ERROR, 0,
  			  "The resource was moved, but a failure "
  			  "occurred during the move of its properties. "
  			  "The resource was moved back to its original "
  			  "location, but its properties may have been "
  			  "partially moved. The server may be in an "
  			  "inconsistent state.",
  			  err);
  }
  
  static dav_error * dav_fs_delete_walker(dav_walker_ctx *ctx, int calltype)
  {
      dav_resource_private *info = ctx->resource->info;
  
      /* do not attempt to remove a null resource,
       * or a collection with children
       */
      if (ctx->resource->exists &&
          (!ctx->resource->collection || calltype == DAV_CALLTYPE_POSTFIX)) {
  	/* try to remove the resource */
  	int result;
  
  	result = ctx->resource->collection
  	    ? rmdir(info->pathname)
  	    : remove(info->pathname);
  
  	/*
          ** If an error occurred, then add it to multistatus response.
          ** Note that we add it for the root resource, too. It is quite
          ** possible to delete the whole darn tree, yet fail on the root.
          **
          ** (also: remember we are deleting via a postfix traversal)
          */
  	if (result != 0) {
              /* ### assume there is a permissions problem */
  
              /* ### use errno to generate DAV:responsedescription? */
              dav_add_response(ctx, ctx->resource->uri, HTTP_FORBIDDEN, NULL);
  	}
      }
  
      return NULL;
  }
  
  static dav_error * dav_fs_remove_resource(dav_resource *resource,
                                            dav_response **response)
  {
      dav_resource_private *info = resource->info;
  
      *response = NULL;
  
      /* if a collection, recursively remove it and its children,
       * including the state dirs
       */
      if (resource->collection) {
  	dav_walker_ctx ctx = { 0 };
  	dav_error *err = NULL;
  
  	ctx.walk_type = DAV_WALKTYPE_ALL | DAV_WALKTYPE_HIDDEN;
  	ctx.postfix = 1;
  	ctx.func = dav_fs_delete_walker;
  	ctx.pool = info->pool;
  	ctx.resource = resource;
  
  	dav_buffer_init(info->pool, &ctx.uri, resource->uri);
  
  	if ((err = dav_fs_walk(&ctx, DAV_INFINITY)) != NULL) {
              /* on a "real" error, then just punt. nothing else to do. */
              return err;
          }
  
          if ((*response = ctx.response) != NULL) {
              /* some multistatus responses exist. wrap them in a 207 */
              return dav_new_error(info->pool, HTTP_MULTI_STATUS, 0,
                                   "Error(s) occurred on some resources during "
                                   "the deletion process.");
          }
  
          /* no errors... update resource state */
          resource->exists = 0;
          resource->collection = 0;
  
  	return NULL;
      }
  
      /* not a collection; remove the file and its properties */
      if (remove(info->pathname) != 0) {
  	/* ### put a description in here */
  	return dav_new_error(info->pool, HTTP_FORBIDDEN, 0, NULL);
      }
  
      /* update resource state */
      resource->exists = 0;
      resource->collection = 0;
  
      /* remove properties and return its result */
      return dav_fs_deleteset(info->pool, resource);
  }
  
  /* ### move this to dav_util? */
  /* Walk recursively down through directories, *
   * including lock-null resources as we go.    */
  dav_error * dav_fs_walker(dav_fs_walker_context *fsctx, int depth)
  {
      dav_error *err = NULL;
      dav_walker_ctx *wctx = fsctx->wctx;
      int isdir = wctx->resource->collection;
      DIR *dirp;
      struct dirent *ep;
  
      /* ensure the context is prepared properly, then call the func */
      err = (*wctx->func)(wctx,
  			isdir
  			? DAV_CALLTYPE_COLLECTION
  			: DAV_CALLTYPE_MEMBER);
      if (err != NULL) {
  	return err;
      }
  
      if (depth == 0 || !isdir) {
  	return NULL;
      }
  
      /* put a trailing slash onto the directory, in preparation for appending
       * files to it as we discovery them within the directory */
      dav_check_bufsize(wctx->pool, &fsctx->path1, DAV_BUFFER_PAD);
      fsctx->path1.buf[fsctx->path1.cur_len++] = '/';
      fsctx->path1.buf[fsctx->path1.cur_len] = '\0';	/* in pad area */
  
      /* if a secondary path is present, then do that, too */
      if (fsctx->path2.buf != NULL) {
  	dav_check_bufsize(wctx->pool, &fsctx->path2, DAV_BUFFER_PAD);
  	fsctx->path2.buf[fsctx->path2.cur_len++] = '/';
  	fsctx->path2.buf[fsctx->path2.cur_len] = '\0';	/* in pad area */
      }
  
      /* Note: the URI should ALREADY have a trailing "/" */
  
      /* for this first pass of files, all resources exist */
      fsctx->res1.exists = 1;
  
      /* a file is the default; we'll adjust if we hit a directory */
      fsctx->res1.collection = 0;
      fsctx->res2.collection = 0;
  
      /* open and scan the directory */
      if ((dirp = opendir(fsctx->path1.buf)) == NULL) {
  	/* ### need a better error */
  	return dav_new_error(wctx->pool, HTTP_NOT_FOUND, 0, NULL);
      }
      while ((ep = readdir(dirp)) != NULL) {
  	size_t len = strlen(ep->d_name);
  
  	/* avoid recursing into our current, parent, or state directories */
  	if (ep->d_name[0] == '.'
  	    && (len == 1 || (ep->d_name[1] == '.' && len == 2))) {
  	    continue;
  	}
  
  	if (wctx->walk_type & DAV_WALKTYPE_AUTH) {
  	    /* ### need to authorize each file */
  	    /* ### example: .htaccess is normally configured to fail auth */
  
  	    /* stuff in the state directory is never authorized! */
  	    if (!strcmp(ep->d_name, DAV_FS_STATE_DIR)) {
  		continue;
  	    }
  	}
  	/* skip the state dir unless a HIDDEN is performed */
  	if (!(wctx->walk_type & DAV_WALKTYPE_HIDDEN)
  	    && !strcmp(ep->d_name, DAV_FS_STATE_DIR)) {
  	    continue;
  	}
  
  	/* append this file onto the path buffer (copy null term) */
  	dav_buffer_place_mem(wctx->pool,
  			     &fsctx->path1, ep->d_name, len + 1, 0);
  
  	if (lstat(fsctx->path1.buf, &fsctx->info1.finfo) != 0) {
  	    /* woah! where'd it go? */
  	    /* ### should have a better error here */
  	    err = dav_new_error(wctx->pool, HTTP_NOT_FOUND, 0, NULL);
  	    break;
  	}
  
  	/* copy the file to the URI, too. NOTE: we will pad an extra byte
  	   for the trailing slash later. */
  	dav_buffer_place_mem(wctx->pool, &wctx->uri, ep->d_name, len + 1, 1);
  
  	/* if there is a secondary path, then do that, too */
  	if (fsctx->path2.buf != NULL) {
  	    dav_buffer_place_mem(wctx->pool, &fsctx->path2,
  				 ep->d_name, len + 1, 0);
  	}
  
  	/* set up the (internal) pathnames for the two resources */
  	fsctx->info1.pathname = fsctx->path1.buf;
  	fsctx->info2.pathname = fsctx->path2.buf;
  
  	/* set up the URI for the current resource */
  	fsctx->res1.uri = wctx->uri.buf;
  
  	/* ### for now, only process regular files (e.g. skip symlinks) */
  	if (S_ISREG(fsctx->info1.finfo.st_mode)) {
  	    /* call the function for the specified dir + file */
  	    if ((err = (*wctx->func)(wctx, DAV_CALLTYPE_MEMBER)) != NULL) {
  		/* ### maybe add a higher-level description? */
  		break;
  	    }
  	}
  	else if (S_ISDIR(fsctx->info1.finfo.st_mode)) {
  	    size_t save_path_len = fsctx->path1.cur_len;
  	    size_t save_uri_len = wctx->uri.cur_len;
  	    size_t save_path2_len = fsctx->path2.cur_len;
  
  	    /* adjust length to incorporate the subdir name */
  	    fsctx->path1.cur_len += len;
  	    fsctx->path2.cur_len += len;
  
  	    /* adjust URI length to incorporate subdir and a slash */
  	    wctx->uri.cur_len += len + 1;
  	    wctx->uri.buf[wctx->uri.cur_len - 1] = '/';
  	    wctx->uri.buf[wctx->uri.cur_len] = '\0';
  
  	    /* switch over to a collection */
  	    fsctx->res1.collection = 1;
  	    fsctx->res2.collection = 1;
  
  	    /* recurse on the subdir */
  	    /* ### don't always want to quit on error from single child */
  	    if ((err = dav_fs_walker(fsctx, depth - 1)) != NULL) {
  		/* ### maybe add a higher-level description? */
  		break;
  	    }
  
  	    /* put the various information back */
  	    fsctx->path1.cur_len = save_path_len;
  	    fsctx->path2.cur_len = save_path2_len;
  	    wctx->uri.cur_len = save_uri_len;
  
  	    fsctx->res1.collection = 0;
  	    fsctx->res2.collection = 0;
  
  	    /* assert: res1.exists == 1 */
  	}
      }
  
      /* ### check the return value of this? */
      closedir(dirp);
  
      if (err != NULL)
  	return err;
  
      if (wctx->walk_type & DAV_WALKTYPE_LOCKNULL) {
  	size_t offset = 0;
  
  	/* null terminate the directory name */
  	fsctx->path1.buf[fsctx->path1.cur_len - 1] = '\0';
  
  	/* Include any lock null resources found in this collection */
  	fsctx->res1.collection = 1;
  	if ((err = dav_fs_get_locknull_members(&fsctx->res1,
                                                 &fsctx->locknull_buf)) != NULL) {
              /* ### maybe add a higher-level description? */
              return err;
  	}
  
  	/* put a slash back on the end of the directory */
  	fsctx->path1.buf[fsctx->path1.cur_len - 1] = '/';
  
  	/* these are all non-existant (files) */
  	fsctx->res1.exists = 0;
  	fsctx->res1.collection = 0;
  	memset(&fsctx->info1.finfo, 0, sizeof(fsctx->info1.finfo));
  
  	while (offset < fsctx->locknull_buf.cur_len) {
  	    size_t len = strlen(fsctx->locknull_buf.buf + offset);
  	    dav_lock *locks = NULL;
  
  	    /*
  	    ** Append the locknull file to the paths and the URI. Note that
  	    ** we don't have to pad the URI for a slash since a locknull
  	    ** resource is not a collection.
  	    */
  	    dav_buffer_place_mem(wctx->pool, &fsctx->path1,
  				 fsctx->locknull_buf.buf + offset, len + 1, 0);
  	    dav_buffer_place_mem(wctx->pool, &wctx->uri,
  				 fsctx->locknull_buf.buf + offset, len + 1, 0);
  	    if (fsctx->path2.buf != NULL) {
  		dav_buffer_place_mem(wctx->pool, &fsctx->path2,
  				     fsctx->locknull_buf.buf + offset,
                                       len + 1, 0);
  	    }
  
  	    /* set up the (internal) pathnames for the two resources */
  	    fsctx->info1.pathname = fsctx->path1.buf;
  	    fsctx->info2.pathname = fsctx->path2.buf;
  
  	    /* set up the URI for the current resource */
  	    fsctx->res1.uri = wctx->uri.buf;
  
  	    /*
  	    ** To prevent a PROPFIND showing an expired locknull
  	    ** resource, query the lock database to force removal
  	    ** of both the lock entry and .locknull, if necessary..
  	    ** Sure, the query in PROPFIND would do this.. after
  	    ** the locknull resource was already included in the 
  	    ** return.
  	    **
  	    ** NOTE: we assume the caller has opened the lock database
  	    **       if they have provided DAV_WALKTYPE_LOCKNULL.
  	    */
  	    /* ### we should also look into opening it read-only and
  	       ### eliding timed-out items from the walk, yet leaving
  	       ### them in the locknull database until somebody opens
  	       ### the thing writable.
  	       */
  	    /* ### probably ought to use has_locks. note the problem
  	       ### mentioned above, though... we would traverse this as
  	       ### a locknull, but then a PROPFIND would load the lock
  	       ### info, causing a timeout and the locks would not be
  	       ### reported. Therefore, a null resource would be returned
  	       ### in the PROPFIND.
  	       ###
  	       ### alternative: just load unresolved locks. any direct
  	       ### locks will be timed out (correct). any indirect will
  	       ### not (correct; consider if a parent timed out -- the
  	       ### timeout routines do not walk and remove indirects;
  	       ### even the resolve func would probably fail when it
  	       ### tried to find a timed-out direct lock).
  	    */
  	    if ((err = dav_lock_query(wctx->lockdb, wctx->resource, &locks)) != NULL) {
  		/* ### maybe add a higher-level description? */
  		return err;
  	    }
  
  	    /* call the function for the specified dir + file */
  	    if (locks != NULL &&
  		(err = (*wctx->func)(wctx, DAV_CALLTYPE_LOCKNULL)) != NULL) {
  		/* ### maybe add a higher-level description? */
  		return err;
  	    }
  
  	    offset += len + 1;
  	}
  
  	/* reset the exists flag */
  	fsctx->res1.exists = 1;
      }
  
      if (wctx->postfix) {
  	/* replace the dirs' trailing slashes with null terms */
  	fsctx->path1.buf[--fsctx->path1.cur_len] = '\0';
  	wctx->uri.buf[--wctx->uri.cur_len] = '\0';
  	if (fsctx->path2.buf != NULL) {
  	    fsctx->path2.buf[--fsctx->path2.cur_len] = '\0';
  	}
  
  	/* this is a collection which exists */
  	fsctx->res1.collection = 1;
  
  	return (*wctx->func)(wctx, DAV_CALLTYPE_POSTFIX);
      }
  
      return NULL;
  }
  
  static dav_error * dav_fs_walk(dav_walker_ctx *wctx, int depth)
  {
      dav_fs_walker_context fsctx = { 0 };
  
  #if DAV_DEBUG
      if ((wctx->walk_type & DAV_WALKTYPE_LOCKNULL) != 0
  	&& wctx->lockdb == NULL) {
  	return dav_new_error(wctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "DESIGN ERROR: walker called to walk locknull "
  			     "resources, but a lockdb was not provided.");
      }
  
      /* ### an assertion that we have space for a trailing slash */
      if (wctx->uri.cur_len + 1 > wctx->uri.alloc_len) {
  	return dav_new_error(wctx->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "DESIGN ERROR: walker should have been called "
  			     "with padding in the URI buffer.");
      }
  #endif
  
      fsctx.wctx = wctx;
  
      wctx->root = wctx->resource;
  
      /* ### zero out versioned, working, baselined? */
  
      fsctx.res1 = *wctx->resource;
  
      fsctx.res1.info = &fsctx.info1;
      fsctx.info1 = *wctx->resource->info;
  
      dav_buffer_init(wctx->pool, &fsctx.path1, fsctx.info1.pathname);
      fsctx.info1.pathname = fsctx.path1.buf;
  
      if (wctx->res2 != NULL) {
  	fsctx.res2 = *wctx->res2;
  	fsctx.res2.exists = 0;
  	fsctx.res2.collection = 0;
  
  	fsctx.res2.info = &fsctx.info2;
  	fsctx.info2 = *wctx->res2->info;
  
  	/* res2 does not exist -- clear its finfo structure */
  	memset(&fsctx.info2.finfo, 0, sizeof(fsctx.info2.finfo));
  
  	dav_buffer_init(wctx->pool, &fsctx.path2, fsctx.info2.pathname);
  	fsctx.info2.pathname = fsctx.path2.buf;
      }
  
      /* if we have a directory, then ensure the URI has a trailing "/" */
      if (fsctx.res1.collection
  	&& wctx->uri.buf[wctx->uri.cur_len - 1] != '/') {
  
  	/* this will fall into the pad area */
  	wctx->uri.buf[wctx->uri.cur_len++] = '/';
  	wctx->uri.buf[wctx->uri.cur_len] = '\0';
      }
  
      /*
      ** URI is tracked in the walker context. Ensure that people do not try
      ** to fetch it from res2. We will ensure that res1 and uri will remain
      ** synchronized.
      */
      fsctx.res1.uri = wctx->uri.buf;
      fsctx.res2.uri = NULL;
  
      /* use our resource structures */
      wctx->resource = &fsctx.res1;
      wctx->res2 = &fsctx.res2;
  
      return dav_fs_walker(&fsctx, depth);
  }
  
  /* dav_fs_etag:  Stolen from ap_make_etag.  Creates a strong etag
   *    for file path.
   * ### do we need to return weak tags sometimes?
   */
  static const char *dav_fs_getetag(const dav_resource *resource)
  {
      dav_resource_private *ctx = resource->info;
  
      if (!resource->exists) 
  	return ap_pstrdup(ctx->pool, "");
  
      if (ctx->finfo.st_mode != 0) {
          return ap_psprintf(ctx->pool, "\"%lx-%lx-%lx\"",
  			   (unsigned long) ctx->finfo.st_ino,
  			   (unsigned long) ctx->finfo.st_size,
  			   (unsigned long) ctx->finfo.st_mtime);
      }
  
      return ap_psprintf(ctx->pool, "\"%lx\"", (unsigned long) ctx->finfo.st_mtime);
  }
  
  static const dav_hooks_repository dav_hooks_repository_fs =
  {
      DEBUG_GET_HANDLER,   /* normally: special GET handling not required */
      dav_fs_get_resource,
      dav_fs_get_parent_resource,
      dav_fs_is_same_resource,
      dav_fs_is_parent_resource,
      dav_fs_open_stream,
      dav_fs_close_stream,
      dav_fs_read_stream,
      dav_fs_write_stream,
      dav_fs_seek_stream,
      dav_fs_set_headers,
  #if DEBUG_PATHNAME_STYLE
      dav_fs_get_pathname,
  #else
      0,
  #endif
      dav_fs_free_file,
      dav_fs_create_collection,
      dav_fs_copy_resource,
      dav_fs_move_resource,
      dav_fs_remove_resource,
      dav_fs_walk,
      dav_fs_getetag,
  };
  
  static int dav_fs_find_prop(const char *ns_uri, const char *name)
  {
      const dav_fs_liveprop_name *scan;
      int ns;
  
      if (*ns_uri == 'h'
  	&& strcmp(ns_uri, dav_fs_namespace_uris[DAV_FS_URI_MYPROPS]) == 0) {
  	ns = DAV_FS_URI_MYPROPS;
      }
      else if (*ns_uri == 'D' && strcmp(ns_uri, "DAV:") == 0) {
  	ns = DAV_FS_URI_DAV;
      }
      else {
  	/* we don't define this property */
  	return 0;
      }
  
      for (scan = dav_fs_props; scan->name != NULL; ++scan)
  	if (ns == scan->ns && strcmp(name, scan->name) == 0)
  	    return scan->propid;
  
      return 0;
  }
  
  static dav_prop_insert dav_fs_insert_prop(const dav_resource *resource,
  					  int propid, int insvalue,
  					  const int *ns_map,
  					  dav_text_header *phdr)
  {
      const char *value;
      const char *s;
      dav_prop_insert which;
      pool *p = resource->info->pool;
      const dav_fs_liveprop_name *scan;
      int ns;
  
      /* an HTTP-date can be 29 chars plus a null term */
      /* a 64-bit size can be 20 chars plus a null term */
      char buf[DAV_TIMEBUF_SIZE];
  
      if (!DAV_PROPID_FS_OURS(propid))
  	return DAV_PROP_INSERT_NOTME;
  
      /*
      ** None of FS provider properties are defined if the resource does not
      ** exist. Just bail for this case.
      **
      ** Note that DAV:displayname and DAV:source will be stored as dead
      ** properties; the NOTDEF return code indicates that dav_props.c should
      ** look there for the value.
      **
      ** Even though we state that the FS properties are not defined, the
      ** client cannot store dead values -- we deny that thru the is_writable
      ** hook function.
      */
      if (!resource->exists)
  	return DAV_PROP_INSERT_NOTDEF;
  
      switch (propid) {
      case DAV_PROPID_FS_creationdate:
  	/*
  	** Closest thing to a creation date. since we don't actually
  	** perform the operations that would modify ctime (after we
  	** create the file), then we should be pretty safe here.
  	*/
  	dav_format_time(DAV_STYLE_ISO8601,
                          resource->info->finfo.st_ctime,
                          buf);
  	value = buf;
  	break;
  
      case DAV_PROPID_FS_getcontentlength:
  	/* our property, but not defined on collection resources */
  	if (resource->collection)
  	    return DAV_PROP_INSERT_NOTDEF;
  
  	(void) sprintf(buf, "%ld", resource->info->finfo.st_size);
  	value = buf;
  	break;
  
      case DAV_PROPID_FS_getetag:
  	value = dav_fs_getetag(resource);
  	break;
  
      case DAV_PROPID_FS_getlastmodified:
  	dav_format_time(DAV_STYLE_RFC822,
                          resource->info->finfo.st_mtime,
                          buf);
  	value = buf;
  	break;
  
      case DAV_PROPID_FS_executable:
  #ifdef WIN32
          /* our property, but not defined on the Win32 platform */
          return DAV_PROP_INSERT_NOTDEF;
  #else
  	/* our property, but not defined on collection resources */
  	if (resource->collection)
  	    return DAV_PROP_INSERT_NOTDEF;
  
  	/* the files are "ours" so we only need to check owner exec privs */
  	if (resource->info->finfo.st_mode & DAV_FS_MODE_XUSR)
  	    value = "T";
  	else
  	    value = "F";
  	break;
  #endif /* WIN32 */
  
      case DAV_PROPID_FS_displayname:
      case DAV_PROPID_FS_source:
      default:
  	/*
  	** This property is not defined. However, it may be a dead
  	** property.
  	*/
  	return DAV_PROP_INSERT_NOTDEF;
      }
  
      /* assert: value != NULL */
  
      for (scan = dav_fs_props; scan->name != NULL; ++scan)
  	if (scan->propid == propid)
  	    break;
      /* assert: scan->name != NULL */
  
      /* map our NS index into a global NS index */
      ns = ns_map[scan->ns];
  
      /* DBG3("FS: inserting lp%d:%s  (local %d)", ns, scan->name, scan->ns); */
  
      if (insvalue) {
  	/* use D: prefix to refer to the DAV: namespace URI */
  	s = ap_psprintf(p, "<lp%d:%s>%s</lp%d:%s>" DEBUG_CR,
  			ns, scan->name, value, ns, scan->name);
  	which = DAV_PROP_INSERT_VALUE;
      }
      else {
  	/* use D: prefix to refer to the DAV: namespace URI */
  	s = ap_psprintf(p, "<lp%d:%s/>" DEBUG_CR, ns, scan->name);
  	which = DAV_PROP_INSERT_NAME;
      }
      dav_text_append(p, phdr, s);
  
      /* we inserted a name or value (this prop is done) */
      return which;
  }
  
  static void dav_fs_insert_all(const dav_resource *resource, int insvalue,
  			      const int *ns_map, dav_text_header *phdr)
  {
      if (!resource->exists) {
  	/* a lock-null resource */
  	/*
  	** ### technically, we should insert empty properties. dunno offhand
  	** ### what part of the spec said this, but it was essentially thus:
  	** ### "the properties should be defined, but may have no value".
  	*/
  	return;
      }
  
      (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_creationdate,
  			      insvalue, ns_map, phdr);
      (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_getcontentlength,
  			      insvalue, ns_map, phdr);
      (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_getlastmodified,
  			      insvalue, ns_map, phdr);
      (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_getetag,
  			      insvalue, ns_map, phdr);
  
  #ifndef WIN32
      /*
      ** Note: this property is not defined on the Win32 platform.
      **       dav_fs_insert_prop() won't insert it, but we may as
      **       well not even call it.
      */
      (void) dav_fs_insert_prop(resource, DAV_PROPID_FS_executable,
  			      insvalue, ns_map, phdr);
  #endif
  
      /* ### we know the others aren't defined as liveprops */
  }
  
  static dav_prop_rw dav_fs_is_writeable(const dav_resource *resource,
  				       int propid)
  {
      if (!DAV_PROPID_FS_OURS(propid))
  	return DAV_PROP_RW_NOTME;
  
      if (propid == DAV_PROPID_FS_displayname
  	|| propid == DAV_PROPID_FS_source
  #ifndef WIN32
          /* this property is not usable (writeable) on the Win32 platform */
  	|| (propid == DAV_PROPID_FS_executable && !resource->collection)
  #endif
  	)
  	return DAV_PROP_RW_YES;
  
      return DAV_PROP_RW_NO;
  }
  
  static dav_error *dav_fs_patch_validate(const dav_resource *resource,
  					const dav_xml_elem *elem,
  					int operation,
  					void **context,
  					int *defer_to_dead)
  {
      const dav_text *cdata;
      const dav_text *f_cdata;
      char value;
  
      if (elem->propid != DAV_PROPID_FS_executable) {
  	*defer_to_dead = 1;
  	return NULL;
      }
  
      if (operation == DAV_PROP_OP_DELETE) {
  	return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
  			     "The 'executable' property cannot be removed.");
      }
  
      cdata = elem->first_cdata.first;
      f_cdata = elem->last_child == NULL
  	? NULL
  	: elem->last_child->following_cdata.first;
  
      /* DBG3("name=%s  cdata=%s  f_cdata=%s",elem->name,cdata ? cdata->text : "[null]",f_cdata ? f_cdata->text : "[null]"); */
  
      if (cdata == NULL) {
  	if (f_cdata == NULL) {
  	    return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
  				 "The 'executable' property expects a single "
  				 "character, valued 'T' or 'F'. There was no "
  				 "value submitted.");
  	}
  	cdata = f_cdata;
      }
      else if (f_cdata != NULL)
  	goto too_long;
  
      if (cdata->next != NULL || strlen(cdata->text) != 1)
  	goto too_long;
  
      value = cdata->text[0];
      if (value != 'T' && value != 'F') {
  	return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
  			     "The 'executable' property expects a single "
  			     "character, valued 'T' or 'F'. The value "
  			     "submitted is invalid.");
      }
  
      *context = (void *)(value == 'T');
  
      return NULL;
  
    too_long:
      return dav_new_error(resource->info->pool, HTTP_CONFLICT, 0,
  			 "The 'executable' property expects a single "
  			 "character, valued 'T' or 'F'. The value submitted"
  			 "has too many characters.");
  
  }
  
  static dav_error *dav_fs_patch_exec(dav_resource *resource,
  				    const dav_xml_elem *elem,
  				    int operation,
  				    void *context,
  				    dav_liveprop_rollback **rollback_ctx)
  {
      int value = context != NULL;
      mode_t mode = resource->info->finfo.st_mode;
      int old_value = (resource->info->finfo.st_mode & DAV_FS_MODE_XUSR) != 0;
  
      /* assert: prop == executable. operation == SET. */
  
      /* don't do anything if there is no change. no rollback info either. */
      if (value == old_value)
  	return NULL;
  
      mode &= ~DAV_FS_MODE_XUSR;
      if (value)
  	mode |= DAV_FS_MODE_XUSR;
  
      if (chmod(resource->info->pathname, mode) == -1) {
  	return dav_new_error(resource->info->pool,
  			     HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "Could not set the executable flag of the "
  			     "target resource.");
      }
  
      /* update the resource and set up the rollback context */
      resource->info->finfo.st_mode = mode;
      *rollback_ctx = (dav_liveprop_rollback *)old_value;
  
      return NULL;
  }
  
  static void dav_fs_patch_commit(dav_resource *resource,
  				int operation,
  				void *context,
  				dav_liveprop_rollback *rollback_ctx)
  {
      /* nothing to do */
  }
  
  static dav_error *dav_fs_patch_rollback(dav_resource *resource,
  					int operation,
  					void *context,
  					dav_liveprop_rollback *rollback_ctx)
  {
      mode_t mode = resource->info->finfo.st_mode & ~DAV_FS_MODE_XUSR;
      int value = rollback_ctx != NULL;
  
      /* assert: prop == executable. operation == SET. */
  
      /* restore the executable bit */
      if (value)
  	mode |= DAV_FS_MODE_XUSR;
  
      if (chmod(resource->info->pathname, mode) == -1) {
  	return dav_new_error(resource->info->pool,
  			     HTTP_INTERNAL_SERVER_ERROR, 0,
  			     "After a failure occurred, the resource's "
  			     "executable flag could not be restored.");
      }
  
      /* restore the resource's state */
      resource->info->finfo.st_mode = mode;
  
      return NULL;
  }
  
  
  static const dav_hooks_liveprop dav_hooks_liveprop_fs =
  {
  #ifdef WIN32
      NULL,
  #else
      "http://apache.org/dav/propset/fs/1",	/* filesystem, set 1 */
  #endif
      dav_fs_find_prop,
      dav_fs_insert_prop,
      dav_fs_insert_all,
      dav_fs_is_writeable,
      dav_fs_namespace_uris,
      dav_fs_patch_validate,
      dav_fs_patch_exec,
      dav_fs_patch_commit,
      dav_fs_patch_rollback,
  };
  
  /*
  ** Note: we do not provide an is_active function at this point. In the
  ** future, mod_dav may use that to determine if a particular provider is
  ** active/enabled, but it doesn't now.
  */
  static const dav_dyn_provider dav_dyn_providers_fs[] =
  {
      /* repository provider */
      {
  	DAV_FS_PROVIDER_ID,
          DAV_DYN_TYPE_REPOSITORY,
          &dav_hooks_repository_fs,
          NULL
      },
      /* liveprop provider */
      {
  	DAV_FS_PROVIDER_ID,
          DAV_DYN_TYPE_LIVEPROP,
          &dav_hooks_liveprop_fs,
          NULL
      },
      /* propdb provider */
      {
  	DAV_FS_PROVIDER_ID,
          DAV_DYN_TYPE_PROPDB,
          &dav_hooks_db_dbm,
          NULL
      },
      /* locks provider */
      {
  	DAV_FS_PROVIDER_ID,
          DAV_DYN_TYPE_LOCKS,
          &dav_hooks_locks_fs,
          NULL
      },
      /* must always be last */
      DAV_DYN_END_MARKER
  };
  
  const dav_dyn_module dav_dyn_module_default =
  {
      DAV_DYN_MAGIC,
      DAV_DYN_VERSION,
      "filesystem",
  
      NULL, /* module_open */
      NULL, /* module_close */
      NULL, /* dir_open */
      NULL, /* dir_param */
      NULL, /* dir_merge */
      NULL, /* dir_close */
  
      dav_dyn_providers_fs
  };
  
  
  
  1.1                  apache-2.0/src/modules/dav/fs/repos.h
  
  Index: repos.h
  ===================================================================
  /*
  ** Copyright (C) 1998-2000 Greg Stein. All Rights Reserved.
  **
  ** By using this file, you agree to the terms and conditions set forth in
  ** the LICENSE.html file which can be found at the top level of the mod_dav
  ** distribution or at http://www.webdav.org/mod_dav/license-1.html.
  **
  ** Contact information:
  **   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  **   gstein@lyra.org, http://www.webdav.org/mod_dav/
  */
  
  /*
  ** Declarations for the filesystem repository implementation
  **
  ** Written by John Vasta, vasta@rational.com, by separating from mod_dav.h
  */
  
  #ifndef _DAV_FS_REPOS_H_
  #define _DAV_FS_REPOS_H_
  
  /* the subdirectory to hold all DAV-related information for a directory */
  #define DAV_FS_STATE_DIR		".DAV"
  #define DAV_FS_STATE_FILE_FOR_DIR	".state_for_dir"
  #define DAV_FS_LOCK_NULL_FILE	        ".locknull"
  
  #ifndef WIN32
  
  #define DAV_FS_MODE_DIR		(S_IRWXU | S_IRWXG)
  #define DAV_FS_MODE_FILE	(S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)
  #define DAV_FS_MODE_XUSR    (S_IXUSR)
  
  #else /* WIN32 */
  
  #define DAV_FS_MODE_DIR		(_S_IREAD | _S_IWRITE)
  #define DAV_FS_MODE_FILE	(_S_IREAD | _S_IWRITE)
  #define DAV_FS_MODE_XUSR    (_S_IEXEC)
  
  #include <limits.h>
  
  typedef int ssize_t;
  
  #define mkdir(p,m)		_mkdir(p)
  
  #endif /* WIN32 */
  
  /* ensure that our state subdirectory is present */
  void dav_fs_ensure_state_dir(pool *p, const char *dirname);
  
  /* return the storage pool associated with a resource */
  pool *dav_fs_pool(const dav_resource *resource);
  
  /* return the full pathname for a resource */
  const char *dav_fs_pathname(const dav_resource *resource);
  
  /* return the directory and filename for a resource */
  void dav_fs_dir_file_name(const dav_resource *resource,
  			  const char **dirpath,
  			  const char **fname);
  
  /* return the list of locknull members in this resource's directory */
  dav_error * dav_fs_get_locknull_members(const dav_resource *resource,
                                          dav_buffer *pbuf);
  
  
  /* DBM functions used by the repository and locking providers */
  extern const dav_hooks_db dav_hooks_db_dbm;
  
  dav_error * dav_dbm_open_direct(pool *p, const char *pathname, int ro,
  				dav_db **pdb);
  void dav_dbm_get_statefiles(pool *p, const char *fname,
  			    const char **state1, const char **state2);
  
  
  #endif /* _DAV_FS_REPOS_H_ */
  
  
  
  1.1                  apache-2.0/src/lib/sdbm/sdbm.c
  
  Index: sdbm.c
  ===================================================================
  /*
   * sdbm - ndbm work-alike hashed database library
   * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978).
   * author: oz@nexus.yorku.ca
   * status: public domain.
   *
   * core routines
   */
  
  #include "sdbm.h"
  #include "sdbm_tune.h"
  #include "sdbm_pair.h"
  
  #include <sys/types.h>
  #include <sys/stat.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <string.h>
  #include <stdlib.h>
  #ifdef WIN32
  #include <io.h>
  #include <stdio.h>
  #else
  #include <unistd.h>	/* for lseek() */
  #endif
  
  
  /*
   * forward
   */
  static int getdbit proto((DBM *, long));
  static int setdbit proto((DBM *, long));
  static int getpage proto((DBM *, long));
  static datum getnext proto((DBM *));
  static int makroom proto((DBM *, long, int));
  
  /*
   * useful macros
   */
  #define bad(x)		((x).dptr == NULL || (x).dsize <= 0)
  #define exhash(item)	sdbm_hash((item).dptr, (item).dsize)
  #define ioerr(db)	((db)->flags |= DBM_IOERR)
  
  #define OFF_PAG(off)	(long) (off) * PBLKSIZ
  #define OFF_DIR(off)	(long) (off) * DBLKSIZ
  
  static long masks[] = {
  	000000000000, 000000000001, 000000000003, 000000000007,
  	000000000017, 000000000037, 000000000077, 000000000177,
  	000000000377, 000000000777, 000000001777, 000000003777,
  	000000007777, 000000017777, 000000037777, 000000077777,
  	000000177777, 000000377777, 000000777777, 000001777777,
  	000003777777, 000007777777, 000017777777, 000037777777,
  	000077777777, 000177777777, 000377777777, 000777777777,
  	001777777777, 003777777777, 007777777777, 017777777777
  };
  
  datum nullitem = {NULL, 0};
  
  DBM *
  sdbm_open(file, flags, mode)
  register char *file;
  register int flags;
  register int mode;
  {
  	register DBM *db;
  	register char *dirname;
  	register char *pagname;
  	register int n;
  
  	if (file == NULL || !*file)
  		return errno = EINVAL, (DBM *) NULL;
  /*
   * need space for two seperate filenames
   */
  	n = strlen(file) * 2 + strlen(DIRFEXT) + strlen(PAGFEXT) + 2;
  
  	if ((dirname = malloc((unsigned) n)) == NULL)
  		return errno = ENOMEM, (DBM *) NULL;
  /*
   * build the file names
   */
  	dirname = strcat(strcpy(dirname, file), DIRFEXT);
  	pagname = strcpy(dirname + strlen(dirname) + 1, file);
  	pagname = strcat(pagname, PAGFEXT);
  
  	db = sdbm_prep(dirname, pagname, flags, mode);
  	free((char *) dirname);
  	return db;
  }
  
  DBM *
  sdbm_prep(dirname, pagname, flags, mode)
  char *dirname;
  char *pagname;
  int flags;
  int mode;
  {
  	register DBM *db;
  	struct stat dstat;
  
  	if ((db = (DBM *) malloc(sizeof(DBM))) == NULL)
  		return errno = ENOMEM, (DBM *) NULL;
  
          db->flags = 0;
          db->hmask = 0;
          db->blkptr = 0;
          db->keyptr = 0;
  /*
   * adjust user flags so that WRONLY becomes RDWR, 
   * as required by this package. Also set our internal
   * flag for RDONLY if needed.
   */
  	if (flags & O_WRONLY)
  		flags = (flags & ~O_WRONLY) | O_RDWR;
  
  	else if ((flags & 03) == O_RDONLY)
  		db->flags = DBM_RDONLY;
  /*
   * open the files in sequence, and stat the dirfile.
   * If we fail anywhere, undo everything, return NULL.
   */
  #if defined(OS2) || defined(MSDOS) || defined(WIN32)
  	flags |= O_BINARY;
  #endif
  	if ((db->pagf = open(pagname, flags, mode)) > -1) {
  	    if ( sdbm_fd_lock(db->pagf, sdbm_rdonly(db)) > -1 ) {
  		if ((db->dirf = open(dirname, flags, mode)) > -1) {
  /*
   * need the dirfile size to establish max bit number.
   */
  			if (fstat(db->dirf, &dstat) == 0) {
  /*
   * zero size: either a fresh database, or one with a single,
   * unsplit data page: dirpage is all zeros.
   */
  				db->dirbno = (!dstat.st_size) ? 0 : -1;
  				db->pagbno = -1;
  				db->maxbno = dstat.st_size * BYTESIZ;
  
  				(void) memset(db->pagbuf, 0, PBLKSIZ);
  				(void) memset(db->dirbuf, 0, DBLKSIZ);
  			/*
  			 * success
  			 */
  				return db;
  			}
  			(void) close(db->dirf);
  		}
  		(void) sdbm_fd_unlock(db->pagf);
  	    }
  	    (void) close(db->pagf);
  	}
  	free((char *) db);
  	return (DBM *) NULL;
  }
  
  void
  sdbm_close(db)
  register DBM *db;
  {
  	if (db == NULL)
  		errno = EINVAL;
  	else {
  		(void) close(db->dirf);
  		(void) sdbm_fd_unlock(db->pagf);
  		(void) close(db->pagf);
  		free((char *) db);
  	}
  }
  
  datum
  sdbm_fetch(db, key)
  register DBM *db;
  datum key;
  {
  	if (db == NULL || bad(key))
  		return errno = EINVAL, nullitem;
  
  	if (getpage(db, exhash(key)))
  		return getpair(db->pagbuf, key);
  
  	return ioerr(db), nullitem;
  }
  
  int
  sdbm_delete(db, key)
  register DBM *db;
  datum key;
  {
  	if (db == NULL || bad(key))
  		return errno = EINVAL, -1;
  	if (sdbm_rdonly(db))
  		return errno = EPERM, -1;
  
  	if (getpage(db, exhash(key))) {
  		if (!delpair(db->pagbuf, key))
  			return -1;
  /*
   * update the page file
   */
  		if (lseek(db->pagf, OFF_PAG(db->pagbno), SEEK_SET) < 0
  		    || write(db->pagf, db->pagbuf, PBLKSIZ) < 0)
  			return ioerr(db), -1;
  
  		return 0;
  	}
  
  	return ioerr(db), -1;
  }
  
  int
  sdbm_store(db, key, val, flags)
  register DBM *db;
  datum key;
  datum val;
  int flags;
  {
  	int need;
  	register long hash;
  
  	if (db == NULL || bad(key))
  		return errno = EINVAL, -1;
  	if (sdbm_rdonly(db))
  		return errno = EPERM, -1;
  
  	need = key.dsize + val.dsize;
  /*
   * is the pair too big (or too small) for this database ??
   */
  	if (need < 0 || need > PAIRMAX)
  		return errno = EINVAL, -1;
  
  	if (getpage(db, (hash = exhash(key)))) {
  /*
   * if we need to replace, delete the key/data pair
   * first. If it is not there, ignore.
   */
  		if (flags == DBM_REPLACE)
  			(void) delpair(db->pagbuf, key);
  #ifdef SEEDUPS
  		else if (duppair(db->pagbuf, key))
  			return 1;
  #endif
  /*
   * if we do not have enough room, we have to split.
   */
  		if (!fitpair(db->pagbuf, need))
  			if (!makroom(db, hash, need))
  				return ioerr(db), -1;
  /*
   * we have enough room or split is successful. insert the key,
   * and update the page file.
   */
  		(void) putpair(db->pagbuf, key, val);
  
  		if (lseek(db->pagf, OFF_PAG(db->pagbno), SEEK_SET) < 0
  		    || write(db->pagf, db->pagbuf, PBLKSIZ) < 0)
  			return ioerr(db), -1;
  	/*
  	 * success
  	 */
  		return 0;
  	}
  
  	return ioerr(db), -1;
  }
  
  /*
   * makroom - make room by splitting the overfull page
   * this routine will attempt to make room for SPLTMAX times before
   * giving up.
   */
  static int
  makroom(db, hash, need)
  register DBM *db;
  long hash;
  int need;
  {
  	long newp;
  	char twin[PBLKSIZ];
  	char *pag = db->pagbuf;
  	char *new = twin;
  	register int smax = SPLTMAX;
  
  	do {
  /*
   * split the current page
   */
  		(void) splpage(pag, new, db->hmask + 1);
  /*
   * address of the new page
   */
  		newp = (hash & db->hmask) | (db->hmask + 1);
  
  /*
   * write delay, read avoidence/cache shuffle:
   * select the page for incoming pair: if key is to go to the new page,
   * write out the previous one, and copy the new one over, thus making
   * it the current page. If not, simply write the new page, and we are
   * still looking at the page of interest. current page is not updated
   * here, as sdbm_store will do so, after it inserts the incoming pair.
   */
  		if (hash & (db->hmask + 1)) {
  			if (lseek(db->pagf, OFF_PAG(db->pagbno), SEEK_SET) < 0
  			    || write(db->pagf, db->pagbuf, PBLKSIZ) < 0)
  				return 0;
  			db->pagbno = newp;
  			(void) memcpy(pag, new, PBLKSIZ);
  		}
  		else if (lseek(db->pagf, OFF_PAG(newp), SEEK_SET) < 0
  			 || write(db->pagf, new, PBLKSIZ) < 0)
  			return 0;
  
  		if (!setdbit(db, db->curbit))
  			return 0;
  /*
   * see if we have enough room now
   */
  		if (fitpair(pag, need))
  			return 1;
  /*
   * try again... update curbit and hmask as getpage would have
   * done. because of our update of the current page, we do not
   * need to read in anything. BUT we have to write the current
   * [deferred] page out, as the window of failure is too great.
   */
  		db->curbit = 2 * db->curbit +
  			((hash & (db->hmask + 1)) ? 2 : 1);
  		db->hmask |= db->hmask + 1;
  
  		if (lseek(db->pagf, OFF_PAG(db->pagbno), SEEK_SET) < 0
  		    || write(db->pagf, db->pagbuf, PBLKSIZ) < 0)
  			return 0;
  
  	} while (--smax);
  /*
   * if we are here, this is real bad news. After SPLTMAX splits,
   * we still cannot fit the key. say goodnight.
   */
  #ifdef BADMESS
  	(void) write(2, "sdbm: cannot insert after SPLTMAX attempts.\n", 44);
  #endif
  	return 0;
  
  }
  
  /*
   * the following two routines will break if
   * deletions aren't taken into account. (ndbm bug)
   */
  datum
  sdbm_firstkey(db)
  register DBM *db;
  {
  	if (db == NULL)
  		return errno = EINVAL, nullitem;
  /*
   * start at page 0
   */
  	if (lseek(db->pagf, OFF_PAG(0), SEEK_SET) < 0
  	    || read(db->pagf, db->pagbuf, PBLKSIZ) < 0)
  		return ioerr(db), nullitem;
  	db->pagbno = 0;
  	db->blkptr = 0;
  	db->keyptr = 0;
  
  	return getnext(db);
  }
  
  datum
  sdbm_nextkey(db)
  register DBM *db;
  {
  	if (db == NULL)
  		return errno = EINVAL, nullitem;
  	return getnext(db);
  }
  
  /*
   * all important binary trie traversal
   */
  static int
  getpage(db, hash)
  register DBM *db;
  register long hash;
  {
  	register int hbit;
  	register long dbit;
  	register long pagb;
  
  	dbit = 0;
  	hbit = 0;
  	while (dbit < db->maxbno && getdbit(db, dbit))
  		dbit = 2 * dbit + ((hash & (1 << hbit++)) ? 2 : 1);
  
  	debug(("dbit: %d...", dbit));
  
  	db->curbit = dbit;
  	db->hmask = masks[hbit];
  
  	pagb = hash & db->hmask;
  /*
   * see if the block we need is already in memory.
   * note: this lookaside cache has about 10% hit rate.
   */
  	if (pagb != db->pagbno) { 
  /*
   * note: here, we assume a "hole" is read as 0s.
   * if not, must zero pagbuf first.
   */
  		if (lseek(db->pagf, OFF_PAG(pagb), SEEK_SET) < 0
  		    || read(db->pagf, db->pagbuf, PBLKSIZ) < 0)
  			return 0;
  		if (!chkpage(db->pagbuf))
  			return 0;
  		db->pagbno = pagb;
  
  		debug(("pag read: %d\n", pagb));
  	}
  	return 1;
  }
  
  static int
  getdbit(db, dbit)
  register DBM *db;
  register long dbit;
  {
  	register long c;
  	register long dirb;
  
  	c = dbit / BYTESIZ;
  	dirb = c / DBLKSIZ;
  
  	if (dirb != db->dirbno) {
  		if (lseek(db->dirf, OFF_DIR(dirb), SEEK_SET) < 0
  		    || read(db->dirf, db->dirbuf, DBLKSIZ) < 0)
  			return 0;
  		db->dirbno = dirb;
  
  		debug(("dir read: %d\n", dirb));
  	}
  
  	return db->dirbuf[c % DBLKSIZ] & (1 << dbit % BYTESIZ);
  }
  
  static int
  setdbit(db, dbit)
  register DBM *db;
  register long dbit;
  {
  	register long c;
  	register long dirb;
  
  	c = dbit / BYTESIZ;
  	dirb = c / DBLKSIZ;
  
  	if (dirb != db->dirbno) {
  		if (lseek(db->dirf, OFF_DIR(dirb), SEEK_SET) < 0
  		    || read(db->dirf, db->dirbuf, DBLKSIZ) < 0)
  			return 0;
  		db->dirbno = dirb;
  
  		debug(("dir read: %d\n", dirb));
  	}
  
  	db->dirbuf[c % DBLKSIZ] |= (1 << dbit % BYTESIZ);
  
  	if (dbit >= db->maxbno)
  		db->maxbno += DBLKSIZ * BYTESIZ;
  
  	if (lseek(db->dirf, OFF_DIR(dirb), SEEK_SET) < 0
  	    || write(db->dirf, db->dirbuf, DBLKSIZ) < 0)
  		return 0;
  
  	return 1;
  }
  
  /*
   * getnext - get the next key in the page, and if done with
   * the page, try the next page in sequence
   */
  static datum
  getnext(db)
  register DBM *db;
  {
  	datum key;
  
  	for (;;) {
  		db->keyptr++;
  		key = getnkey(db->pagbuf, db->keyptr);
  		if (key.dptr != NULL)
  			return key;
  /*
   * we either run out, or there is nothing on this page..
   * try the next one... If we lost our position on the
   * file, we will have to seek.
   */
  		db->keyptr = 0;
  		if (db->pagbno != db->blkptr++)
  			if (lseek(db->pagf, OFF_PAG(db->blkptr), SEEK_SET) < 0)
  				break;
  		db->pagbno = db->blkptr;
  		if (read(db->pagf, db->pagbuf, PBLKSIZ) <= 0)
  			break;
  		if (!chkpage(db->pagbuf))
  			break;
  	}
  
  	return ioerr(db), nullitem;
  }
  
  
  
  1.1                  apache-2.0/src/lib/sdbm/sdbm.h
  
  Index: sdbm.h
  ===================================================================
  /*
   * sdbm - ndbm work-alike hashed database library
   * based on Per-Ake Larson's Dynamic Hashing algorithms. BIT 18 (1978).
   * author: oz@nexus.yorku.ca
   * status: public domain. 
   */
  /* increase the block/page size and what can be inserted */
  #if 1
  #define DBLKSIZ 16384
  #define PBLKSIZ 8192
  #define PAIRMAX 8008			/* arbitrary on PBLKSIZ-N */
  #else
  #define DBLKSIZ 4096
  #define PBLKSIZ 1024
  #define PAIRMAX 1008			/* arbitrary on PBLKSIZ-N */
  #endif
  #define SPLTMAX	10			/* maximum allowed splits */
  					/* for a single insertion */
  #define DIRFEXT	".dir"
  #define PAGFEXT	".pag"
  
  typedef struct {
  	int dirf;		       /* directory file descriptor */
  	int pagf;		       /* page file descriptor */
  	int flags;		       /* status/error flags, see below */
  	long maxbno;		       /* size of dirfile in bits */
  	long curbit;		       /* current bit number */
  	long hmask;		       /* current hash mask */
  	long blkptr;		       /* current block for nextkey */
  	int keyptr;		       /* current key for nextkey */
  	long blkno;		       /* current page to read/write */
  	long pagbno;		       /* current page in pagbuf */
  	char pagbuf[PBLKSIZ];	       /* page file block buffer */
  	long dirbno;		       /* current block in dirbuf */
  	char dirbuf[DBLKSIZ];	       /* directory file block buffer */
  } DBM;
  
  #define DBM_RDONLY	0x1	       /* data base open read-only */
  #define DBM_IOERR	0x2	       /* data base I/O error */
  
  /*
   * utility macros
   */
  #define sdbm_rdonly(db)		((db)->flags & DBM_RDONLY)
  #define sdbm_error(db)		((db)->flags & DBM_IOERR)
  
  #define sdbm_clearerr(db)	((db)->flags &= ~DBM_IOERR)  /* ouch */
  
  #define sdbm_dirfno(db)	((db)->dirf)
  #define sdbm_pagfno(db)	((db)->pagf)
  
  typedef struct {
  	char *dptr;
  	int dsize;
  } datum;
  
  extern datum nullitem;
  
  #ifdef __STDC__
  #define proto(p) p
  #else
  #define proto(p) ()
  #endif
  
  /*
   * flags to sdbm_store
   */
  #define DBM_INSERT	0
  #define DBM_REPLACE	1
  
  /*
   * ndbm interface
   */
  extern DBM *sdbm_open proto((char *, int, int));
  extern void sdbm_close proto((DBM *));
  extern datum sdbm_fetch proto((DBM *, datum));
  extern int sdbm_delete proto((DBM *, datum));
  extern int sdbm_store proto((DBM *, datum, datum, int));
  extern datum sdbm_firstkey proto((DBM *));
  extern datum sdbm_nextkey proto((DBM *));
  
  /*
   * other
   */
  extern DBM *sdbm_prep proto((char *, char *, int, int));
  extern long sdbm_hash proto((char *, int));
  
  
  
  1.1                  apache-2.0/src/lib/sdbm/sdbm_hash.c
  
  Index: sdbm_hash.c
  ===================================================================
  /*
   * sdbm - ndbm work-alike hashed database library
   * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978).
   * author: oz@nexus.yorku.ca
   * status: public domain. keep it that way.
   *
   * hashing routine
   */
  
  #include "sdbm.h"
  /*
   * polynomial conversion ignoring overflows
   * [this seems to work remarkably well, in fact better
   * then the ndbm hash function. Replace at your own risk]
   * use: 65599	nice.
   *      65587   even better. 
   */
  long
  sdbm_hash(str, len)
  register char *str;
  register int len;
  {
  	register unsigned long n = 0;
  
  #define DUFF	/* go ahead and use the loop-unrolled version */
  #ifdef DUFF
  
  #define HASHC	n = *str++ + 65599 * n
  
  	if (len > 0) {
  		register int loop = (len + 8 - 1) >> 3;
  
  		switch(len & (8 - 1)) {
  		case 0:	do {
  			HASHC;	case 7:	HASHC;
  		case 6:	HASHC;	case 5:	HASHC;
  		case 4:	HASHC;	case 3:	HASHC;
  		case 2:	HASHC;	case 1:	HASHC;
  			} while (--loop);
  		}
  
  	}
  #else
  	while (len--)
  		n = *str++ + 65599 * n;
  #endif
  	return n;
  }
  
  
  
  1.1                  apache-2.0/src/lib/sdbm/sdbm_lock.c
  
  Index: sdbm_lock.c
  ===================================================================
  /*
  ** File locking
  **
  ** Snarfed from mod_rewrite.c. Munged up for our use.
  */
  
  #include "ap_config.h"
  
      /* The locking support:
       * Try to determine whether we should use fcntl() or flock().
       * Would be better ap_config.h could provide this... :-(
       */
  #if defined(USE_FCNTL_SERIALIZED_ACCEPT)
  #define USE_FCNTL 1
  #include <fcntl.h>
  #endif
  #if defined(USE_FLOCK_SERIALIZED_ACCEPT)
  #define USE_FLOCK 1
  #include <sys/file.h>
  #endif
  #if !defined(USE_FCNTL) && !defined(USE_FLOCK)
  #define USE_FLOCK 1
  #if !defined(MPE) && !defined(WIN32)
  #include <sys/file.h>
  #endif
  #ifndef LOCK_UN
  #undef USE_FLOCK
  #define USE_FCNTL 1
  #include <fcntl.h>
  #endif
  #endif
  #ifdef AIX
  #undef USE_FLOCK
  #define USE_FCNTL 1
  #include <fcntl.h>
  #endif
  #ifdef WIN32
  #undef USE_FCNTL
  #define USE_LOCKING
  #include <sys/locking.h>
  #endif
  
  
  #ifdef USE_FCNTL
  /* ugly interface requires this structure to be "live" for a while */
  static struct flock   lock_it;
  static struct flock unlock_it;
  #endif
  
  /* NOTE: this function blocks until it acquires the lock */
  int sdbm_fd_lock(int fd, int readonly)
  {
      int rc;
  
  #ifdef USE_FCNTL
      lock_it.l_whence = SEEK_SET; /* from current point */
      lock_it.l_start  = 0;        /* -"- */
      lock_it.l_len    = 0;        /* until end of file */
      lock_it.l_type   = readonly ? F_RDLCK : F_WRLCK;  /* set lock type */
      lock_it.l_pid    = 0;        /* pid not actually interesting */
  
      while (   ((rc = fcntl(fd, F_SETLKW, &lock_it)) < 0)
                && (errno == EINTR)                               ) {
          continue;
      }
  #endif
  #ifdef USE_FLOCK
      while (   ((rc = flock(fd, readonly ? LOCK_SH : LOCK_EX)) < 0)
                && (errno == EINTR)               ) {
          continue;
      }
  #endif
  #ifdef USE_LOCKING
      /* ### this doesn't allow simultaneous reads! */
      /* ### this doesn't block forever */
      /* Lock the first byte */
      lseek(fd, 0, SEEK_SET);
      rc = _locking(fd, _LK_LOCK, 1);
  #endif
  
      return rc;
  }
  
  int sdbm_fd_unlock(int fd)
  {
      int rc;
  
  #ifdef USE_FCNTL
      unlock_it.l_whence = SEEK_SET; /* from current point */
      unlock_it.l_start  = 0;        /* -"- */
      unlock_it.l_len    = 0;        /* until end of file */
      unlock_it.l_type   = F_UNLCK;  /* unlock */
      unlock_it.l_pid    = 0;        /* pid not actually interesting */
  
      rc = fcntl(fd, F_SETLKW, &unlock_it);
  #endif
  #ifdef USE_FLOCK
      rc = flock(fd, LOCK_UN);
  #endif
  #ifdef USE_LOCKING
      lseek(fd, 0, SEEK_SET);
      rc = _locking(fd, _LK_UNLCK, 1);
  #endif
  
      return rc;
  }
  
  
  
  1.1                  apache-2.0/src/lib/sdbm/sdbm_pair.c
  
  Index: sdbm_pair.c
  ===================================================================
  /*
   * sdbm - ndbm work-alike hashed database library
   * based on Per-Aake Larson's Dynamic Hashing algorithms. BIT 18 (1978).
   * author: oz@nexus.yorku.ca
   * status: public domain.
   *
   * page-level routines
   */
  
  #include "sdbm.h"
  #include "sdbm_tune.h"
  #include "sdbm_pair.h"
  
  #include <string.h>	/* for memset() */
  
  
  #define exhash(item)	sdbm_hash((item).dptr, (item).dsize)
  
  /* 
   * forward 
   */
  static int seepair proto((char *, int, char *, int));
  
  /*
   * page format:
   *	+------------------------------+
   * ino	| n | keyoff | datoff | keyoff |
   * 	+------------+--------+--------+
   *	| datoff | - - - ---->	       |
   *	+--------+---------------------+
   *	|	 F R E E A R E A       |
   *	+--------------+---------------+
   *	|  <---- - - - | data          |
   *	+--------+-----+----+----------+
   *	|  key   | data     | key      |
   *	+--------+----------+----------+
   *
   * calculating the offsets for free area:  if the number
   * of entries (ino[0]) is zero, the offset to the END of
   * the free area is the block size. Otherwise, it is the
   * nth (ino[ino[0]]) entry's offset.
   */
  
  int
  fitpair(pag, need)
  char *pag;
  int need;
  {
  	register int n;
  	register int off;
  	register int avail;
  	register short *ino = (short *) pag;
  
  	off = ((n = ino[0]) > 0) ? ino[n] : PBLKSIZ;
  	avail = off - (n + 1) * sizeof(short);
  	need += 2 * sizeof(short);
  
  	debug(("avail %d need %d\n", avail, need));
  
  	return need <= avail;
  }
  
  void
  putpair(pag, key, val)
  char *pag;
  datum key;
  datum val;
  {
  	register int n;
  	register int off;
  	register short *ino = (short *) pag;
  
  	off = ((n = ino[0]) > 0) ? ino[n] : PBLKSIZ;
  /*
   * enter the key first
   */
  	off -= key.dsize;
  	(void) memcpy(pag + off, key.dptr, key.dsize);
  	ino[n + 1] = off;
  /*
   * now the data
   */
  	off -= val.dsize;
  	(void) memcpy(pag + off, val.dptr, val.dsize);
  	ino[n + 2] = off;
  /*
   * adjust item count
   */
  	ino[0] += 2;
  }
  
  datum
  getpair(pag, key)
  char *pag;
  datum key;
  {
  	register int i;
  	register int n;
  	datum val;
  	register short *ino = (short *) pag;
  
  	if ((n = ino[0]) == 0)
  		return nullitem;
  
  	if ((i = seepair(pag, n, key.dptr, key.dsize)) == 0)
  		return nullitem;
  
  	val.dptr = pag + ino[i + 1];
  	val.dsize = ino[i] - ino[i + 1];
  	return val;
  }
  
  #ifdef SEEDUPS
  int
  duppair(pag, key)
  char *pag;
  datum key;
  {
  	register short *ino = (short *) pag;
  	return ino[0] > 0 && seepair(pag, ino[0], key.dptr, key.dsize) > 0;
  }
  #endif
  
  datum
  getnkey(pag, num)
  char *pag;
  int num;
  {
  	datum key;
  	register int off;
  	register short *ino = (short *) pag;
  
  	num = num * 2 - 1;
  	if (ino[0] == 0 || num > ino[0])
  		return nullitem;
  
  	off = (num > 1) ? ino[num - 1] : PBLKSIZ;
  
  	key.dptr = pag + ino[num];
  	key.dsize = off - ino[num];
  
  	return key;
  }
  
  int
  delpair(pag, key)
  char *pag;
  datum key;
  {
  	register int n;
  	register int i;
  	register short *ino = (short *) pag;
  
  	if ((n = ino[0]) == 0)
  		return 0;
  
  	if ((i = seepair(pag, n, key.dptr, key.dsize)) == 0)
  		return 0;
  /*
   * found the key. if it is the last entry
   * [i.e. i == n - 1] we just adjust the entry count.
   * hard case: move all data down onto the deleted pair,
   * shift offsets onto deleted offsets, and adjust them.
   * [note: 0 < i < n]
   */
  	if (i < n - 1) {
  		register int m;
  		register char *dst = pag + (i == 1 ? PBLKSIZ : ino[i - 1]);
  		register char *src = pag + ino[i + 1];
  		register int   zoo = dst - src;
  
  		debug(("free-up %d ", zoo));
  /*
   * shift data/keys down
   */
  		m = ino[i + 1] - ino[n];
  
  #undef DUFF	/* just use memmove. it should be plenty fast. */
  #ifdef DUFF
  #define MOVB 	*--dst = *--src
  
  		if (m > 0) {
  			register int loop = (m + 8 - 1) >> 3;
  
  			switch (m & (8 - 1)) {
  			case 0:	do {
  				MOVB;	case 7:	MOVB;
  			case 6:	MOVB;	case 5:	MOVB;
  			case 4:	MOVB;	case 3:	MOVB;
  			case 2:	MOVB;	case 1:	MOVB;
  				} while (--loop);
  			}
  		}
  #else
  		dst -= m;
  		src -= m;
  		memmove(dst, src, m);
  #endif
  
  /*
   * adjust offset index up
   */
  		while (i < n - 1) {
  			ino[i] = ino[i + 2] + zoo;
  			i++;
  		}
  	}
  	ino[0] -= 2;
  	return 1;
  }
  
  /*
   * search for the key in the page.
   * return offset index in the range 0 < i < n.
   * return 0 if not found.
   */
  static int
  seepair(pag, n, key, siz)
  char *pag;
  register int n;
  register char *key;
  register int siz;
  {
  	register int i;
  	register int off = PBLKSIZ;
  	register short *ino = (short *) pag;
  
  	for (i = 1; i < n; i += 2) {
  		if (siz == off - ino[i] &&
  		    memcmp(key, pag + ino[i], siz) == 0)
  			return i;
  		off = ino[i + 1];
  	}
  	return 0;
  }
  
  void
  splpage(pag, new, sbit)
  char *pag;
  char *new;
  long sbit;
  {
  	datum key;
  	datum val;
  
  	register int n;
  	register int off = PBLKSIZ;
  	char cur[PBLKSIZ];
  	register short *ino = (short *) cur;
  
  	(void) memcpy(cur, pag, PBLKSIZ);
  	(void) memset(pag, 0, PBLKSIZ);
  	(void) memset(new, 0, PBLKSIZ);
  
  	n = ino[0];
  	for (ino++; n > 0; ino += 2) {
  		key.dptr = cur + ino[0]; 
  		key.dsize = off - ino[0];
  		val.dptr = cur + ino[1];
  		val.dsize = ino[0] - ino[1];
  /*
   * select the page pointer (by looking at sbit) and insert
   */
  		(void) putpair((exhash(key) & sbit) ? new : pag, key, val);
  
  		off = ino[1];
  		n -= 2;
  	}
  
  	debug(("%d split %d/%d\n", ((short *) cur)[0] / 2, 
  	       ((short *) new)[0] / 2,
  	       ((short *) pag)[0] / 2));
  }
  
  /*
   * check page sanity: 
   * number of entries should be something
   * reasonable, and all offsets in the index should be in order.
   * this could be made more rigorous.
   */
  int
  chkpage(pag)
  char *pag;
  {
  	register int n;
  	register int off;
  	register short *ino = (short *) pag;
  
  	if ((n = ino[0]) < 0 || n > PBLKSIZ / sizeof(short))
  		return 0;
  
  	if (n > 0) {
  		off = PBLKSIZ;
  		for (ino++; n > 0; ino += 2) {
  			if (ino[0] > off || ino[1] > off ||
  			    ino[1] > ino[0])
  				return 0;
  			off = ino[1];
  			n -= 2;
  		}
  	}
  	return 1;
  }
  
  
  
  1.1                  apache-2.0/src/lib/sdbm/sdbm_pair.h
  
  Index: sdbm_pair.h
  ===================================================================
  /* Mini EMBED (pair.c) */
  #define chkpage sdbm__chkpage
  #define delpair sdbm__delpair
  #define duppair sdbm__duppair
  #define fitpair sdbm__fitpair
  #define getnkey sdbm__getnkey
  #define getpair sdbm__getpair
  #define putpair sdbm__putpair
  #define splpage sdbm__splpage
  
  extern int fitpair proto((char *, int));
  extern void  putpair proto((char *, datum, datum));
  extern datum	getpair proto((char *, datum));
  extern int  delpair proto((char *, datum));
  extern int  chkpage proto((char *));
  extern datum getnkey proto((char *, int));
  extern void splpage proto((char *, char *, long));
  #ifdef SEEDUPS
  extern int duppair proto((char *, datum));
  #endif
  
  
  
  1.1                  apache-2.0/src/lib/sdbm/sdbm_tune.h
  
  Index: sdbm_tune.h
  ===================================================================
  /*
   * sdbm - ndbm work-alike hashed database library
   * tuning and portability constructs [not nearly enough]
   * author: oz@nexus.yorku.ca
   */
  
  #define BYTESIZ		8
  
  /*
   * important tuning parms (hah)
   */
  
  #define SEEDUPS			/* always detect duplicates */
  #define BADMESS			/* generate a message for worst case:
  				   cannot make room after SPLTMAX splits */
  /*
   * misc
   */
  #ifdef DEBUG
  #define debug(x)	printf x
  #else
  #define debug(x)
  #endif
  
  int sdbm_fd_lock(int fd, int readonly);
  int sdbm_fd_unlock(int fd);
  
  
  

Mime
View raw message