diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:26 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:26 +0000 |
commit | 7b31d4f4901cdb89a79f2f7de4a6b8bb637b523b (patch) | |
tree | fdeb0b5ff80273f95ce61607fc3613dff0b9a235 /modules/dav/main | |
parent | Adding upstream version 2.4.38. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.59.upstream/2.4.59upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/dav/main')
-rw-r--r-- | modules/dav/main/mod_dav.c | 635 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.h | 132 | ||||
-rw-r--r-- | modules/dav/main/props.c | 88 | ||||
-rw-r--r-- | modules/dav/main/std_liveprop.c | 8 | ||||
-rw-r--r-- | modules/dav/main/util.c | 87 |
5 files changed, 754 insertions, 196 deletions
diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c index 84012f2..dea3f18 100644 --- a/modules/dav/main/mod_dav.c +++ b/modules/dav/main/mod_dav.c @@ -81,8 +81,10 @@ typedef struct { const char *provider_name; const dav_provider *provider; const char *dir; + const char *base; int locktimeout; int allow_depthinfinity; + int allow_lockdiscovery; } dav_dir_conf; @@ -195,8 +197,11 @@ static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides) newconf->locktimeout = DAV_INHERIT_VALUE(parent, child, locktimeout); newconf->dir = DAV_INHERIT_VALUE(parent, child, dir); + newconf->base = DAV_INHERIT_VALUE(parent, child, base); newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child, allow_depthinfinity); + newconf->allow_lockdiscovery = DAV_INHERIT_VALUE(parent, child, + allow_lockdiscovery); return newconf; } @@ -207,7 +212,7 @@ DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r) return conf ? conf->provider_name : NULL; } -static const dav_provider *dav_get_provider(request_rec *r) +DAV_DECLARE(const dav_provider *) dav_get_provider(request_rec *r) { dav_dir_conf *conf; @@ -280,6 +285,18 @@ static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1) } /* + * Command handler for the DAVBasePath directive, which is TAKE1 + */ +static const char *dav_cmd_davbasepath(cmd_parms *cmd, void *config, const char *arg1) +{ + dav_dir_conf *conf = config; + + conf->base = arg1; + + return NULL; +} + +/* * Command handler for the DAVDepthInfinity directive, which is FLAG. */ static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config, @@ -295,6 +312,21 @@ static const char *dav_cmd_davdepthinfinity(cmd_parms *cmd, void *config, } /* + * Command handler for the DAVLockDiscovery directive, which is FLAG. + */ +static const char *dav_cmd_davlockdiscovery(cmd_parms *cmd, void *config, + int arg) +{ + dav_dir_conf *conf = (dav_dir_conf *)config; + + if (arg) + conf->allow_lockdiscovery = DAV_ENABLED_ON; + else + conf->allow_lockdiscovery = DAV_ENABLED_OFF; + return NULL; +} + +/* * Command handler for DAVMinTimeout directive, which is TAKE1 */ static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config, @@ -436,7 +468,7 @@ static const char *dav_xml_escape_uri(apr_pool_t *p, const char *uri) } -/* Write a complete RESPONSE object out as a <DAV:repsonse> xml +/* Write a complete RESPONSE object out as a <DAV:response> xml element. Data is sent into brigade BB, which is auto-flushed into the output filter stack for request R. Use POOL for any temporary allocations. @@ -557,6 +589,7 @@ DAV_DECLARE(void) dav_send_multistatus(request_rec *r, int status, dav_begin_multistatus(bb, r, status, namespaces); apr_pool_create(&subpool, r->pool); + apr_pool_tag(subpool, "mod_dav-multistatus"); for (; first != NULL; first = first->next) { apr_pool_clear(subpool); @@ -662,8 +695,8 @@ static int dav_created(request_rec *r, const char *locn, const char *what, /* Apache doesn't allow us to set a variable body for HTTP_CREATED, so * we must manufacture the entire response. */ - body = apr_psprintf(r->pool, "%s %s has been created.", - what, ap_escape_html(r->pool, locn)); + body = apr_pstrcat(r->pool, what, " ", ap_escape_html(r->pool, locn), + " has been created.", NULL); return dav_error_response(r, HTTP_CREATED, body); } @@ -676,7 +709,7 @@ DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth) return def_depth; } - if (strcasecmp(depth, "infinity") == 0) { + if (ap_cstr_casecmp(depth, "infinity") == 0) { return DAV_INFINITY; } else if (strcmp(depth, "0") == 0) { @@ -725,11 +758,11 @@ static int dav_get_overwrite(request_rec *r) * the resource identified by the DAV:checked-in property of the resource * identified by the Request-URI. */ -static dav_error *dav_get_resource(request_rec *r, int label_allowed, +DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed, int use_checked_in, dav_resource **res_p) { dav_dir_conf *conf; - const char *label = NULL; + const char *label = NULL, *base; dav_error *err; /* if the request target can be overridden, get any target selector */ @@ -746,11 +779,27 @@ static dav_error *dav_get_resource(request_rec *r, int label_allowed, ap_escape_html(r->pool, r->uri))); } + /* Take the repos root from DAVBasePath if configured, else the + * path of the enclosing section. */ + base = conf->base ? conf->base : conf->dir; + /* resolve the resource */ - err = (*conf->provider->repos->get_resource)(r, conf->dir, + err = (*conf->provider->repos->get_resource)(r, base, label, use_checked_in, res_p); if (err != NULL) { + /* In the error path, give a hint that DavBasePath needs to be + * used if the location was configured via a regex match. */ + if (!conf->base) { + core_dir_config *cdc = ap_get_core_module_config(r->per_dir_config); + + if (cdc->r) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(10484) + "failed to find repository for location configured " + "via regex match - missing DAVBasePath?"); + } + } + err = dav_push_error(r->pool, err->status, 0, "Could not fetch resource information.", err); return err; @@ -774,7 +823,9 @@ static dav_error *dav_get_resource(request_rec *r, int label_allowed, return NULL; } -static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb) +DAV_DECLARE(dav_error *) dav_open_lockdb(request_rec *r, + int ro, + dav_lockdb **lockdb) { const dav_hooks_locks *hooks = DAV_GET_HOOKS_LOCKS(r); @@ -787,6 +838,11 @@ static dav_error * dav_open_lockdb(request_rec *r, int ro, dav_lockdb **lockdb) return (*hooks->open_lockdb)(r, ro, 0, lockdb); } +DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb) +{ + (lockdb->hooks->close_lockdb)(lockdb); +} + /** * @return 1 if valid content-range, * 0 if no content-range, @@ -799,16 +855,15 @@ static int dav_parse_range(request_rec *r, char *range; char *dash; char *slash; - char *errp; range_c = apr_table_get(r->headers_in, "content-range"); if (range_c == NULL) return 0; range = apr_pstrdup(r->pool, range_c); - if (strncasecmp(range, "bytes ", 6) != 0 - || (dash = ap_strchr(range, '-')) == NULL - || (slash = ap_strchr(range, '/')) == NULL) { + if (ap_cstr_casecmpn(range, "bytes ", 6) != 0 + || (dash = ap_strchr(range + 6, '-')) == NULL + || (slash = ap_strchr(range + 6, '/')) == NULL) { /* malformed header */ return -1; } @@ -816,20 +871,19 @@ static int dav_parse_range(request_rec *r, *dash++ = *slash++ = '\0'; /* detect invalid ranges */ - if (apr_strtoff(range_start, range + 6, &errp, 10) - || *errp || *range_start < 0) { + if (!ap_parse_strict_length(range_start, range + 6)) { return -1; } - if (apr_strtoff(range_end, dash, &errp, 10) - || *errp || *range_end < 0 || *range_end < *range_start) { + if (!ap_parse_strict_length(range_end, dash) + || *range_end < *range_start) { return -1; } if (*slash != '*') { apr_off_t dummy; - if (apr_strtoff(&dummy, slash, &errp, 10) - || *errp || dummy <= *range_end) { + if (!ap_parse_strict_length(&dummy, slash) + || dummy <= *range_end) { return -1; } } @@ -854,6 +908,12 @@ static int dav_method_get(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -901,6 +961,12 @@ static int dav_method_post(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + /* 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) { @@ -934,6 +1000,12 @@ static int dav_method_put(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + /* If not a file or collection resource, PUT not allowed */ if (resource->type != DAV_RESOURCE_TYPE_REGULAR && resource->type != DAV_RESOURCE_TYPE_WORKING) { @@ -996,12 +1068,17 @@ static int dav_method_put(request_rec *r) /* 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, - apr_psprintf(r->pool, - "Unable to PUT new contents for %s.", - ap_escape_html(r->pool, r->uri)), - err); + int status = err->status ? err->status : HTTP_FORBIDDEN; + if (status > 299) { + err = dav_push_error(r->pool, status, 0, + apr_psprintf(r->pool, + "Unable to PUT new contents for %s.", + ap_escape_html(r->pool, r->uri)), + err); + } + else { + err = NULL; + } } if (err == NULL && has_range) { @@ -1204,6 +1281,13 @@ static int dav_method_delete(request_rec *r) &resource); if (err != NULL) return dav_handle_err(r, err, NULL); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -1319,10 +1403,10 @@ static dav_error *dav_gen_supported_methods(request_rec *r, if (elts[i].key == NULL) continue; - s = apr_psprintf(r->pool, - "<D:supported-method D:name=\"%s\"/>" - DEBUG_CR, - elts[i].key); + s = apr_pstrcat(r->pool, + "<D:supported-method D:name=\"", + elts[i].key, + "\"/>" DEBUG_CR, NULL); apr_text_append(r->pool, body, s); } } @@ -1348,10 +1432,9 @@ static dav_error *dav_gen_supported_methods(request_rec *r, /* see if method is supported */ if (apr_table_get(methods, name) != NULL) { - s = apr_psprintf(r->pool, - "<D:supported-method D:name=\"%s\"/>" - DEBUG_CR, - name); + s = apr_pstrcat(r->pool, + "<D:supported-method D:name=\"", + name, "\"/>" DEBUG_CR, NULL); apr_text_append(r->pool, body, s); } } @@ -1375,8 +1458,7 @@ static dav_error *dav_gen_supported_live_props(request_rec *r, dav_error *err; /* open lock database, to report on supported lock properties */ - /* ### should open read-only */ - if ((err = dav_open_lockdb(r, 0, &lockdb)) != NULL) { + if ((err = dav_open_lockdb(r, 1, &lockdb)) != NULL) { return dav_push_error(r->pool, err->status, 0, "The lock database could not be opened, " "preventing the reporting of supported lock " @@ -1385,7 +1467,7 @@ static dav_error *dav_gen_supported_live_props(request_rec *r, } /* open the property database (readonly) for the resource */ - if ((err = dav_open_propdb(r, lockdb, resource, 1, NULL, + if ((err = dav_open_propdb(r, lockdb, resource, DAV_PROPDB_RO, NULL, &propdb)) != NULL) { if (lockdb != NULL) (*lockdb->hooks->close_lockdb)(lockdb); @@ -1451,89 +1533,95 @@ static dav_error *dav_gen_supported_live_props(request_rec *r, return err; } + /* generate DAV:supported-report-set OPTIONS response */ static dav_error *dav_gen_supported_reports(request_rec *r, const dav_resource *resource, const apr_xml_elem *elem, - const dav_hooks_vsn *vsn_hooks, apr_text_header *body) { apr_xml_elem *child; apr_xml_attr *attr; - dav_error *err; + dav_error *err = NULL; char *s; + apr_array_header_t *reports; + const dav_report_elem *rp; apr_text_append(r->pool, body, "<D:supported-report-set>" DEBUG_CR); - if (vsn_hooks != NULL) { - const dav_report_elem *reports; - const dav_report_elem *rp; + reports = apr_array_make(r->pool, 5, sizeof(const char *)); + dav_run_gather_reports(r, resource, reports, &err); + if (err != NULL) { + return dav_push_error(r->pool, err->status, 0, + "DAV:supported-report-set could not be " + "determined due to a problem fetching the " + "available reports for this resource.", + err); + } - if ((err = (*vsn_hooks->avail_reports)(resource, &reports)) != NULL) { - return dav_push_error(r->pool, err->status, 0, - "DAV:supported-report-set could not be " - "determined due to a problem fetching the " - "available reports for this resource.", - err); + if (elem->first_child == NULL) { + int i; + + /* show all supported reports */ + rp = (const dav_report_elem *)reports->elts; + for (i = 0; i < reports->nelts; i++, rp++) { + /* Note: we presume reports->namespace is + * properly XML/URL quoted */ + s = apr_pstrcat(r->pool, + "<D:supported-report D:name=\"", + rp->name, + "\" D:namespace=\"", + rp->nmspace, + "\"/>" DEBUG_CR, NULL); + apr_text_append(r->pool, body, s); } + } + else { + /* check for support of specific report */ + for (child = elem->first_child; child != NULL; child = child->next) { + if (child->ns == APR_XML_NS_DAV_ID + && strcmp(child->name, "supported-report") == 0) { + const char *name = NULL; + const char *nmspace = NULL; + int i; - if (reports != NULL) { - if (elem->first_child == NULL) { - /* show all supported reports */ - for (rp = reports; rp->nmspace != NULL; ++rp) { - /* Note: we presume reports->namespace is - * properly XML/URL quoted */ - s = apr_psprintf(r->pool, - "<D:supported-report D:name=\"%s\" " - "D:namespace=\"%s\"/>" DEBUG_CR, - rp->name, rp->nmspace); - apr_text_append(r->pool, body, s); + /* go through attributes to find name and namespace */ + for (attr = child->attr; attr != NULL; attr = attr->next) { + if (attr->ns == APR_XML_NS_DAV_ID) { + if (strcmp(attr->name, "name") == 0) + name = attr->value; + else if (strcmp(attr->name, "namespace") == 0) + nmspace = attr->value; + } } - } - else { - /* check for support of specific report */ - for (child = elem->first_child; child != NULL; child = child->next) { - if (child->ns == APR_XML_NS_DAV_ID - && strcmp(child->name, "supported-report") == 0) { - const char *name = NULL; - const char *nmspace = NULL; - - /* go through attributes to find name and namespace */ - for (attr = child->attr; attr != NULL; attr = attr->next) { - if (attr->ns == APR_XML_NS_DAV_ID) { - if (strcmp(attr->name, "name") == 0) - name = attr->value; - else if (strcmp(attr->name, "namespace") == 0) - nmspace = attr->value; - } - } - - if (name == NULL) { - return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0, - "A DAV:supported-report element " - "does not have a \"name\" attribute"); - } - - /* default namespace to DAV: */ - if (nmspace == NULL) - nmspace = "DAV:"; - - for (rp = reports; rp->nmspace != NULL; ++rp) { - if (strcmp(name, rp->name) == 0 - && strcmp(nmspace, rp->nmspace) == 0) { - /* Note: we presume reports->nmspace is - * properly XML/URL quoted - */ - s = apr_psprintf(r->pool, - "<D:supported-report " - "D:name=\"%s\" " - "D:namespace=\"%s\"/>" - DEBUG_CR, - rp->name, rp->nmspace); - apr_text_append(r->pool, body, s); - break; - } - } + + if (name == NULL) { + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0, + "A DAV:supported-report element " + "does not have a \"name\" attribute"); + } + + /* default namespace to DAV: */ + if (nmspace == NULL) { + nmspace = "DAV:"; + } + + rp = (const dav_report_elem *)reports->elts; + for (i = 0; i < reports->nelts; i++, rp++) { + if (strcmp(name, rp->name) == 0 + && strcmp(nmspace, rp->nmspace) == 0) { + /* Note: we presume reports->nmspace is + * properly XML/URL quoted + */ + s = apr_pstrcat(r->pool, + "<D:supported-report " + "D:name=\"", + rp->name, + "\" D:namespace=\"", + rp->nmspace, + "\"/>" DEBUG_CR, NULL); + apr_text_append(r->pool, body, s); + break; } } } @@ -1640,6 +1728,12 @@ static int dav_method_options(request_rec *r) } /* note: doc == NULL if no request body */ + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (doc && !dav_validate_root(doc, "options")) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00584) "The \"options\" element was not found."); @@ -1903,7 +1997,7 @@ static int dav_method_options(request_rec *r) core_option = 1; } else if (strcmp(elem->name, "supported-report-set") == 0) { - err = dav_gen_supported_reports(r, resource, elem, vsn_hooks, &body); + err = dav_gen_supported_reports(r, resource, elem, &body); core_option = 1; } } @@ -1968,10 +2062,23 @@ static void dav_cache_badprops(dav_walker_ctx *ctx) static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype) { dav_walker_ctx *ctx = wres->walk_ctx; + dav_dir_conf *conf; + int flags = DAV_PROPDB_RO; dav_error *err; dav_propdb *propdb; dav_get_props_result propstats = { 0 }; + /* check for any method preconditions */ + if (dav_run_method_precondition(ctx->r, NULL, wres->resource, ctx->doc, &err) != DECLINED + && err) { + apr_pool_clear(ctx->scratchpool); + return NULL; + } + + conf = ap_get_module_config(ctx->r->per_dir_config, &dav_module); + if (conf && conf->allow_lockdiscovery == DAV_ENABLED_OFF) + flags |= DAV_PROPDB_DISABLE_LOCKDISCOVERY; + /* ** Note: ctx->doc can only be NULL for DAV_PROPFIND_IS_ALLPROP. Since ** dav_get_allprops() does not need to do namespace translation, @@ -1980,8 +2087,9 @@ static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype) ** 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->w.lockdb, wres->resource, 1, - ctx->doc ? ctx->doc->namespaces : NULL, &propdb); + err = dav_popen_propdb(ctx->scratchpool, + ctx->r, ctx->w.lockdb, wres->resource, flags, + ctx->doc ? ctx->doc->namespaces : NULL, &propdb); if (err != NULL) { /* ### do something with err! */ @@ -2012,10 +2120,10 @@ static dav_error * dav_propfind_walker(dav_walk_resource *wres, int calltype) : DAV_PROP_INSERT_NAME; propstats = dav_get_allprops(propdb, what); } - dav_close_propdb(propdb); - dav_stream_response(wres, 0, &propstats, ctx->scratchpool); + dav_close_propdb(propdb); + /* at this point, ctx->scratchpool has been used to stream a single response. this function fully controls the pool, and thus has the right to clear it for the next iteration of this @@ -2042,6 +2150,17 @@ static int dav_method_propfind(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + if ((result = ap_xml_parse_input(r, &doc)) != OK) { + return result; + } + /* note: doc == NULL if no request body */ + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (dav_get_resource_state(r, resource) == DAV_RESOURCE_NULL) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -2069,11 +2188,6 @@ static int dav_method_propfind(request_rec *r) } } - if ((result = ap_xml_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, 0, r, APLOGNO(00585) @@ -2103,7 +2217,7 @@ static int dav_method_propfind(request_rec *r) return HTTP_BAD_REQUEST; } - ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH; + ctx.w.walk_type = DAV_WALKTYPE_NORMAL | DAV_WALKTYPE_AUTH | DAV_WALKTYPE_TOLERANT; ctx.w.func = dav_propfind_walker; ctx.w.walk_ctx = &ctx; ctx.w.pool = r->pool; @@ -2113,9 +2227,9 @@ static int dav_method_propfind(request_rec *r) ctx.r = r; ctx.bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); apr_pool_create(&ctx.scratchpool, r->pool); + apr_pool_tag(ctx.scratchpool, "mod_dav-scratch"); - /* ### should open read-only */ - if ((err = dav_open_lockdb(r, 0, &ctx.w.lockdb)) != NULL) { + if ((err = dav_open_lockdb(r, 1, &ctx.w.lockdb)) != NULL) { err = dav_push_error(r->pool, err->status, 0, "The lock database could not be opened, " "preventing access to the various lock " @@ -2318,16 +2432,23 @@ static int dav_method_proppatch(request_rec *r) &resource); if (err != NULL) return dav_handle_err(r, err, NULL); - if (!resource->exists) { - /* Apache will supply a default error for this. */ - return HTTP_NOT_FOUND; - } if ((result = ap_xml_parse_input(r, &doc)) != OK) { return result; } /* note: doc == NULL if no request body */ + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + + if (!resource->exists) { + /* Apache will supply a default error for this. */ + return HTTP_NOT_FOUND; + } + if (doc == NULL || !dav_validate_root(doc, "propertyupdate")) { /* This supplies additional information for the default message. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00587) @@ -2352,7 +2473,8 @@ static int dav_method_proppatch(request_rec *r) return dav_handle_err(r, err, NULL); } - if ((err = dav_open_propdb(r, NULL, resource, 0, doc->namespaces, + if ((err = dav_open_propdb(r, NULL, resource, + DAV_PROPDB_NONE, doc->namespaces, &propdb)) != NULL) { /* undo any auto-checkout */ dav_auto_checkin(r, resource, 1 /*undo*/, 0 /*unlock*/, &av_info); @@ -2470,7 +2592,7 @@ static int process_mkcol_body(request_rec *r) r->remaining = 0; if (tenc) { - if (strcasecmp(tenc, "chunked")) { + if (ap_cstr_casecmp(tenc, "chunked")) { /* Use this instead of Apache's default error string */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00589) "Unknown Transfer-Encoding %s", tenc); @@ -2480,20 +2602,13 @@ static int process_mkcol_body(request_rec *r) r->read_chunked = 1; } else if (lenp) { - const char *pos = lenp; - - while (apr_isdigit(*pos) || apr_isspace(*pos)) { - ++pos; - } - - if (*pos != '\0') { + if (!ap_parse_strict_length(&r->remaining, lenp)) { + r->remaining = 0; /* This supplies additional information for the default message. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00590) "Invalid Content-Length %s", lenp); return HTTP_BAD_REQUEST; } - - r->remaining = apr_atoi64(lenp); } if (r->read_chunked || r->remaining > 0) { @@ -2534,6 +2649,12 @@ static int dav_method_mkcol(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (resource->exists) { /* oops. something was already there! */ @@ -2654,6 +2775,12 @@ static int dav_method_copymove(request_rec *r, int is_move) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -2676,7 +2803,7 @@ static int dav_method_copymove(request_rec *r, int is_move) const char *nscp_path = apr_table_get(r->headers_in, "New-uri"); if (nscp_host != NULL && nscp_path != NULL) - dest = apr_psprintf(r->pool, "http://%s%s", nscp_host, nscp_path); + dest = apr_pstrcat(r->pool, "http://", nscp_host, nscp_path, NULL); } if (dest == NULL) { /* This supplies additional information for the default message. */ @@ -2720,6 +2847,12 @@ static int dav_method_copymove(request_rec *r, int is_move) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, resnew, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + /* are the two resources handled by the same repository? */ if (resource->hooks != resnew->hooks) { /* ### this message exposes some backend config, but screw it... */ @@ -3071,6 +3204,12 @@ static int dav_method_lock(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + /* Check if parent collection exists */ if ((err = resource->hooks->get_parent_resource(resource, &parent)) != NULL) { /* ### add a higher-level description? */ @@ -3270,6 +3409,12 @@ static int dav_method_unlock(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + resource_state = dav_get_resource_state(r, resource); /* @@ -3294,8 +3439,8 @@ static int dav_method_unlock(request_rec *r) /* ### 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. + * resource has to be the root of an infinite lock. Thus, an UNLOCK + * on any part of an infinite 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. @@ -3329,15 +3474,21 @@ static int dav_method_vsn_control(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); - /* remember the pre-creation resource state */ - resource_state = dav_get_resource_state(r, resource); - /* parse the request body (may be a version-control element) */ if ((result = ap_xml_parse_input(r, &doc)) != OK) { return result; } /* note: doc == NULL if no request body */ + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + + /* remember the pre-creation resource state */ + resource_state = dav_get_resource_state(r, resource); + if (doc != NULL) { const apr_xml_elem *child; apr_size_t tsize; @@ -3582,6 +3733,12 @@ static int dav_method_checkout(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -3658,6 +3815,12 @@ static int dav_method_uncheckout(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -3735,6 +3898,12 @@ static int dav_method_checkin(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -3856,6 +4025,12 @@ static int dav_method_update(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -3930,6 +4105,9 @@ typedef struct dav_label_walker_ctx /* input: */ dav_walk_params w; + /* original request */ + request_rec *r; + /* label being manipulated */ const char *label; @@ -3949,13 +4127,19 @@ static dav_error * dav_label_walker(dav_walk_resource *wres, int calltype) dav_label_walker_ctx *ctx = wres->walk_ctx; dav_error *err = NULL; + /* check for any method preconditions */ + if (dav_run_method_precondition(ctx->r, NULL, wres->resource, NULL, &err) != DECLINED + && err) { + /* precondition failed, dropping through */ + } + /* Check the state of the resource: must be a version or * non-checkedout version selector */ /* ### need a general mechanism for reporting precondition violations * ### (should be returning XML document for 403/409 responses) */ - if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION && + else if (wres->resource->type != DAV_RESOURCE_TYPE_VERSION && (wres->resource->type != DAV_RESOURCE_TYPE_REGULAR || !wres->resource->versioned)) { err = dav_new_error(ctx->w.pool, HTTP_CONFLICT, 0, 0, @@ -4001,11 +4185,23 @@ static int dav_method_label(request_rec *r) if (vsn_hooks == NULL || vsn_hooks->add_label == NULL) return DECLINED; + /* parse the request body */ + if ((result = ap_xml_parse_input(r, &doc)) != OK) { + return result; + } + /* Ask repository module to resolve the resource */ err = dav_get_resource(r, 1 /* label_allowed */, 0 /* use_checked_in */, &resource); if (err != NULL) return dav_handle_err(r, err, NULL); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -4017,11 +4213,6 @@ static int dav_method_label(request_rec *r) return HTTP_BAD_REQUEST; } - /* parse the request body */ - if ((result = ap_xml_parse_input(r, &doc)) != OK) { - return result; - } - if (doc == NULL || !dav_validate_root(doc, "label")) { /* This supplies additional information for the default message. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00610) @@ -4070,6 +4261,7 @@ static int dav_method_label(request_rec *r) ctx.w.walk_ctx = &ctx; ctx.w.pool = r->pool; ctx.w.root = resource; + ctx.r = r; ctx.vsn_hooks = vsn_hooks; err = (*resource->hooks->walk)(&ctx.w, depth, &multi_status); @@ -4109,21 +4301,60 @@ static int dav_method_label(request_rec *r) return DONE; } +static int dav_core_deliver_report(request_rec *r, + const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output, dav_error **err) +{ + const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); + + if (vsn_hooks) { + *err = (*vsn_hooks->deliver_report)(r, resource, doc, + r->output_filters); + return OK; + } + + return DECLINED; +} + +static void dav_core_gather_reports( + request_rec *r, + const dav_resource *resource, + apr_array_header_t *reports, + dav_error **err) +{ + const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); + + if (vsn_hooks) { + const dav_report_elem *rp; + + (*err) = (*vsn_hooks->avail_reports)(resource, &rp); + while (rp && rp->name) { + + dav_report_elem *report = apr_array_push(reports); + + report->nmspace = rp->nmspace; + report->name = rp->name; + + rp++; + } + } + +} + static int dav_method_report(request_rec *r) { dav_resource *resource; const dav_hooks_vsn *vsn_hooks = DAV_GET_HOOKS_VSN(r); - int result; - int label_allowed; apr_xml_doc *doc; - dav_error *err; + dav_error *err = NULL; - /* If no versioning provider, decline the request */ - if (vsn_hooks == NULL) - return DECLINED; + int result; + int label_allowed; - if ((result = ap_xml_parse_input(r, &doc)) != OK) + if ((result = ap_xml_parse_input(r, &doc)) != OK) { return result; + } if (doc == NULL) { /* This supplies additional information for the default msg. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00614) @@ -4135,11 +4366,18 @@ static int dav_method_report(request_rec *r) * First determine whether a Target-Selector header is allowed * for this report. */ - label_allowed = (*vsn_hooks->report_label_header_allowed)(doc); + label_allowed = vsn_hooks ? (*vsn_hooks->report_label_header_allowed)(doc) : 0; err = dav_get_resource(r, label_allowed, 0 /* use_checked_in */, &resource); - if (err != NULL) + if (err != NULL) { + return dav_handle_err(r, err, NULL); + } + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { return dav_handle_err(r, err, NULL); + } if (!resource->exists) { /* Apache will supply a default error for this. */ @@ -4149,13 +4387,17 @@ static int dav_method_report(request_rec *r) /* set up defaults for the report response */ r->status = HTTP_OK; ap_set_content_type(r, DAV_XML_CONTENT_TYPE); + err = NULL; /* run report hook */ - if ((err = (*vsn_hooks->deliver_report)(r, resource, doc, - r->output_filters)) != NULL) { - if (! r->sent_bodyct) + result = dav_run_deliver_report(r, resource, doc, + r->output_filters, &err); + if (err != NULL) { + + if (! r->sent_bodyct) { /* No data has been sent to client yet; throw normal error. */ return dav_handle_err(r, err, NULL); + } /* If an error occurred during the report delivery, there's basically nothing we can do but abort the connection and @@ -4168,6 +4410,16 @@ static int dav_method_report(request_rec *r) " a REPORT response.", err); dav_log_err(r, err, APLOG_ERR); r->connection->aborted = 1; + + return DONE; + } + switch (result) { + case OK: + return DONE; + case DECLINED: + /* No one handled the report */ + return HTTP_NOT_IMPLEMENTED; + default: return DONE; } @@ -4199,6 +4451,12 @@ static int dav_method_make_workspace(request_rec *r) return result; } + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (doc == NULL || !dav_validate_root(doc, "mkworkspace")) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00615) @@ -4258,6 +4516,12 @@ static int dav_method_make_activity(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + /* MKACTIVITY does not have a defined request body. */ if ((result = ap_discard_request_body(r)) != OK) { return result; @@ -4383,6 +4647,12 @@ static int dav_method_merge(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, source_resource, NULL, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + no_auto_merge = dav_find_child(doc->root, "no-auto-merge") != NULL; no_checkout = dav_find_child(doc->root, "no-checkout") != NULL; @@ -4400,6 +4670,13 @@ static int dav_method_merge(request_rec *r) &resource); if (err != NULL) return dav_handle_err(r, err, NULL); + + /* check for any method preconditions */ + if (dav_run_method_precondition(r, source_resource, resource, doc, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -4467,6 +4744,12 @@ static int dav_method_bind(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, NULL, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + if (!resource->exists) { /* Apache will supply a default error for this. */ return HTTP_NOT_FOUND; @@ -4517,6 +4800,12 @@ static int dav_method_bind(request_rec *r) if (err != NULL) return dav_handle_err(r, err, NULL); + /* check for any method preconditions */ + if (dav_run_method_precondition(r, resource, binding, NULL, &err) != DECLINED + && err) { + return dav_handle_err(r, err, NULL); + } + /* are the two resources handled by the same repository? */ if (resource->hooks != binding->hooks) { /* ### this message exposes some backend config, but screw it... */ @@ -4886,6 +5175,11 @@ static void register_hooks(apr_pool_t *p) dav_hook_insert_all_liveprops(dav_core_insert_all_liveprops, NULL, NULL, APR_HOOK_MIDDLE); + dav_hook_deliver_report(dav_core_deliver_report, + NULL, NULL, APR_HOOK_LAST); + dav_hook_gather_reports(dav_core_gather_reports, + NULL, NULL, APR_HOOK_LAST); + dav_core_register_uris(p); } @@ -4900,6 +5194,10 @@ static const command_rec dav_cmds[] = AP_INIT_TAKE1("DAV", dav_cmd_dav, NULL, ACCESS_CONF, "specify the DAV provider for a directory or location"), + /* per directory/location */ + AP_INIT_TAKE1("DAVBasePath", dav_cmd_davbasepath, NULL, ACCESS_CONF, + "specify the DAV repository base URL"), + /* per directory/location, or per server */ AP_INIT_TAKE1("DAVMinTimeout", dav_cmd_davmintimeout, NULL, ACCESS_CONF|RSRC_CONF, @@ -4910,6 +5208,11 @@ static const command_rec dav_cmds[] = ACCESS_CONF|RSRC_CONF, "allow Depth infinity PROPFIND requests"), + /* per directory/location, or per server */ + AP_INIT_FLAG("DAVLockDiscovery", dav_cmd_davlockdiscovery, NULL, + ACCESS_CONF|RSRC_CONF, + "allow lock discovery by PROPFIND requests"), + { NULL } }; @@ -4928,6 +5231,9 @@ APR_HOOK_STRUCT( APR_HOOK_LINK(gather_propsets) APR_HOOK_LINK(find_liveprop) APR_HOOK_LINK(insert_all_liveprops) + APR_HOOK_LINK(deliver_report) + APR_HOOK_LINK(gather_reports) + APR_HOOK_LINK(method_precondition) ) APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_propsets, @@ -4944,3 +5250,22 @@ APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, insert_all_liveprops, (request_rec *r, const dav_resource *resource, dav_prop_insert what, apr_text_header *phdr), (r, resource, what, phdr)) + +APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, deliver_report, + (request_rec *r, + const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output, dav_error **err), + (r, resource, doc, output, err), DECLINED) + +APR_IMPLEMENT_EXTERNAL_HOOK_VOID(dav, DAV, gather_reports, + (request_rec *r, const dav_resource *resource, + apr_array_header_t *reports, dav_error **err), + (r, resource, reports, err)) + +APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(dav, DAV, int, method_precondition, + (request_rec *r, + dav_resource *src, const dav_resource *dest, + const apr_xml_doc *doc, + dav_error **err), + (r, src, dest, doc, err), DECLINED) diff --git a/modules/dav/main/mod_dav.h b/modules/dav/main/mod_dav.h index 80ad117..c8c54f3 100644 --- a/modules/dav/main/mod_dav.h +++ b/modules/dav/main/mod_dav.h @@ -50,7 +50,7 @@ extern "C" { #define DAV_READ_BLOCKSIZE 2048 /* used for reading input blocks */ -#define DAV_RESPONSE_BODY_1 "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>" +#define DAV_RESPONSE_BODY_1 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html>\n<head>\n<title>" #define DAV_RESPONSE_BODY_2 "</title>\n</head><body>\n<h1>" #define DAV_RESPONSE_BODY_3 "</h1>\n<p>" #define DAV_RESPONSE_BODY_4 "</p>\n" @@ -427,6 +427,9 @@ typedef struct dav_resource { */ typedef struct dav_locktoken dav_locktoken; +DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed, + int use_checked_in, dav_resource **res_p); + /* -------------------------------------------------------------------- ** @@ -574,8 +577,22 @@ DAV_DECLARE(int) dav_get_depth(request_rec *r, int def_depth); DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc, const char *tagname); +DAV_DECLARE(int) dav_validate_root_ns(const apr_xml_doc *doc, + int ns, const char *tagname); DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem, const char *tagname); +DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem, + int ns, const char *tagname); +DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem, + int ns, const char *tagname); + +/* find and return the attribute with a name in the given namespace */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem, + int ns, const char *attrname); + +/* find and return the attribute with a given DAV: tagname */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem, + const char *attrname); /* gather up all the CDATA into a single string */ DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool, @@ -644,10 +661,10 @@ DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi, ** mod_dav 1.0). There are too many dependencies between a dav_resource ** (defined by <repos>) and the other functionality. ** -** Live properties are not part of the dav_provider structure because they -** are handled through the APR_HOOK interface (to allow for multiple liveprop -** providers). The core always provides some properties, and then a given -** provider will add more properties. +** Live properties and report extensions are not part of the dav_provider +** structure because they are handled through the APR_HOOK interface (to +** allow for multiple providers). The core always provides some +** properties, and then a given provider will add more properties. ** ** Some providers may need to associate a context with the dav_provider ** structure -- the ctx field is available for storing this context. Just @@ -711,6 +728,68 @@ APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, insert_all_liveprops, (request_rec *r, const dav_resource *resource, dav_prop_insert what, apr_text_header *phdr)) +/* +** deliver_report: given a parsed report request, process the request +** an deliver the resulting report. +** +** The hook implementer should decide whether it should handle the given +** report, and if so, write the response to the output filter. If the +** report is not relevant, return DECLINED. +*/ +APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, deliver_report, + (request_rec *r, + const dav_resource *resource, + const apr_xml_doc *doc, + ap_filter_t *output, dav_error **err)) + +/* +** gather_reports: get all reports. +** +** The hook implementor should push one or more dav_report_elem structures +** containing report names into the specified array. These names are returned +** in the DAV:supported-reports-set property to let clients know +** what reports are supported by the installation. +** +*/ +APR_DECLARE_EXTERNAL_HOOK(dav, DAV, void, gather_reports, + (request_rec *r, const dav_resource *resource, + apr_array_header_t *reports, dav_error **err)) + +/* + ** method_precondition: check method preconditions. + ** + ** If a WebDAV extension needs to set any preconditions on a method, this + ** hook is where to do it. If the precondition fails, return an error + ** response with the tagname set to the value of the failed precondition. + ** + ** If the method requires an XML body, this will be read and provided as + ** the doc value. If not, doc is NULL. An extension that needs to verify + ** the non-XML body of a request should register an input filter to do so + ** within this hook. + ** + ** Methods like PUT will supply a single src resource, and the dst will + ** be NULL. + ** + ** Methods like COPY or MOVE will trigger this hook twice. The first + ** invocation will supply just the source resource. The second invocation + ** will supply a source and destination. This allows preconditions on the + ** source resource to be verified before making an attempt to get the + ** destination resource. + ** + ** Methods like PROPFIND and LABEL will trigger this hook initially for + ** the src resource, and then subsequently for each resource that has + ** been walked during processing, with the walked resource passed in dst, + ** and NULL passed in src. + ** + ** As a rule, the src resource originates from a request that has passed + ** through httpd's authn/authz hooks, while the dst resource has not. + */ +APR_DECLARE_EXTERNAL_HOOK(dav, DAV, int, method_precondition, + (request_rec *r, + dav_resource *src, const dav_resource *dst, + const apr_xml_doc *doc, dav_error **err)) + + DAV_DECLARE(const dav_hooks_locks *) dav_get_lock_hooks(request_rec *r); DAV_DECLARE(const dav_hooks_propdb *) dav_get_propdb_hooks(request_rec *r); DAV_DECLARE(const dav_hooks_vsn *) dav_get_vsn_hooks(request_rec *r); @@ -721,6 +800,7 @@ DAV_DECLARE(void) dav_register_provider(apr_pool_t *p, const char *name, const dav_provider *hooks); DAV_DECLARE(const dav_provider *) dav_lookup_provider(const char *name); DAV_DECLARE(const char *) dav_get_provider_name(request_rec *r); +DAV_DECLARE(const dav_provider *) dav_get_provider(request_rec *r); /* ### deprecated */ @@ -827,6 +907,14 @@ struct dav_hooks_liveprop ** property, and does not want it handled as a dead property, it should ** return DAV_PROP_INSERT_NOTSUPP. ** + ** Some DAV extensions, like CalDAV, specify both document elements + ** and property elements that need to be taken into account when + ** generating a property. The document element and property element + ** are made available in the dav_liveprop_elem structure under the + ** resource, accessible as follows: + ** + ** dav_get_liveprop_element(resource); + ** ** Returns one of DAV_PROP_INSERT_* based on what happened. ** ** ### we may need more context... ie. the lock database @@ -974,6 +1062,18 @@ DAV_DECLARE(long) dav_get_liveprop_ns_count(void); DAV_DECLARE(void) dav_add_all_liveprop_xmlns(apr_pool_t *p, apr_text_header *phdr); +typedef struct { + const apr_xml_doc *doc; + const apr_xml_elem *elem; +} dav_liveprop_elem; + +/* + ** When calling insert_prop(), the associated request element and + ** document is accessible using the following call. + */ +DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource + *resource); + /* ** The following three functions are part of mod_dav's internal handling ** for the core WebDAV properties. They are not part of mod_dav's API. @@ -1314,8 +1414,12 @@ DAV_DECLARE(const char *)dav_lock_get_activelock(request_rec *r, dav_buffer *pbuf); /* LockDB-related public lock functions */ +DAV_DECLARE(dav_error *) dav_open_lockdb(request_rec *r, + int ro, + dav_lockdb **lockdb); +DAV_DECLARE(void) dav_close_lockdb(dav_lockdb *lockdb); DAV_DECLARE(dav_error *) dav_lock_parse_lockinfo(request_rec *r, - const dav_resource *resrouce, + const dav_resource *resource, dav_lockdb *lockdb, const apr_xml_doc *doc, dav_lock **lock_request); @@ -1581,15 +1685,28 @@ struct dav_hooks_locks typedef struct dav_propdb dav_propdb; +#define DAV_PROPDB_NONE 0 +#define DAV_PROPDB_RO 1 +#define DAV_PROPDB_DISABLE_LOCKDISCOVERY 2 DAV_DECLARE(dav_error *) dav_open_propdb( request_rec *r, dav_lockdb *lockdb, const dav_resource *resource, - int ro, + int flags, apr_array_header_t *ns_xlate, dav_propdb **propdb); +DAV_DECLARE(dav_error *) dav_popen_propdb( + apr_pool_t *p, + request_rec *r, + dav_lockdb *lockdb, + const dav_resource *resource, + int flags, + apr_array_header_t *ns_xlate, + dav_propdb **propdb); + + DAV_DECLARE(void) dav_close_propdb(dav_propdb *db); DAV_DECLARE(dav_get_props_result) dav_get_props( @@ -1706,6 +1823,7 @@ typedef struct #define DAV_WALKTYPE_AUTH 0x0001 /* limit to authorized files */ #define DAV_WALKTYPE_NORMAL 0x0002 /* walk normal files */ #define DAV_WALKTYPE_LOCKNULL 0x0004 /* walk locknull resources */ +#define DAV_WALKTYPE_TOLERANT 0x0008 /* tolerate non-fatal errors */ /* callback function and a client context for the walk */ dav_error * (*func)(dav_walk_resource *wres, int calltype); diff --git a/modules/dav/main/props.c b/modules/dav/main/props.c index f64878e..c320f8a 100644 --- a/modules/dav/main/props.c +++ b/modules/dav/main/props.c @@ -167,6 +167,8 @@ #define DAV_EMPTY_VALUE "\0" /* TWO null terms */ +#define DAV_PROP_ELEMENT "mod_dav-element" + struct dav_propdb { apr_pool_t *p; /* the pool we should use */ request_rec *r; /* the request record */ @@ -183,6 +185,8 @@ struct dav_propdb { dav_buffer wb_lock; /* work buffer for lockdiscovery property */ + int flags; /* ro, disable lock discovery */ + /* if we ever run a GET subreq, it will be stored here */ request_rec *subreq; @@ -323,7 +327,7 @@ static void dav_do_prop_subreq(dav_propdb *propdb) { /* need to escape the uri that's in the resource struct because during * the property walker it's not encoded. */ - const char *e_uri = ap_escape_uri(propdb->resource->pool, + const char *e_uri = ap_escape_uri(propdb->p, propdb->resource->uri); /* perform a "GET" on the resource's URI (note that the resource @@ -349,6 +353,11 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb, switch (propid) { case DAV_PROPID_CORE_lockdiscovery: + if (propdb->flags & DAV_PROPDB_DISABLE_LOCKDISCOVERY) { + value = ""; + break; + } + if (propdb->lockdb != NULL) { dav_lock *locks; @@ -421,18 +430,18 @@ static dav_error * dav_insert_coreprop(dav_propdb *propdb, /* use D: prefix to refer to the DAV: namespace URI, * and let the namespace attribute default to "DAV:" */ - s = apr_psprintf(propdb->p, - "<D:supported-live-property D:name=\"%s\"/>" DEBUG_CR, - name); + s = apr_pstrcat(propdb->p, + "<D:supported-live-property D:name=\"", + name, "\"/>" DEBUG_CR, NULL); } else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') { /* use D: prefix to refer to the DAV: namespace URI */ - s = apr_psprintf(propdb->p, "<D:%s>%s</D:%s>" DEBUG_CR, - name, value, name); + s = apr_pstrcat(propdb->p, "<D:", name, ">", value, "</D:", name, + ">" DEBUG_CR, NULL); } else { /* use D: prefix to refer to the DAV: namespace URI */ - s = apr_psprintf(propdb->p, "<D:%s/>" DEBUG_CR, name); + s = apr_pstrcat(propdb->p, "<D:", name, "/>" DEBUG_CR, NULL); } apr_text_append(propdb->p, phdr, s); @@ -473,11 +482,11 @@ static void dav_output_prop_name(apr_pool_t *pool, const char *s; if (*name->ns == '\0') - s = apr_psprintf(pool, "<%s/>" DEBUG_CR, name->name); + s = apr_pstrcat(pool, "<", name->name, "/>" DEBUG_CR, NULL); else { const char *prefix = dav_xmlns_add_uri(xi, name->ns); - s = apr_psprintf(pool, "<%s:%s/>" DEBUG_CR, prefix, name->name); + s = apr_pstrcat(pool, "<", prefix, ":", name->name, "/>" DEBUG_CR, NULL); } apr_text_append(pool, phdr, s); @@ -520,11 +529,25 @@ static dav_error *dav_really_open_db(dav_propdb *propdb, int ro) DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb, const dav_resource *resource, - int ro, + int flags, apr_array_header_t * ns_xlate, dav_propdb **p_propdb) { - dav_propdb *propdb = apr_pcalloc(r->pool, sizeof(*propdb)); + return dav_popen_propdb(r->pool, r, lockdb, resource, + flags, ns_xlate, p_propdb); +} + +DAV_DECLARE(dav_error *)dav_popen_propdb(apr_pool_t *p, + request_rec *r, dav_lockdb *lockdb, + const dav_resource *resource, + int flags, + apr_array_header_t * ns_xlate, + dav_propdb **p_propdb) +{ + dav_propdb *propdb = NULL; + + propdb = apr_pcalloc(p, sizeof(*propdb)); + propdb->p = p; *p_propdb = NULL; @@ -537,7 +560,6 @@ DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb, #endif propdb->r = r; - apr_pool_create(&propdb->p, r->pool); propdb->resource = resource; propdb->ns_xlate = ns_xlate; @@ -545,6 +567,8 @@ DAV_DECLARE(dav_error *)dav_open_propdb(request_rec *r, dav_lockdb *lockdb, propdb->lockdb = lockdb; + propdb->flags = flags; + /* always defer actual open, to avoid expense of accessing db * when only live properties are involved */ @@ -562,10 +586,10 @@ DAV_DECLARE(void) dav_close_propdb(dav_propdb *propdb) (*propdb->db_hooks->close)(propdb->db); } - /* Currently, mod_dav's pool usage doesn't allow clearing this pool. */ -#if 0 - apr_pool_destroy(propdb->p); -#endif + if (propdb->subreq) { + ap_destroy_sub_req(propdb->subreq); + propdb->subreq = NULL; + } } DAV_DECLARE(dav_get_props_result) dav_get_allprops(dav_propdb *propdb, @@ -706,10 +730,24 @@ DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb, apr_text_header hdr_ns = { 0 }; int have_good = 0; dav_get_props_result result = { 0 }; + dav_liveprop_elem *element; char *marks_liveprop; dav_xmlns_info *xi; int xi_filled = 0; + /* we lose both the document and the element when calling (insert_prop), + * make these available in the pool. + */ + element = dav_get_liveprop_element(propdb->resource); + if (!element) { + element = apr_pcalloc(propdb->resource->pool, sizeof(dav_liveprop_elem)); + apr_pool_userdata_setn(element, DAV_PROP_ELEMENT, NULL, propdb->resource->pool); + } + else { + memset(element, 0, sizeof(dav_liveprop_elem)); + } + element->doc = doc; + /* ### NOTE: we should pass in TWO buffers -- one for keys, one for the marks */ @@ -733,13 +771,16 @@ DAV_DECLARE(dav_get_props_result) dav_get_props(dav_propdb *propdb, dav_prop_insert inserted; dav_prop_name name; + element->elem = elem; + /* ** First try live property providers; if they don't handle ** the property, then try looking it up in the propdb. */ if (elem->priv == NULL) { - elem->priv = apr_pcalloc(propdb->p, sizeof(*priv)); + /* elem->priv outlives propdb->p. Hence use the request pool */ + elem->priv = apr_pcalloc(propdb->r->pool, sizeof(*priv)); } priv = elem->priv; @@ -909,6 +950,15 @@ DAV_DECLARE(void) dav_get_liveprop_supported(dav_propdb *propdb, } } +DAV_DECLARE(dav_liveprop_elem *) dav_get_liveprop_element(const dav_resource *resource) +{ + dav_liveprop_elem *element; + + apr_pool_userdata_get((void **)&element, DAV_PROP_ELEMENT, resource->pool); + + return element; +} + DAV_DECLARE_NONSTD(void) dav_prop_validate(dav_prop_ctx *ctx) { dav_propdb *propdb = ctx->propdb; @@ -1047,6 +1097,10 @@ DAV_DECLARE_NONSTD(void) dav_prop_exec(dav_prop_ctx *ctx) /* ** Delete the property. Ignore errors -- the property is there, or ** we are deleting it for a second time. + ** + ** http://tools.ietf.org/html/rfc4918#section-14.23 says + ** "Specifying the removal of a property that does not exist is + ** not an error" */ /* ### but what about other errors? */ (void) (*propdb->db_hooks->remove)(propdb->db, &name); diff --git a/modules/dav/main/std_liveprop.c b/modules/dav/main/std_liveprop.c index e760c65..cb46b65 100644 --- a/modules/dav/main/std_liveprop.c +++ b/modules/dav/main/std_liveprop.c @@ -154,10 +154,10 @@ static dav_prop_insert dav_core_insert_prop(const dav_resource *resource, /* assert: info != NULL && info->name != NULL */ if (what == DAV_PROP_INSERT_SUPPORTED) { - s = apr_psprintf(p, - "<D:supported-live-property D:name=\"%s\" " - "D:namespace=\"%s\"/>" DEBUG_CR, - info->name, dav_core_namespace_uris[info->ns]); + s = apr_pstrcat(p, + "<D:supported-live-property D:name=\"", info->name, + "\" D:namespace=\"", dav_core_namespace_uris[info->ns], + "\"/>" DEBUG_CR, NULL); } else if (what == DAV_PROP_INSERT_VALUE && *value != '\0') { s = apr_psprintf(p, "<lp%ld:%s>%s</lp%ld:%s>" DEBUG_CR, diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c index 9f24604..3f7822f 100644 --- a/modules/dav/main/util.c +++ b/modules/dav/main/util.c @@ -101,6 +101,9 @@ DAV_DECLARE(dav_error*) dav_join_error(dav_error *dest, dav_error *src) return dest; } +/* ### Unclear if this was designed to be used with an uninitialized + * dav_buffer struct, but is used on by dav_lock_get_activelock(). + * Hence check for pbuf->buf. */ DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf, apr_size_t extra_needed) { @@ -110,7 +113,8 @@ DAV_DECLARE(void) dav_check_bufsize(apr_pool_t * p, dav_buffer *pbuf, pbuf->alloc_len += extra_needed + DAV_BUFFER_PAD; newbuf = apr_palloc(p, pbuf->alloc_len); - memcpy(newbuf, pbuf->buf, pbuf->cur_len); + if (pbuf->buf) + memcpy(newbuf, pbuf->buf, pbuf->cur_len); pbuf->buf = newbuf; } } @@ -240,7 +244,7 @@ DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri, request. the port must match our port. */ port = r->connection->local_addr->port; - if (strcasecmp(comp.scheme, scheme) != 0 + if (ap_cstr_casecmp(comp.scheme, scheme) != 0 #ifdef APACHE_PORT_HANDLING_IS_BUSTED || comp.port != port #endif @@ -312,26 +316,71 @@ DAV_DECLARE(dav_lookup_result) dav_lookup_uri(const char *uri, */ /* validate that the root element uses a given DAV: tagname (TRUE==valid) */ -DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc, - const char *tagname) +DAV_DECLARE(int) dav_validate_root_ns(const apr_xml_doc *doc, + int ns, const char *tagname) { return doc->root && - doc->root->ns == APR_XML_NS_DAV_ID && + doc->root->ns == ns && strcmp(doc->root->name, tagname) == 0; } -/* find and return the (unique) child with a given DAV: tagname */ -DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem, - const char *tagname) +/* validate that the root element uses a given DAV: tagname (TRUE==valid) */ +DAV_DECLARE(int) dav_validate_root(const apr_xml_doc *doc, + const char *tagname) +{ + return dav_validate_root_ns(doc, APR_XML_NS_DAV_ID, tagname); +} + +/* find and return the next child with a tagname in the given namespace */ +DAV_DECLARE(apr_xml_elem *) dav_find_next_ns(const apr_xml_elem *elem, + int ns, const char *tagname) +{ + apr_xml_elem *child = elem->next; + + for (; child; child = child->next) + if (child->ns == ns && !strcmp(child->name, tagname)) + return child; + return NULL; +} + +/* find and return the (unique) child with a tagname in the given namespace */ +DAV_DECLARE(apr_xml_elem *) dav_find_child_ns(const apr_xml_elem *elem, + int ns, const char *tagname) { apr_xml_elem *child = elem->first_child; for (; child; child = child->next) - if (child->ns == APR_XML_NS_DAV_ID && !strcmp(child->name, tagname)) + if (child->ns == ns && !strcmp(child->name, tagname)) return child; return NULL; } +/* find and return the (unique) child with a given DAV: tagname */ +DAV_DECLARE(apr_xml_elem *) dav_find_child(const apr_xml_elem *elem, + const char *tagname) +{ + return dav_find_child_ns(elem, APR_XML_NS_DAV_ID, tagname); +} + +/* find and return the attribute with a name in the given namespace */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr_ns(const apr_xml_elem *elem, + int ns, const char *attrname) +{ + apr_xml_attr *attr = elem->attr; + + for (; attr; attr = attr->next) + if (attr->ns == ns && !strcmp(attr->name, attrname)) + return attr; + return NULL; +} + +/* find and return the attribute with a given DAV: tagname */ +DAV_DECLARE(apr_xml_attr *) dav_find_attr(const apr_xml_elem *elem, + const char *attrname) +{ + return dav_find_attr_ns(elem, APR_XML_NS_DAV_ID, attrname); +} + /* gather up all the CDATA into a single string */ DAV_DECLARE(const char *) dav_xml_get_cdata(const apr_xml_elem *elem, apr_pool_t *pool, int strip_white) @@ -470,8 +519,8 @@ DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi, apr_hash_this(hi, &prefix, NULL, &uri); - s = apr_psprintf(xi->pool, " xmlns:%s=\"%s\"", - (const char *)prefix, (const char *)uri); + s = apr_pstrcat(xi->pool, " xmlns:", (const char *)prefix, "=\"", + (const char *)uri, "\"", NULL); apr_text_append(xi->pool, phdr, s); } } @@ -660,7 +709,13 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih) /* note that parsed_uri.path is allocated; we can trash it */ /* clean up the URI a bit */ - ap_getparents(parsed_uri.path); + if (!ap_normalize_path(parsed_uri.path, + AP_NORMALIZE_NOT_ABOVE_ROOT | + AP_NORMALIZE_DECODE_UNRESERVED)) { + return dav_new_error(r->pool, HTTP_BAD_REQUEST, + DAV_ERR_IF_TAGGED, rv, + "Invalid URI path tagged If-header."); + } /* the resources we will compare to have unencoded paths */ if (ap_unescape_url(parsed_uri.path) != OK) { @@ -746,8 +801,14 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih) "for the same state."); } condition = DAV_IF_COND_NOT; + list += 2; + } + else { + return dav_new_error(r->pool, HTTP_BAD_REQUEST, + DAV_ERR_IF_UNK_CHAR, 0, + "Invalid \"If:\" header: " + "Unexpected character in List"); } - list += 2; break; case ' ': |