diff options
Diffstat (limited to 'modules/dav/main/mod_dav.c')
-rw-r--r-- | modules/dav/main/mod_dav.c | 635 |
1 files changed, 480 insertions, 155 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) |