You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by gs...@locus.apache.org on 2000/06/28 10:56:18 UTC
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
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, <l)) != 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(×tamp);
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 '<', '>', and '&'.
** If quotes is true, then replace '"' with '"'.
**
** 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; /* < or > */
else if (c == '&')
extra += 4; /* & */
else if (quotes && c == '"')
extra += 5; /* " */
}
/* 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);
Re: 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
Posted by Ben Laurie <be...@algroup.co.uk>.
gstein@locus.apache.org wrote:
>
> 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).
Cool!
Cheers,
Ben.
--
http://www.apache-ssl.org/ben.html
Coming to ApacheCon Europe 2000? http://apachecon.com/
Re: 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
Posted by Ben Laurie <be...@algroup.co.uk>.
gstein@locus.apache.org wrote:
>
> 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).
Cool!
Cheers,
Ben.
--
http://www.apache-ssl.org/ben.html
Coming to ApacheCon Europe 2000? http://apachecon.com/