diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | modules/dav/main/NWGNUmakefile | 1 | ||||
-rw-r--r-- | modules/dav/main/config5.m4 | 2 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.c | 67 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.dsp | 4 | ||||
-rw-r--r-- | modules/dav/main/mod_dav.h | 12 | ||||
-rw-r--r-- | modules/dav/main/ms_wdv.c | 830 | ||||
-rw-r--r-- | modules/dav/main/util.c | 20 |
8 files changed, 933 insertions, 4 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b7f9a69d30..9b920efac1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -450,6 +450,7 @@ SET(mod_dav_extra_sources modules/dav/main/liveprop.c modules/dav/main/props.c modules/dav/main/std_liveprop.c modules/dav/main/providers.c modules/dav/main/util.c modules/dav/main/util_lock.c + modules/dav/mail/ms_wdv.c ) SET(mod_dav_install_lib 1) SET(mod_dav_fs_extra_sources diff --git a/modules/dav/main/NWGNUmakefile b/modules/dav/main/NWGNUmakefile index a5b28c92c5..34f001cdb0 100644 --- a/modules/dav/main/NWGNUmakefile +++ b/modules/dav/main/NWGNUmakefile @@ -176,6 +176,7 @@ TARGET_lib = \ FILES_nlm_objs = \ $(OBJDIR)/mod_dav.o \ $(OBJDIR)/liveprop.o \ + $(OBJDIR)/ms_wdv.o \ $(OBJDIR)/props.o \ $(OBJDIR)/providers.o \ $(OBJDIR)/std_liveprop.o \ diff --git a/modules/dav/main/config5.m4 b/modules/dav/main/config5.m4 index ee798e3202..114a573583 100644 --- a/modules/dav/main/config5.m4 +++ b/modules/dav/main/config5.m4 @@ -2,7 +2,7 @@ dnl modules enabled in this directory by default APACHE_MODPATH_INIT(dav/main) -dav_objects="mod_dav.lo props.lo util.lo util_lock.lo liveprop.lo providers.lo std_liveprop.lo" +dav_objects="mod_dav.lo ms_wdv.lo props.lo util.lo util_lock.lo liveprop.lo providers.lo std_liveprop.lo" if test "$enable_http" = "no"; then dav_enable=no diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c index ea87317c3f..51687203cc 100644 --- a/modules/dav/main/mod_dav.c +++ b/modules/dav/main/mod_dav.c @@ -76,6 +76,12 @@ enum { DAV_ENABLED_ON }; +typedef enum { + DAV_MSEXT_NONE = 0, + DAV_MSEXT_WDV = 1, + DAV_MSEXT_ALL = 1, +} dav_msext_opts; + /* per-dir configuration */ typedef struct { const char *provider_name; @@ -84,7 +90,7 @@ typedef struct { int locktimeout; int allow_depthinfinity; int allow_lockdiscovery; - + dav_msext_opts msext_opts; } dav_dir_conf; /* per-server configuration */ @@ -108,6 +114,7 @@ enum { }; static int dav_methods[DAV_M_LAST]; +static const char *dav_cmd_davmsext(cmd_parms *, void *, const char *); static int dav_init_handler(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) @@ -206,6 +213,8 @@ static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides) allow_depthinfinity); newconf->allow_lockdiscovery = DAV_INHERIT_VALUE(parent, child, allow_lockdiscovery); + newconf->msext_opts = DAV_INHERIT_VALUE(parent, child, + msext_opts); return newconf; } @@ -319,6 +328,33 @@ static const char *dav_cmd_davlockdiscovery(cmd_parms *cmd, void *config, } /* + * Command handler for the DAVmsExt directive, which is RAW + */ +static const char *dav_cmd_davmsext(cmd_parms *cmd, void *config, const char *w) +{ + dav_dir_conf *conf = (dav_dir_conf *)config; + + if (!ap_cstr_casecmp(w, "None")) + conf->msext_opts = DAV_MSEXT_NONE; + else if (!ap_cstr_casecmp(w, "Off")) + conf->msext_opts = DAV_MSEXT_NONE; + else if (!ap_cstr_casecmp(w, "+WDV")) + conf->msext_opts |= DAV_MSEXT_WDV; + else if (!ap_cstr_casecmp(w, "WDV")) + conf->msext_opts |= DAV_MSEXT_WDV; + else if (!ap_cstr_casecmp(w, "-WDV")) + conf->msext_opts &= ~DAV_MSEXT_WDV; + else if (!ap_cstr_casecmp(w, "All")) + conf->msext_opts = DAV_MSEXT_ALL; + else if (!ap_cstr_casecmp(w, "On")) + conf->msext_opts = DAV_MSEXT_ALL; + else + return "DAVMSext values can be None | [+|-]WDV | All"; + + return NULL; +} + +/* * Command handler for DAVMinTimeout directive, which is TAKE1 */ static const char *dav_cmd_davmintimeout(cmd_parms *cmd, void *config, @@ -964,6 +1000,7 @@ static int dav_method_post(request_rec *r) /* handle the PUT method */ static int dav_method_put(request_rec *r) { + dav_dir_conf *conf; dav_resource *resource; int resource_state; dav_auto_version_info av_info; @@ -1167,6 +1204,11 @@ static int dav_method_put(request_rec *r) dav_log_err(r, err2, APLOG_WARNING); } + /* This performs MS-WDV PROPPATCH combined with PUT */ + conf = ap_get_module_config(r->per_dir_config, &dav_module); + if (conf->msext_opts & DAV_MSEXT_WDV) + (void)dav_mswdv_postprocessing(r); + /* ### place the Content-Type and Content-Language into the propdb */ if (locks_hooks != NULL) { @@ -4971,6 +5013,15 @@ static int dav_method_bind(request_rec *r) */ static int dav_handler(request_rec *r) { + dav_dir_conf *conf; + int ret; + + conf = ap_get_module_config(r->per_dir_config, &dav_module); + if (conf->msext_opts & DAV_MSEXT_WDV) { + if ((ret = dav_mswdv_preprocessing(r)) != OK) + return ret; + } + if (strcmp(r->handler, DAV_HANDLER_NAME) != 0) return DECLINED; @@ -4978,7 +5029,7 @@ static int dav_handler(request_rec *r) * be more destructive than the user intended. */ if (r->parsed_uri.fragment != NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00622) - "buggy client used un-escaped hash in Request-URI"); + "buggy client used un-escaped hash in Request-URI %s in method %d", r->unparsed_uri, r->method_number); return dav_error_response(r, HTTP_BAD_REQUEST, "The request was invalid: the URI included " "an un-escaped hash character"); @@ -5204,6 +5255,8 @@ static int dav_fixups(request_rec *r) return DECLINED; } + + static void register_hooks(apr_pool_t *p) { ap_hook_handler(dav_handler, NULL, NULL, APR_HOOK_MIDDLE); @@ -5219,6 +5272,11 @@ static void register_hooks(apr_pool_t *p) dav_hook_gather_reports(dav_core_gather_reports, NULL, NULL, APR_HOOK_LAST); + ap_register_output_filter("DAV_MSWDV_OUT", dav_mswdv_output, NULL, + AP_FTYPE_RESOURCE); + ap_register_input_filter("DAV_MSWDV_IN", dav_mswdv_input, NULL, + AP_FTYPE_RESOURCE); + dav_core_register_uris(p); } @@ -5248,6 +5306,11 @@ static const command_rec dav_cmds[] = ACCESS_CONF|RSRC_CONF, "allow lock discovery by PROPFIND requests"), + /* per directory/location, or per server */ + AP_INIT_ITERATE("DAVMSext", dav_cmd_davmsext, NULL, + ACCESS_CONF|RSRC_CONF, + "Enable MS-WDV extensions"), + { NULL } }; diff --git a/modules/dav/main/mod_dav.dsp b/modules/dav/main/mod_dav.dsp index 752cea898e..dc5f609c87 100644 --- a/modules/dav/main/mod_dav.dsp +++ b/modules/dav/main/mod_dav.dsp @@ -112,6 +112,10 @@ SOURCE=.\mod_dav.c # End Source File # Begin Source File +SOURCE=.\ms_wdv.c +# End Source File +# Begin Source File + SOURCE=.\props.c # End Source File # Begin Source File diff --git a/modules/dav/main/mod_dav.h b/modules/dav/main/mod_dav.h index 68b04f3851..b0c6f526bd 100644 --- a/modules/dav/main/mod_dav.h +++ b/modules/dav/main/mod_dav.h @@ -1321,6 +1321,7 @@ struct dav_hooks_propdb #define DAV_TIMEOUT_INFINITE 0 DAV_DECLARE(time_t) dav_get_timeout(request_rec *r); +DAV_DECLARE(time_t) dav_get_timeout_string(request_rec *r, const char *s); /* ** Opaque, provider-specific information for a lock database. @@ -2650,6 +2651,17 @@ typedef struct { } dav_elem_private; +/* MS-WDV combined operation handler */ +DAV_DECLARE(int) dav_mswdv_preprocessing(request_rec *r); +DAV_DECLARE(dav_error *) dav_mswdv_postprocessing(request_rec *r); +DAV_DECLARE(apr_status_t) dav_mswdv_output(ap_filter_t *f, + apr_bucket_brigade *bb); +DAV_DECLARE(apr_status_t) dav_mswdv_input(ap_filter_t *f, + apr_bucket_brigade *bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes); + /* -------------------------------------------------------------------- ** ** DAV ACL HOOKS diff --git a/modules/dav/main/ms_wdv.c b/modules/dav/main/ms_wdv.c new file mode 100644 index 0000000000..dc368ba264 --- /dev/null +++ b/modules/dav/main/ms_wdv.c @@ -0,0 +1,830 @@ +#include "apr_strings.h" +#include "apr_lib.h" + +#include "httpd.h" +#include "http_config.h" +#include "http_protocol.h" +#include "http_request.h" +#include "http_log.h" + +#include "mod_dav.h" + +/* + * Extended error codes, from MS-WDV section 6 + * This is a subset of codes defined in MS-WEBDAVE section 2.2.3 + */ +#define DAV_DOC_CHECKED_OUT 0x0009000E +#define DAV_CHECKOUT_REQUIRED 0x00090075 +#define DAV_BAD_FILETYPE_NO_URL 0x0009006F +#define DAV_SHTML_REQUEST_TOO_LONG 0x0006000A +#define DAV_FORMS_AUTH_NOT_BROWSER 0x000E0098 +#define DAV_VIRUS_INFECTED_UL 0x00960004 +#define DAV_VIRUS_INFECTED_BLOCKED_DL 0x00960009 +#define DAV_VIRUS_DELETED_DL 0x00960008 +#define DAV_BAD_CHARS_IN_URL 0x00090070 +#define DAV_NO_RENAME_TO_THICKET_FOLDER 0x00090071 +#define DAV_URL_TOO_LONG 0x00090068 +#define DAV_OVER_QUOTA 0x00090063 + +/* + * Cope with MS behavior on DELETE: + * If: (<locktoken>) is changed into If: <uri> (<locktoken>) + */ +static void delete_if_fixup(request_rec *r) +{ + const char *if_hdr; + const char *cp; + apr_size_t len; + + if ((if_hdr = apr_table_get(r->headers_in, "If")) == NULL) + goto out; + + /* check for parenthesis enclosed value */ + len = strlen(if_hdr); + if (if_hdr[0] != '(' || if_hdr[len - 1]!= ')') + goto out; + + for (cp = if_hdr; *cp; cp++) { + if (*cp == ')' && *(cp + 1)) + goto out; + } + + if_hdr = apr_psprintf(r->pool, "<%s> %s", r->uri, if_hdr); + apr_table_set(r->headers_in, "If", if_hdr); + +out: + return; +} + +/* + * Ms-Echo-Request and Ms-Echo-Reply headers are specified + * in MS-WDV sections 2.2.7 and 2.2.8 + */ +static dav_error *mswdv_echo(request_rec *r) +{ + const char *value; + + if ((value = apr_table_get(r->headers_in, "Ms-Echo-Request")) != NULL) + apr_table_set(r->headers_out, "Ms-Echo-Reply", value); + + return NULL; +} + + +static const char *get_lock_owner(request_rec *r, dav_lock *lock) +{ + while (lock) { + if (lock->auth_user) { + break; + } + lock = lock->next; + } + + return lock->auth_user; +} + + +static const char *mswdv_urlencode(request_rec *r, const char *str) +{ + const char *ip = str; + char *output; + char *op; + + output = apr_palloc(r->pool, 3 * strlen(str) + 1); + op = output; + + for (ip = str; *ip; ip++) { + if (apr_isalnum(*ip)) { + *op++ = *ip; + } else { + char msb = (*ip >> 4); + char lsb = (*ip & 0x0f); + *op++ = '%'; + *op++ = msb > 10 ? 'A' + msb - 10 : '0' +msb; + *op++ = lsb > 10 ? 'A' + lsb - 10 : '0' +lsb; + } + } + *op++ = '\0'; + + return (const char *)output; +} + +static void mswdv_err_checked_out(request_rec *r, const char *owner) +{ + const char *msg; + + msg = apr_psprintf(r->pool, "Resource already locked by %s", + owner ? owner : "anonymous"); + msg = mswdv_urlencode(r, msg); + + apr_table_set(r->err_headers_out, + "X-MSDAVEXT_ERROR", + apr_psprintf(r->pool, + "%d; %s", DAV_DOC_CHECKED_OUT, msg)); +} + +static dav_error *check_locked_by_other(request_rec *r) +{ + const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); + dav_lockdb *lockdb = NULL; + dav_resource *resource; + dav_lock *lock = NULL; + const char *owner = NULL; + dav_error *err = NULL; + + if ((err = dav_get_resource(r, 0, 0, &resource)) != NULL) + goto out; + + /* dav_lock_query reads R/W in dav_fs_save_lock_record() */ + if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) + goto out; + + if ((err = dav_lock_query(lockdb, resource, &lock)) != NULL) + goto out; + + if (!lock) + goto out; + + owner = get_lock_owner(r, lock); + if ((owner && r->user && strcmp(owner, r->user) != 0) || + (owner && !r->user) || (!owner && r->user)) + mswdv_err_checked_out(r, owner); + + /* Let lock method fail the request */ + +out: + (*lockdb->hooks->close_lockdb)(lockdb); + + return err; +} + +/* + * Adding lock headers to existing commands is specified + * in MS-WDV section 3.2.5.2 + */ +static dav_error *mswdv_combined_lock(request_rec *r) +{ + const char *lock_token_hdr; + const char *lock_timeout_hdr; + dav_locktoken *lock_token; + time_t lock_timeout = 0; + apr_status_t status; + dav_error *err = NULL; + const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r); + dav_lockdb *lockdb = NULL; + dav_resource *resource; + dav_lock *lock = NULL; + const char *owner = NULL; + dav_lock *newlock = NULL; + /* conditions */ + int timeout_zero = 0; + int token_match = 0; + int lock_exists = 0; + int locked_by_other = 0; + /* action */ + const char *failmsg = NULL; + int http_error = HTTP_BAD_REQUEST; + enum { ERROR, LOCK, UNLOCK, REFRESH, PASS } action = ERROR; + + lock_token_hdr = apr_table_get(r->headers_in, "Lock-Token"); + lock_timeout_hdr = apr_table_get(r->headers_in, "X-MSDAVEXTLockTimeout"); + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "%s Lock-Token = \"%s\" X-MSDAVEXTLockTimeout = \"%s\"", + __func__, lock_token_hdr, lock_timeout_hdr); + + /* + * Strip brackets if present. They should be present, but MS-WDV + * section 4.5 suggests using Lock-Token without brakets. + */ + if (lock_token_hdr) { + apr_size_t len = strlen(lock_token_hdr); + + if (lock_token_hdr[0] == '<' || lock_token_hdr[len - 1] == '>') + lock_token_hdr = apr_pstrndup(r->pool, lock_token_hdr + 1, len - 2); + } + + if (lock_timeout_hdr) { + if (strcmp(lock_timeout_hdr, "Second-0") == 0) + timeout_zero = 1; + lock_timeout = dav_get_timeout_string(r, lock_timeout_hdr); + } + + /* Check MS-WDV section 3.2.5.2 for specified behaviors */ + + /* + * First handle behaviors that do not use lock database + */ + if (r->method_number == M_GET || + r->method_number == M_POST) { + if (lock_token_hdr && !lock_timeout_hdr) + goto out; + } + + if (!lock_token_hdr && lock_timeout_hdr && timeout_zero) { + failmsg = "Unlock operation requires a lock token."; + goto done; + } + + /* + * Determine is token_match, lock_exists and locked_by_other + */ + if ((err = dav_get_resource(r, 0, 0, &resource)) != NULL) + goto out; + + /* dav_lock_query reads R/W in dav_fs_save_lock_record() */ + if ((err = (*locks_hooks->open_lockdb)(r, 0, 0, &lockdb)) != NULL) + goto out; + + if ((err = dav_lock_query(lockdb, resource, &lock)) != NULL) + goto out; + + if (lock) { + lock_exists = 1; + owner = get_lock_owner(r, lock); + } + + if (lock_token_hdr) { + if ((err = (*locks_hooks->parse_locktoken)(r->pool, lock_token_hdr, + &lock_token)) != NULL) + goto out; + + if ((err = (*locks_hooks->find_lock)(lockdb, resource, lock_token, + 0, &lock)) != NULL) + goto out; + + if (lock) + token_match = 1; + + } + + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "%s lock_exists = %d, owner = \"%s\", " + "token_match = %d, lock_timeout = %ld, timeout_zero = %d", + __func__, lock_exists, owner ? owner : "-", token_match, + lock_timeout, timeout_zero); + + /* This implements the table from MS-WDV section 3.2.5.2 */ + if (r->method_number == M_GET || + r->method_number == M_POST) { + + if (lock_token_hdr && !lock_timeout_hdr) { + action = PASS; + goto done; + } + + if (lock_token_hdr && lock_timeout_hdr) { + if (!token_match) { + failmsg = "Provided lock token does not match."; + if (lock_exists) { + http_error = HTTP_LOCKED; + mswdv_err_checked_out(r, owner); + } else { + http_error = HTTP_FORBIDDEN; + } + goto done; + } + + if (!lock_exists) { + failmsg = "Refresh or unlock operation on unlocked resource."; + goto done; + } + + if (!timeout_zero) { + action = REFRESH; + goto done; + } + + if (timeout_zero) { + action = UNLOCK; + goto done; + } + + /* NOTREACHED */ + } + + if (!lock_token_hdr && lock_timeout_hdr) { + if (lock_exists) { + failmsg = "Lock operation on an already locked resource."; + http_error = HTTP_LOCKED; + mswdv_err_checked_out(r, owner); + goto done; + } + + if (timeout_zero) { + failmsg = "Lock operation with immediate timeout."; + goto done; + } + + if (!lock_exists) { + action = LOCK; + goto done; + } + } + + if (!lock_token_hdr && !lock_timeout_hdr) { + action = PASS; + goto done; + } + } + + if (r->method_number == M_PUT) { + if (lock_token_hdr && !lock_timeout_hdr) { + if (!token_match) { + failmsg = "Provided lock token does not match."; + if (lock_exists) { + http_error = HTTP_LOCKED; + mswdv_err_checked_out(r, owner); + } else { + http_error = HTTP_FORBIDDEN; + } + goto done; + } + + if (!lock_exists) { + failmsg = "PUT with lock on an unlocked resource."; + goto done; + } + + if (token_match && lock_exists) { + action = PASS; + goto done; + } + } + + if (lock_token_hdr && lock_timeout_hdr) { + if (!token_match) { + failmsg = "Provided lock token does not match"; + if (lock_exists) { + http_error = HTTP_LOCKED; + mswdv_err_checked_out(r, owner); + } else { + http_error = HTTP_FORBIDDEN; + } + goto done; + } + + if (!lock_exists) { + failmsg = "PUT with lock on an unlocked resource"; + goto done; + } + + if (!timeout_zero) { + action = REFRESH; + goto done; + } + + if (timeout_zero) { + action = UNLOCK; + goto done; + } + /* NOTREACHED */ + } + + + if (!lock_token_hdr && lock_timeout_hdr) { + if (lock_exists) { + failmsg = "Lock operation on already locked resource."; + http_error = HTTP_LOCKED; + mswdv_err_checked_out(r, owner); + goto done; + } + + if (timeout_zero) { + failmsg = "Lock operation with immediate timeout."; + goto done; + } + + if (!lock_exists) { + action = LOCK; + goto done; + } + } + + if (!lock_token_hdr && !lock_timeout_hdr) { + action = PASS; + goto done; + } + } + +done: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "%s failmsg = \"%s\", action = %s%s%s%s%s", + __func__, failmsg, + action == LOCK ? "LOCK" : "", + action == UNLOCK ? "UNLOCK" : "", + action == REFRESH ? "REFRESH" : "", + action == ERROR ? "ERROR" : "", + action == PASS ? "PASS" : ""); + + if (failmsg) { + err = dav_new_error(r->pool, http_error, 0, 0, failmsg); + goto out; + } + + switch (action) { + case PASS: + if (lock_token_hdr) { + /* Add a If: lock header to palcate further processing */ + apr_table_setn(r->headers_in, "If", + apr_psprintf(r->pool, "(<%s>)", lock_token_hdr)); + } + break; + case LOCK: { + dav_response *dontcare; + + if ((err = (*locks_hooks->create_lock)(lockdb, resource, + &newlock)) != NULL) + goto out; + + newlock->depth = DAV_INFINITY; + newlock->timeout = lock_timeout; + newlock->type = DAV_LOCKTYPE_WRITE; + newlock->scope = DAV_LOCKSCOPE_EXCLUSIVE; + newlock->auth_user = apr_pstrdup(r->pool, r->user); + newlock->owner = apr_psprintf(r->pool, + "<ns0:owner xmlns:ns0=\"DAV:\">" + "<ns0:href>%s</ns0:href>" + "</ns0:owner>", + r->user ? r->user : "anonymous"); + if ((err = dav_add_lock(r, resource, lockdb, newlock, + &dontcare)) != NULL) + goto out; + + break; + } + + case UNLOCK: + if ((err = (*locks_hooks->remove_lock)(lockdb, resource, + lock_token)) != NULL) + goto out; + + break; + + case REFRESH: { + const dav_locktoken_list ltl = { lock_token, NULL }; + + if ((err = (*locks_hooks->refresh_locks)(lockdb, resource, <l, + lock_timeout, + &newlock)) != NULL) + goto out; + + break; + } + + case ERROR: /* FALLTHROUGH */ + default: + /* NOTREACHED */ + err = dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "Unexpected X-MSDAVEXT combined lock action."); + goto out; + break; + } + + if (newlock) { + /* + * MS-WDV section 4.5 suggests to send a lock token without + * brackets, which is at odds with standards. + */ + apr_table_setn(r->headers_out, "Lock-Token", + (*locks_hooks->format_locktoken)(r->pool, + newlock->locktoken)); + + apr_table_setn(r->headers_out, "X-MSDAVEXTLockTimeout", + newlock->timeout == DAV_TIMEOUT_INFINITE ? + "Infinite" : + apr_psprintf(r->pool, "Second-%d", + newlock->timeout - time(NULL))); + + /* Add a If: lock header to palcate further PUT processing */ + apr_table_setn(r->headers_in, "If", + apr_pstrcat(r->pool, "(<", + (*locks_hooks->format_locktoken)(r->pool, + newlock->locktoken), + ">)", NULL)); + } + + +out: + if (lockdb) + (*lockdb->hooks->close_lockdb)(lockdb); + + return err; +} + +/* + * Combined PROPFIND is specified in MS-WDV sections 2.2.1 and 2.2.5 + */ +static dav_error *mswdv_combined_propfind(request_rec *r) +{ + apr_bucket_brigade *bbsub; + apr_bucket_brigade *bb; + ap_filter_t *f; + apr_bucket *b; + request_rec *rr = NULL; + apr_off_t length; + apr_status_t status; + int ret; + + bbsub = apr_brigade_create(r->pool, r->output_filters->c->bucket_alloc); + + rr = ap_sub_req_method_uri("PROPFIND", r->uri, r, r->output_filters); + if (!rr || rr->status != HTTP_OK) + return dav_new_error(r->pool, + rr ? rr->status : HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "X-DAVMSEXT PROPFIND subrequest lookup failed"); + + f = ap_add_output_filter("DAV_MSWDV_OUT", bbsub, rr, rr->connection); + if (!f) + return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "DAV_MSWDV_OUT filter not found"); + + if ((ret = ap_run_sub_req(rr)) != OK) { + char *errmsg = apr_psprintf(r->pool, + "X-DAVMSEXT PROPFIND status %d", + ret); + return dav_new_error(r->pool, rr->status, 0, 0, errmsg); + } + + ap_remove_output_filter(f); + + if ((status = apr_brigade_length(bbsub, 1, &length)) != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, status, + "read response error"); + + bb = apr_brigade_create(r->pool,r->output_filters->c->bucket_alloc); + + apr_brigade_printf(bb, NULL, NULL, + "%016" APR_UINT64_T_HEX_FMT, length); + + APR_BRIGADE_CONCAT(bb, bbsub); + + ap_destroy_sub_req(rr); + + rr = ap_sub_req_lookup_uri(r->uri, r, r->output_filters); + if (!rr || rr->status != HTTP_OK) + return dav_new_error(r->pool, + rr ? rr->status : HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "X-DAVMSEXT GET subrequest lookup failed"); + + if (rr->filename == NULL || rr->finfo.filetype != APR_REG) + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0, + "Not a plain file"); + + apr_brigade_printf(bb, NULL, NULL, + "%016" APR_UINT64_T_HEX_FMT, rr->finfo.size); + + ap_set_content_type(r, "multipart/MSDAVEXTPrefixEncoded"); + + ap_pass_brigade(r->output_filters, bb); + + ap_destroy_sub_req(rr); + + return NULL; +} + +/* + * Combined PROPPATCH is specified in MS-WDV sections 2.2.1 and 2.2.5 + */ +static dav_error *mswdv_combined_proppatch(request_rec *r) +{ + dav_error *err = NULL; + apr_bucket_brigade *bbsub; + apr_bucket_brigade *bb; + apr_bucket *b; + apr_status_t status; + char *proppach_data; + apr_size_t len = 16; + apr_off_t proppatch_len; + char proppatch_len_str[16 + 1]; + char *proppatch_data; + apr_off_t index; + apr_size_t proppatch_datalen; + ap_filter_t *f; + request_rec *rr; + int ret; + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + status = ap_get_brigade(r->input_filters, bb, + AP_MODE_READBYTES, APR_BLOCK_READ, + len); + if (status != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, status, + "error reading PROPPATCH part ldength"); + + status = apr_brigade_flatten(bb, proppatch_len_str, &len); + if (status != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, status, + "error reading input"); + + if (len != 16) + return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, status, + "Unexpected PROPPATCH part length"); + + proppatch_len_str[16] = '\0'; + + status = apr_strtoff(&proppatch_len, proppatch_len_str, NULL, 16); + if (status != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, status, + "Bad PROPPATCH part length"); + + apr_brigade_destroy(bb); + + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + status = ap_get_brigade(r->input_filters, bb, + AP_MODE_READBYTES, APR_BLOCK_READ, + proppatch_len); + if (status != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, status, + "Error reading PROPPATCH part"); + + /* + * For file creation, the PROPATCH subrequest must be done after + * the PUT, otherwise the file does not exist yet. This mean we + * need to copy the PROPPATCH data to perform subrequest in + * dav_mswdv_postprocessing(). + */ + proppatch_data = apr_palloc(r->pool, proppatch_len); + + len = proppatch_len; + status = apr_brigade_flatten(bb, proppatch_data, &len); + if (status != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, status, + "Error flattening PROPPATCH part"); + + apr_table_setn(r->notes, "dav_mswdv_proppatch_data", proppatch_data); + + apr_brigade_destroy(bb); + + /* skip file length to give the file to plain PUT processing */ + bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + status = ap_get_brigade(r->input_filters, bb, + AP_MODE_READBYTES, APR_BLOCK_READ, + 16); + if (status != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, status, + "Error reading PUT part length"); + + apr_table_setn(r->headers_in, "Content-Type", "application/octet-stream"); + + return NULL; +} + +DAV_DECLARE(int) dav_mswdv_preprocessing(request_rec *r) +{ + const char *hdr; + dav_error *err = NULL; + + /* MS-WDV extensions need X-MSDAVEXT even on an error */ + if (r->method_number == M_OPTIONS) { + apr_table_setn(r->headers_out, "X-MSDAVEXT", "1"); + apr_table_setn(r->err_headers_out, "X-MSDAVEXT", "1"); + } + + /* Remove tailing # */ + if (r->method_number != M_GET && r->method_number != M_POST) + r->parsed_uri.fragment = NULL; + + if (r->main) + goto out; + + if (apr_table_get(r->headers_in, "Ms-Echo-Request")) { + if ((err = mswdv_echo(r)) != NULL) + goto out; + } + + if ((apr_table_get(r->headers_in, "Lock-Token") || + apr_table_get(r->headers_in, "X-MSDAVEXTLockTimeout")) && + (r->method_number == M_GET || + r->method_number == M_POST || + r->method_number == M_PUT)) { + if ((err = mswdv_combined_lock(r)) != NULL) + goto out; + } + + if ((hdr = apr_table_get(r->headers_in, "X-MSDAVEXT")) != NULL) { + if (!strcmp(hdr, "PROPFIND") && + (r->method_number == M_GET || + r->method_number == M_POST || + r->method_number == M_PUT)) { + if ((err = mswdv_combined_propfind(r)) != NULL) + goto out; + } + + if (!strcmp(hdr, "PROPPATCH") && + r->method_number == M_PUT) + if ((err = mswdv_combined_proppatch(r)) != NULL) + goto out; + } + + if (r->method_number == M_DELETE) + delete_if_fixup(r); + + if (r->method_number == M_LOCK || + r->method_number == M_MOVE || + r->method_number == M_PUT || + r->method_number == M_DELETE) { + if ((err = check_locked_by_other(r)) != NULL) + goto out; + } + +out: + if (err) + return dav_handle_err(r, err, NULL); + + return OK; +} + +DAV_DECLARE(dav_error *)dav_mswdv_postprocessing(request_rec *r) +{ + dav_error *err = NULL; + const char *proppatch_data; + apr_bucket_brigade *bbsub; + apr_bucket *b; + request_rec *rr; + ap_filter_t *f; + apr_status_t status; + int ret; + + if (r->method_number != M_PUT) + goto out; + + proppatch_data = apr_table_get(r->notes, "dav_mswdv_proppatch_data"); + if (proppatch_data == NULL) + goto out; + + bbsub = apr_brigade_create(r->pool, r->connection->bucket_alloc); + + status = apr_brigade_puts(bbsub, NULL, NULL, proppatch_data); + if (status != APR_SUCCESS) + return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, status, + "Error postprocessing PROPPATCH part"); + + b = apr_bucket_eos_create(r->connection->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bbsub, b); + + rr = ap_sub_req_method_uri("PROPPATCH", r->uri, r, r->output_filters); + if (!rr || rr->status != HTTP_OK) { + return dav_new_error(r->pool, + rr ? rr->status : HTTP_INTERNAL_SERVER_ERROR, + 0, 0, "PROPPATCH subrequest lookup failed"); + } + + f = ap_add_input_filter("DAV_MSWDV_IN", bbsub, rr, rr->connection); + if (!f) + return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0, + "DAV_MSWDV_IN filter not found"); + + + if ((ret = ap_run_sub_req(rr)) != OK) { + char *errmsg = apr_psprintf(r->pool, + "X-DAVMSEXT PROPPATCH status %d", + ret); + return dav_new_error(r->pool, rr->status, 0, 0, errmsg); + } + + ap_remove_input_filter(f); + + ap_destroy_sub_req(rr); + +out: + return err; +} + +DAV_DECLARE(apr_status_t) dav_mswdv_output(ap_filter_t *f, + apr_bucket_brigade *bb) +{ + apr_bucket_brigade *bbsub = f->ctx; + apr_bucket *b; + + b = APR_BRIGADE_FIRST(bb); + while (b != APR_BRIGADE_SENTINEL(bb)) { + apr_bucket *nb; + if (APR_BUCKET_IS_EOS(b)) + break; + + nb = APR_BUCKET_NEXT(b); + APR_BUCKET_REMOVE(b); + APR_BRIGADE_INSERT_TAIL(bbsub, b); + b = nb; + } + + return ap_pass_brigade(f->next, bb); +} + +DAV_DECLARE(apr_status_t) dav_mswdv_input(ap_filter_t *f, + apr_bucket_brigade *bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + apr_bucket_brigade *bbsub = f->ctx; + + APR_BRIGADE_CONCAT(bb, bbsub); + + return APR_SUCCESS; +} + diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c index 3f7822fc93..2351e3f38e 100644 --- a/modules/dav/main/util.c +++ b/modules/dav/main/util.c @@ -541,9 +541,27 @@ DAV_DECLARE(void) dav_xmlns_generate(dav_xmlns_info *xi, */ DAV_DECLARE(time_t) dav_get_timeout(request_rec *r) { + const char *timeout_const = apr_table_get(r->headers_in, "Timeout"); + + if (timeout_const == NULL) + return DAV_TIMEOUT_INFINITE; + + return dav_get_timeout_string(r, timeout_const); +} + +/* dav_get_timeout_string: From a Timeout header value, return a time_t + * when this lock is expected to expire. Otherwise, return + * a time_t of DAV_TIMEOUT_INFINITE. + * + * It's unclear if DAV clients are required to understand + * Seconds-xxx and Infinity time values. We assume that they do. + * In addition, for now, that's all we understand, too. + */ +DAV_DECLARE(time_t) dav_get_timeout_string(request_rec *r, + const char *timeout_const) +{ time_t now, expires = DAV_TIMEOUT_INFINITE; - const char *timeout_const = apr_table_get(r->headers_in, "Timeout"); const char *timeout = apr_pstrdup(r->pool, timeout_const), *val; if (timeout == NULL) |