You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ma...@apache.org on 2023/02/13 16:48:35 UTC

svn commit: r1907608 - in /httpd/httpd/trunk: CMakeLists.txt modules/dav/main/NWGNUmakefile modules/dav/main/config5.m4 modules/dav/main/mod_dav.c modules/dav/main/mod_dav.dsp modules/dav/main/mod_dav.h modules/dav/main/ms_wdv.c modules/dav/main/util.c

Author: manu
Date: Mon Feb 13 16:48:35 2023
New Revision: 1907608

URL: http://svn.apache.org/viewvc?rev=1907608&view=rev
Log:
Add MS-WDV support

MS-WDV specification:
https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wdv

The changes introduces the DAVMSext directive, which is used to 
enable MS-WDV: DAVMSext +WDV

dav_get_timeout_string() is introduced as a variant of dav_get_timeout().
The former parses a string, the later parse the Timeout HTTP header.


Added:
    httpd/httpd/trunk/modules/dav/main/ms_wdv.c
Modified:
    httpd/httpd/trunk/CMakeLists.txt
    httpd/httpd/trunk/modules/dav/main/NWGNUmakefile
    httpd/httpd/trunk/modules/dav/main/config5.m4
    httpd/httpd/trunk/modules/dav/main/mod_dav.c
    httpd/httpd/trunk/modules/dav/main/mod_dav.dsp
    httpd/httpd/trunk/modules/dav/main/mod_dav.h
    httpd/httpd/trunk/modules/dav/main/util.c

Modified: httpd/httpd/trunk/CMakeLists.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CMakeLists.txt?rev=1907608&r1=1907607&r2=1907608&view=diff
==============================================================================
--- httpd/httpd/trunk/CMakeLists.txt (original)
+++ httpd/httpd/trunk/CMakeLists.txt Mon Feb 13 16:48:35 2023
@@ -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

Modified: httpd/httpd/trunk/modules/dav/main/NWGNUmakefile
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/NWGNUmakefile?rev=1907608&r1=1907607&r2=1907608&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/dav/main/NWGNUmakefile (original)
+++ httpd/httpd/trunk/modules/dav/main/NWGNUmakefile Mon Feb 13 16:48:35 2023
@@ -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 \

Modified: httpd/httpd/trunk/modules/dav/main/config5.m4
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/config5.m4?rev=1907608&r1=1907607&r2=1907608&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/dav/main/config5.m4 (original)
+++ httpd/httpd/trunk/modules/dav/main/config5.m4 Mon Feb 13 16:48:35 2023
@@ -2,7 +2,7 @@ dnl modules enabled in this directory by
 
 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

Modified: httpd/httpd/trunk/modules/dav/main/mod_dav.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/mod_dav.c?rev=1907608&r1=1907607&r2=1907608&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/dav/main/mod_dav.c (original)
+++ httpd/httpd/trunk/modules/dav/main/mod_dav.c Mon Feb 13 16:48:35 2023
@@ -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_po
                                                      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_davlockdiscov
 }
 
 /*
+ * 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 *
 /* 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 *
  */
 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 }
 };
 

Modified: httpd/httpd/trunk/modules/dav/main/mod_dav.dsp
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/mod_dav.dsp?rev=1907608&r1=1907607&r2=1907608&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/dav/main/mod_dav.dsp (original)
+++ httpd/httpd/trunk/modules/dav/main/mod_dav.dsp Mon Feb 13 16:48:35 2023
@@ -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

Modified: httpd/httpd/trunk/modules/dav/main/mod_dav.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/mod_dav.h?rev=1907608&r1=1907607&r2=1907608&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/dav/main/mod_dav.h (original)
+++ httpd/httpd/trunk/modules/dav/main/mod_dav.h Mon Feb 13 16:48:35 2023
@@ -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

Added: httpd/httpd/trunk/modules/dav/main/ms_wdv.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/ms_wdv.c?rev=1907608&view=auto
==============================================================================
--- httpd/httpd/trunk/modules/dav/main/ms_wdv.c (added)
+++ httpd/httpd/trunk/modules/dav/main/ms_wdv.c Mon Feb 13 16:48:35 2023
@@ -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, &ltl,
+                                                 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;
+}
+

Modified: httpd/httpd/trunk/modules/dav/main/util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/util.c?rev=1907608&r1=1907607&r2=1907608&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/dav/main/util.c (original)
+++ httpd/httpd/trunk/modules/dav/main/util.c Mon Feb 13 16:48:35 2023
@@ -541,9 +541,27 @@ DAV_DECLARE(void) dav_xmlns_generate(dav
  */
 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)



Re: svn commit: r1907608 - in /httpd/httpd/trunk: CMakeLists.txt modules/dav/main/NWGNUmakefile modules/dav/main/config5.m4 modules/dav/main/mod_dav.c modules/dav/main/mod_dav.dsp modules/dav/main/mod_dav.h modules/dav/main/ms_wdv.c modules/dav/main/util.c

Posted by Emmanuel Dreyfus <ma...@netbsd.org>.
> > +static const char *mswdv_urlencode(request_rec *r, const char *str)
(...)
> Any reason why we cannot use an existing function like  ap_escape_path_segment here?

MS-WDV section 2.2.3 quoted below says it needs to be percent-encoded as 
in RFC3986 section 2.1. How to do it with existing function was not obvious
to me. Would you whare an example?

> An Error-string is a percentage-encoded UTF-8 string, as specified
> in [RFC3986] section 2.1, that gives additional explanatory text
> about the cause of the error. This string is not significant to
> protocol operation and is intended only for display and logging
> purposes.


-- 
Emmanuel Dreyfus
manu@netbsd.org

Re: svn commit: r1907608 - in /httpd/httpd/trunk: CMakeLists.txt modules/dav/main/NWGNUmakefile modules/dav/main/config5.m4 modules/dav/main/mod_dav.c modules/dav/main/mod_dav.dsp modules/dav/main/mod_dav.h modules/dav/main/ms_wdv.c modules/dav/main/util.c

Posted by Ruediger Pluem <rp...@apache.org>.

On 2/13/23 5:48 PM, manu@apache.org wrote:
> Author: manu
> Date: Mon Feb 13 16:48:35 2023
> New Revision: 1907608
> 
> URL: http://svn.apache.org/viewvc?rev=1907608&view=rev
> Log:
> Add MS-WDV support
> 
> MS-WDV specification:
> https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wdv
> 
> The changes introduces the DAVMSext directive, which is used to 
> enable MS-WDV: DAVMSext +WDV
> 
> dav_get_timeout_string() is introduced as a variant of dav_get_timeout().
> The former parses a string, the later parse the Timeout HTTP header.
> 
> 
> Added:
>     httpd/httpd/trunk/modules/dav/main/ms_wdv.c
> Modified:
>     httpd/httpd/trunk/CMakeLists.txt
>     httpd/httpd/trunk/modules/dav/main/NWGNUmakefile
>     httpd/httpd/trunk/modules/dav/main/config5.m4
>     httpd/httpd/trunk/modules/dav/main/mod_dav.c
>     httpd/httpd/trunk/modules/dav/main/mod_dav.dsp
>     httpd/httpd/trunk/modules/dav/main/mod_dav.h
>     httpd/httpd/trunk/modules/dav/main/util.c
> 
> Modified: httpd/httpd/trunk/CMakeLists.txt
> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CMakeLists.txt?rev=1907608&r1=1907607&r2=1907608&view=diff
> ==============================================================================
> --- httpd/httpd/trunk/CMakeLists.txt (original)
> +++ httpd/httpd/trunk/CMakeLists.txt Mon Feb 13 16:48:35 2023
> @@ -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

I guess this should be main and not mail :-)

>  )
>  SET(mod_dav_install_lib 1)
>  SET(mod_dav_fs_extra_sources
> 


> Modified: httpd/httpd/trunk/modules/dav/main/mod_dav.c
> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/mod_dav.c?rev=1907608&r1=1907607&r2=1907608&view=diff
> ==============================================================================
> --- httpd/httpd/trunk/modules/dav/main/mod_dav.c (original)
> +++ httpd/httpd/trunk/modules/dav/main/mod_dav.c Mon Feb 13 16:48:35 2023
> @@ -76,6 +76,12 @@ enum {
>      DAV_ENABLED_ON
>  };
>  
> +typedef enum {
> +    DAV_MSEXT_NONE = 0,
> +    DAV_MSEXT_WDV = 1,
> +    DAV_MSEXT_ALL = 1,

Why not setting ALL to 1 and WDV to 2? This would allow future extensions to continue with 3, 4 ...
Looking at the code later, maybe a bit field would be even more appropriate as it easily allows
any kind of combination of future extensions.

Hence something like

dav_msext_opts being an unsigned int and

#define DAV_MSEXT_NONE 0
#define DAV_MSEXT_WDV     (1u << 0)
#define DAV_MSEXT_NEXTONE (1u << 1)
#define DAV_MSEXT_ALL      DAV_MSEXT_WDV | DAV_MSEXT_NEXTONE


See also server/core.c::set_options for processing stuff.

> +} dav_msext_opts;
> +
>  /* per-dir configuration */
>  typedef struct {
>      const char *provider_name;

> 
> Modified: httpd/httpd/trunk/modules/dav/main/mod_dav.h
> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/mod_dav.h?rev=1907608&r1=1907607&r2=1907608&view=diff
> ==============================================================================
> --- httpd/httpd/trunk/modules/dav/main/mod_dav.h (original)
> +++ httpd/httpd/trunk/modules/dav/main/mod_dav.h Mon Feb 13 16:48:35 2023
> @@ -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
> 
> Added: httpd/httpd/trunk/modules/dav/main/ms_wdv.c
> URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/dav/main/ms_wdv.c?rev=1907608&view=auto
> ==============================================================================
> --- httpd/httpd/trunk/modules/dav/main/ms_wdv.c (added)
> +++ httpd/httpd/trunk/modules/dav/main/ms_wdv.c Mon Feb 13 16:48:35 2023
> @@ -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;
> +}

Any reason why we cannot use an existing function like  ap_escape_path_segment here?

> +
> +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, &ltl,
> +                                                 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");

Provided a non file based dav provider can provide a content length why does it need to be a 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;
> +    }

I guess scanning the brigade from the end to the begining for an EOS bucket and doing an
apr_brigade_split_ex if found is more effective.

Regards

Rüdiger


Re: svn commit: r1907608 - in /httpd/httpd/trunk: CMakeLists.txt modules/dav/main/NWGNUmakefile modules/dav/main/config5.m4 modules/dav/main/mod_dav.c modules/dav/main/mod_dav.dsp modules/dav/main/mod_dav.h modules/dav/main/ms_wdv.c modules/dav/main/util.c

Posted by Christophe JAILLET <ch...@wanadoo.fr>.
Le 13/02/2023 à 17:48, manu@apache.org a écrit :
> Author: manu
> Date: Mon Feb 13 16:48:35 2023
> New Revision: 1907608
> 
> URL: http://svn.apache.org/viewvc?rev=1907608&view=rev
> Log:
> Add MS-WDV support
> 
> MS-WDV specification:
> https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wdv
> 
> The changes introduces the DAVMSext directive, which is used to
> enable MS-WDV: DAVMSext +WDV
> 
> dav_get_timeout_string() is introduced as a variant of dav_get_timeout().
> The former parses a string, the later parse the Timeout HTTP header.
> 
> 
> Added:
>      httpd/httpd/trunk/modules/dav/main/ms_wdv.c
> Modified:
>      httpd/httpd/trunk/CMakeLists.txt
>      httpd/httpd/trunk/modules/dav/main/NWGNUmakefile
>      httpd/httpd/trunk/modules/dav/main/config5.m4
>      httpd/httpd/trunk/modules/dav/main/mod_dav.c
>      httpd/httpd/trunk/modules/dav/main/mod_dav.dsp
>      httpd/httpd/trunk/modules/dav/main/mod_dav.h
>      httpd/httpd/trunk/modules/dav/main/util.c
> 

[...]

>   /*
> + * 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;

Hi, I think that:
     if (!ap_cstr_casecmp(w, "None") ||
         !ap_cstr_casecmp(w, "Off"))
         conf->msext_opts = DAV_MSEXT_NONE;

would be more readable. It is easier to see that both are the same.
But it is mostly a matter of taste :).

(same below when setting DAV_MSEXT_ALL)

> +    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;

is |= expected here?

> +    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;
> +}
> +


Re: svn commit: r1907608 - in /httpd/httpd/trunk: CMakeLists.txt modules/dav/main/NWGNUmakefile modules/dav/main/config5.m4 modules/dav/main/mod_dav.c modules/dav/main/mod_dav.dsp modules/dav/main/mod_dav.h modules/dav/main/ms_wdv.c modules/dav/main/util.c

Posted by Ruediger Pluem <rp...@apache.org>.

On 2/13/23 5:48 PM, manu@apache.org wrote:
> Author: manu
> Date: Mon Feb 13 16:48:35 2023
> New Revision: 1907608
> 
> URL: http://svn.apache.org/viewvc?rev=1907608&view=rev
> Log:
> Add MS-WDV support
> 
> MS-WDV specification:
> https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wdv
> 
> The changes introduces the DAVMSext directive, which is used to 
> enable MS-WDV: DAVMSext +WDV
> 
> dav_get_timeout_string() is introduced as a variant of dav_get_timeout().
> The former parses a string, the later parse the Timeout HTTP header.
> 
> 
> Added:
>     httpd/httpd/trunk/modules/dav/main/ms_wdv.c
> Modified:
>     httpd/httpd/trunk/CMakeLists.txt
>     httpd/httpd/trunk/modules/dav/main/NWGNUmakefile
>     httpd/httpd/trunk/modules/dav/main/config5.m4
>     httpd/httpd/trunk/modules/dav/main/mod_dav.c
>     httpd/httpd/trunk/modules/dav/main/mod_dav.dsp
>     httpd/httpd/trunk/modules/dav/main/mod_dav.h
>     httpd/httpd/trunk/modules/dav/main/util.c
> 

Noticed the below warnings when compiling:

ms_wdv.c: In function ‘mswdv_combined_lock’:
ms_wdv.c:496:55: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘time_t’ {aka ‘long int’} [-Wformat=]
                        apr_psprintf(r->pool, "Second-%d",
                                                      ~^
                                                      %ld
                                       newlock->timeout - time(NULL)));
                                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ms_wdv.c:183:9: warning: unused variable ‘locked_by_other’ [-Wunused-variable]
     int locked_by_other = 0;
         ^~~~~~~~~~~~~~~
ms_wdv.c:171:18: warning: unused variable ‘status’ [-Wunused-variable]
     apr_status_t status;
                  ^~~~~~
ms_wdv.c: In function ‘mswdv_combined_propfind’:
ms_wdv.c:523:17: warning: unused variable ‘b’ [-Wunused-variable]
     apr_bucket *b;
                 ^
ms_wdv.c: In function ‘mswdv_combined_proppatch’:
ms_wdv.c:605:9: warning: unused variable ‘ret’ [-Wunused-variable]
     int ret;
         ^~~
ms_wdv.c:604:18: warning: unused variable ‘rr’ [-Wunused-variable]
     request_rec *rr;
                  ^~
ms_wdv.c:603:18: warning: unused variable ‘f’ [-Wunused-variable]
     ap_filter_t *f;
                  ^
ms_wdv.c:602:16: warning: unused variable ‘proppatch_datalen’ [-Wunused-variable]
     apr_size_t proppatch_datalen;
                ^~~~~~~~~~~~~~~~~
ms_wdv.c:601:15: warning: unused variable ‘index’ [-Wunused-variable]
     apr_off_t index;
               ^~~~~
ms_wdv.c:596:11: warning: unused variable ‘proppach_data’ [-Wunused-variable]
     char *proppach_data;
           ^~~~~~~~~~~~~
ms_wdv.c:594:17: warning: unused variable ‘b’ [-Wunused-variable]
     apr_bucket *b;
                 ^
ms_wdv.c:592:25: warning: unused variable ‘bbsub’ [-Wunused-variable]
     apr_bucket_brigade *bbsub;
                         ^~~~~
ms_wdv.c:591:16: warning: unused variable ‘err’ [-Wunused-variable]
     dav_error *err = NULL;
                ^~~

Regards

Rüdiger