** - various utilities, repository-independent
*/
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
#include "mod_dav.h"
#include "http_request.h"
#include "http_log.h"
#include "http_protocol.h"
-#include "apr_strings.h"
-
-dav_error *dav_new_error(apr_pool_t *p, int status, int error_id, const char *desc)
+DAV_DECLARE(dav_error*) dav_new_error(apr_pool_t *p, int status,
+ int error_id, const char *desc)
{
int save_errno = errno;
dav_error *err = apr_pcalloc(p, sizeof(*err));
return err;
}
-dav_error *dav_push_error(apr_pool_t *p, int status, int error_id, const char *desc,
- dav_error *prev)
+DAV_DECLARE(dav_error*) dav_push_error(apr_pool_t *p, int status,
+ int error_id, const char *desc,
+ dav_error *prev)
{
dav_error *err = apr_pcalloc(p, sizeof(*err));
return err;
}
-void dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf, size_t extra_needed)
+DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf,
+ apr_size_t extra_needed)
{
/* grow the buffer if necessary */
if (pbuf->cur_len + extra_needed > pbuf->alloc_len) {
}
}
-void dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf, size_t size)
+DAV_DECLARE(void) dav_set_bufsize(apr_pool_t * p, dav_buffer *pbuf,
+ apr_size_t size)
{
/* NOTE: this does not retain prior contents */
/* initialize a buffer and copy the specified (null-term'd) string into it */
-void dav_buffer_init(apr_pool_t *p, dav_buffer *pbuf, const char *str)
+DAV_DECLARE(void) dav_buffer_init(apr_pool_t *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(apr_pool_t *p, dav_buffer *pbuf, const char *str)
+DAV_DECLARE(void) dav_buffer_append(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
{
size_t len = strlen(str);
}
/* place a string on the end of the buffer, do NOT adjust length */
-void dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf, const char *str)
+DAV_DECLARE(void) dav_buffer_place(apr_pool_t *p, dav_buffer *pbuf,
+ const char *str)
{
size_t len = strlen(str);
}
/* place some memory on the end of a buffer; do NOT adjust length */
-void dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf, const void *mem,
- size_t amt, size_t pad)
+DAV_DECLARE(void) dav_buffer_place_mem(apr_pool_t *p, dav_buffer *pbuf,
+ const void *mem, apr_size_t amt,
+ apr_size_t pad)
{
dav_check_bufsize(p, pbuf, amt + pad);
memcpy(pbuf->buf + pbuf->cur_len, mem, amt);
{
dav_lookup_result result = { 0 };
const char *scheme;
- unsigned short port = ntohs(r->connection->local_addr.sin_port);
+ apr_port_t port;
uri_components comp;
char *new_file;
const char *domain;
the port, must match our port.
the URI must not have a query (args) or a fragment
*/
+ apr_sockaddr_port_get(&port, r->connection->local_addr);
if (strcasecmp(comp.scheme, scheme) != 0 ||
comp.port != port) {
result.err.status = HTTP_BAD_GATEWAY;
* same HTTP method on the destination. This allows the destination
* to apply appropriate restrictions (e.g. readonly).
*/
- result.rnew = ap_sub_req_method_uri(r->method, new_file, r);
+ result.rnew = ap_sub_req_method_uri(r->method, new_file, r, NULL);
return result;
}
return NULL;
}
+/* gather up all the CDATA into a single string */
+const char *dav_xml_get_cdata(const ap_xml_elem *elem, apr_pool_t *pool,
+ int strip_white)
+{
+ apr_size_t len = 0;
+ ap_text *scan;
+ const ap_xml_elem *child;
+ char *cdata;
+ char *s;
+ apr_size_t tlen;
+
+ for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next)
+ len += strlen(scan->text);
+
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ for (scan = child->following_cdata.first;
+ scan != NULL;
+ scan = scan->next)
+ len += strlen(scan->text);
+ }
+
+ cdata = s = apr_palloc(pool, len + 1);
+
+ for (scan = elem->first_cdata.first; scan != NULL; scan = scan->next) {
+ tlen = strlen(scan->text);
+ memcpy(s, scan->text, tlen);
+ s += tlen;
+ }
+
+ for (child = elem->first_child; child != NULL; child = child->next) {
+ for (scan = child->following_cdata.first;
+ scan != NULL;
+ scan = scan->next) {
+ tlen = strlen(scan->text);
+ memcpy(s, scan->text, tlen);
+ s += tlen;
+ }
+ }
+
+ *s = '\0';
+
+ if (strip_white && len > 0) {
+ /* trim leading whitespace */
+ while (apr_isspace(*cdata)) /* assume: return false for '\0' */
+ ++cdata;
+
+ /* trim trailing whitespace */
+ while (len-- > 0 && apr_isspace(cdata[len]))
+ continue;
+ cdata[len + 1] = '\0';
+ }
+
+ return cdata;
+}
+
/* ---------------------------------------------------------------
**
** Timeout header processing
int num_matched;
int num_that_apply;
int seen_locktoken;
- size_t uri_len;
+ apr_size_t uri_len;
const char *reason = NULL;
/* DBG1("validate: <%s>", resource->uri); */
}
/* dav_validate_walker: Walker callback function to validate resource state */
-static dav_error * dav_validate_walker(dav_walker_ctx *ctx, int calltype)
+static dav_error * dav_validate_walker(dav_walk_resource *wres, int calltype)
{
+ dav_walker_ctx *ctx = wres->walk_ctx;
dav_error *err;
- if ((err = dav_validate_resource_state(ctx->pool, ctx->resource,
- ctx->lockdb,
+ if ((err = dav_validate_resource_state(ctx->w.pool, wres->resource,
+ ctx->w.lockdb,
ctx->if_header, ctx->flags,
&ctx->work_buf, ctx->r)) == NULL) {
/* There was no error, so just bug out. */
** 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)) {
+ || (*wres->resource->hooks->is_same_resource)(wres->resource,
+ ctx->w.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);
+ dav_add_response(wres, err->status, NULL);
return NULL;
}
/* (1) Validate the specified resource, at the specified depth */
if (resource->exists && depth > 0) {
- dav_walker_ctx ctx = { 0 };
+ dav_walker_ctx ctx = { { 0 } };
+ dav_response *multi_status;
+
+ ctx.w.walk_type = DAV_WALKTYPE_NORMAL;
+ ctx.w.func = dav_validate_walker;
+ ctx.w.walk_ctx = &ctx;
+ ctx.w.pool = r->pool;
+ ctx.w.root = resource;
- 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;
+ ctx.w.lockdb = lockdb;
+ ctx.w.walk_type |= DAV_WALKTYPE_LOCKNULL;
}
- dav_buffer_init(r->pool, &ctx.uri, resource->uri);
-
- err = (*repos_hooks->walk)(&ctx, DAV_INFINITY);
+ err = (*repos_hooks->walk)(&ctx.w, DAV_INFINITY, &multi_status);
if (err == NULL) {
- *response = ctx.response;
+ *response = multi_status;;
}
/* else: implies a 5xx status code occurred. */
}
/* (2) Validate the parent resource if requested */
if (err == NULL && (flags & DAV_VALIDATE_PARENT)) {
- dav_resource *parent_resource = (*repos_hooks->get_parent_resource)(resource);
+ dav_resource *parent_resource;
+
+ err = (*repos_hooks->get_parent_resource)(resource, &parent_resource);
- if (parent_resource == NULL) {
+ if (err == NULL && parent_resource == NULL) {
err = dav_new_error(r->pool, HTTP_FORBIDDEN, 0,
"Cannot access parent of repository root.");
}
- else {
+ else if (err == NULL) {
err = dav_validate_resource_state(r->pool, parent_resource, lockdb,
if_header,
flags | DAV_VALIDATE_IS_PARENT,
return NULL;
}
+#if 0 /* not needed right now... */
+
static const char *strip_white(const char *s, apr_pool_t *pool)
{
apr_size_t idx;
return s;
}
+#endif
-/* see mod_dav.h for docco */
-const char *dav_get_target_selector(request_rec *r, const ap_xml_elem *version)
+#define DAV_LABEL_HDR "Label"
+
+/* dav_add_vary_header
+ *
+ * If there were any headers in the request which require a Vary header
+ * in the response, add it.
+ */
+void dav_add_vary_header(request_rec *in_req,
+ request_rec *out_req,
+ const dav_resource *resource)
{
- if (version != NULL) {
- /* DAV:version contains a DAV:href element. find it. */
- if ((version = dav_find_child(version, "href")) == NULL) {
- /* ### this should generate an error... fallthru for now */
- }
- else {
- /* return the contents of the DAV:href element */
- /* ### this presumes no child elements */
- return strip_white(version->first_cdata.first->text, r->pool);
- }
+ const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(in_req);
+
+ /* ### this is probably all wrong... I think there is a function in
+ ### the Apache API to add things to the Vary header. need to check */
+
+ /* Only versioning headers require a Vary response header,
+ * so only do this check if there is a versioning provider */
+ if (vsn_hooks != NULL) {
+ const char *target = apr_table_get(in_req->headers_in, DAV_LABEL_HDR);
+ const char *vary = apr_table_get(out_req->headers_out, "Vary");
+
+ /* If Target-Selector specified, add it to the Vary header */
+ if (target != NULL) {
+ if (vary == NULL)
+ vary = DAV_LABEL_HDR;
+ else
+ vary = apr_pstrcat(out_req->pool, vary, "," DAV_LABEL_HDR,
+ NULL);
+
+ apr_table_setn(out_req->headers_out, "Vary", vary);
+ }
}
-
- /* no element. see if a Target-Selector header was provided. */
- return apr_table_get(r->headers_in, "Target-Selector");
}
/* see mod_dav.h for docco */
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)
+ dav_resource *resource,
+ int parent_only,
+ dav_auto_version_info *av_info)
{
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
- dav_resource *parent = NULL;
const char *body;
- int auto_version;
dav_error *err;
- const char *location;
-
- 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) == NULL);
+ /* Initialize results */
+ memset(av_info, 0, sizeof(*av_info));
/* check parent resource if requested or if resource must be created */
if (!resource->exists || parent_only) {
- parent = (*resource->hooks->get_parent_resource)(resource);
+ dav_resource *parent;
+
+ if ((err = (*resource->hooks->get_parent_resource)(resource,
+ &parent)) != NULL)
+ return err;
+
if (parent == NULL || !parent->exists) {
body = apr_psprintf(r->pool,
- "Missing one or more intermediate collections. "
- "Cannot create resource %s.",
- ap_escape_html(r->pool, resource->uri));
+ "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;
+ av_info->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;
}
"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, &location)) != NULL) {
+ /* if parent cannot be automatically checked out, fail */
+ if (!(*vsn_hooks->auto_version_enabled)(parent)) {
+ body = apr_psprintf(r->pool,
+ "Parent collection must be checked out. "
+ "Cannot create resource %s.",
+ ap_escape_html(r->pool, resource->uri));
+ return dav_new_error(r->pool, HTTP_CONFLICT, 0, body);
+ }
+
+ /* Try to checkout the parent collection.
+ * Note that auto-versioning can only be applied to a version selector,
+ * so no separate working resource will be created.
+ */
+ if ((err = (*vsn_hooks->checkout)(parent, 0, 0, 0, NULL, NULL))
+ != NULL)
+ {
body = apr_psprintf(r->pool,
- "Unable to checkout parent collection. "
- "Cannot create resource %s.",
- ap_escape_html(r->pool, resource->uri));
+ "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);
}
- /* ### what to do with the location? */
+ /* remember that parent was checked out */
+ av_info->parent_checkedout = 1;
}
/* if not just checking parent, create new child resource */
if (!parent_only) {
- if ((err = (*vsn_hooks->mkresource)(resource)) != NULL) {
+ if ((err = (*vsn_hooks->vsn_control)(resource, NULL)) != NULL) {
body = apr_psprintf(r->pool,
- "Unable to create versioned resource %s.",
- ap_escape_html(r->pool, resource->uri));
+ "Unable to create versioned resource %s.",
+ ap_escape_html(r->pool, resource->uri));
return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
}
+
+ /* remember that resource was created */
+ av_info->resource_created = 1;
}
}
- 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;
+ else if (!resource->versioned) {
+ /* resource exists and is not versioned; assume it is writable */
+ return NULL;
}
/* if not just checking parent, make sure child resource is checked out */
if (!parent_only && !resource->working) {
- if ((err = (*vsn_hooks->checkout)(resource, &location)) != NULL) {
+ /* Auto-versioning can only be applied to version selectors, so
+ * no separate working resource will be created. */
+ if ((err = (*vsn_hooks->checkout)(resource, 0, 0, 0, NULL, NULL))
+ != NULL)
+ {
body = apr_psprintf(r->pool,
- "Unable to checkout resource %s.",
- ap_escape_html(r->pool, resource->uri));
+ "Unable to checkout resource %s.",
+ ap_escape_html(r->pool, resource->uri));
return dav_push_error(r->pool, HTTP_CONFLICT, 0, body, err);
}
- /* ### what to do with the location? */
+ /* remember that resource was checked out */
+ av_info->resource_checkedout = 1;
}
return NULL;
}
/* see mod_dav.h for docco */
-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)
+dav_error *dav_revert_resource_writability(
+ request_rec *r,
+ dav_resource *resource,
+ int undo,
+ const dav_auto_version_info *av_info)
{
const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r);
const char *body;
dav_error *err;
+ /* If a resource was provided, restore its writable state.
+ * Otherwise, only the parent must have been modified */
if (resource != NULL) {
- if (!resource_was_writable
- && resource->versioned && resource->working) {
+ if (av_info->resource_checkedout) {
if (undo)
err = (*vsn_hooks->uncheckout)(resource);
else
- err = (*vsn_hooks->checkin)(resource);
+ err = (*vsn_hooks->checkin)(resource, NULL);
if (err != NULL) {
body = apr_psprintf(r->pool,
- "Unable to %s resource %s.",
- undo ? "uncheckout" : "checkin",
- ap_escape_html(r->pool, resource->uri));
+ "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) {
+ /* If undoing because of an error, and the resource was created,
+ * then remove it */
+ if (undo && av_info->resource_created) {
dav_response *response;
/* ### should we do anything with the response? */
if ((err = (*resource->hooks->remove_resource)(resource,
&response)) != NULL) {
body = apr_psprintf(r->pool,
- "Unable to undo creation of resource %s.",
- ap_escape_html(r->pool, resource->uri));
+ "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 parent resource was made writable, restore its state */
+ if (av_info->parent_resource != NULL && av_info->parent_checkedout) {
if (undo)
- err = (*vsn_hooks->uncheckout)(parent_resource);
+ err = (*vsn_hooks->uncheckout)(av_info->parent_resource);
else
- err = (*vsn_hooks->checkin)(parent_resource);
+ err = (*vsn_hooks->checkin)(av_info->parent_resource, NULL);
if (err != NULL) {
body = apr_psprintf(r->pool,
- "Unable to %s parent collection of %s.",
- undo ? "uncheckout" : "checkin",
- ap_escape_html(r->pool, resource->uri));
+ "Unable to %s parent collection %s.",
+ undo ? "uncheckout" : "checkin",
+ ap_escape_html(r->pool, av_info->parent_resource->uri));
return dav_push_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
body, err);
}