Hello!

I've done first part of the job - there is now a FETCH method that works 
in that way:

FETCH /destination/path HTTP/1.1
Source: http://webdav.example.com/webdav-resource-to-fetch

It fetches resource http://webdav.example.com/webdav-resource-to-fetch 
(with GET method) and stores it in /destination/path just like in PUT 
method.

I've used mainly the PUT method code (it is unfortunately not re-usable, 
so i had to copy-paste-and-modify it..., maybe some refactoring needed?) 
and some code from mod_proxy.

It would be nice if someone made review of this code and tell me what is 
wrong and where this code can be enchanced.

Now I'll start to work on fetching whole collections, but I think it 
will be a lot harder that fetching simple resources.

Best regards
  Rafał Malinowski

----------------------------------------------------
DODA i Stachursky dźwigają muzę w Gdańsku, 
już w najbliższy weekend przyjdź na koncert. 
Kliknij po więcej: 
http://klik.wp.pl/?adr=http%3A%2F%2Fcorto.www.wp.pl%2Fas%2Fmuzyka_gdansk.html&sid=417
diff -bur httpd-2.2.9-orig/include/httpd.h httpd-2.2.9/include/httpd.h
--- httpd-2.2.9-orig/include/httpd.h	2008-05-27 23:43:39.000000000 +0200
+++ httpd-2.2.9/include/httpd.h	2008-07-13 13:55:55.000000000 +0200
@@ -601,7 +601,8 @@
 #define M_MKACTIVITY            23
 #define M_BASELINE_CONTROL      24
 #define M_MERGE                 25
-#define M_INVALID               26      /** RFC 3253: WebDAV Versioning */
+#define M_FETCH                 26
+#define M_INVALID               27      /** RFC 3253: WebDAV Versioning */
 
 /**
  * METHODS needs to be equal to the number of bits
diff -bur httpd-2.2.9-orig/modules/dav/main/mod_dav.c httpd-2.2.9/modules/dav/main/mod_dav.c
--- httpd-2.2.9-orig/modules/dav/main/mod_dav.c	2008-05-27 17:57:23.000000000 +0200
+++ httpd-2.2.9/modules/dav/main/mod_dav.c	2008-07-16 19:16:52.000000000 +0200
@@ -55,6 +55,7 @@
 #include "http_main.h"
 #include "http_protocol.h"
 #include "http_request.h"
+#include "util_ebcdic.h"
 #include "util_script.h"
 
 #include "mod_dav.h"
@@ -2550,6 +2551,442 @@
     return dav_created(r, NULL, "Collection", 0);
 }
 
+DAV_DECLARE(dav_error *) dav_fetch_create_socket(apr_pool_t *p, apr_sockaddr_t *addr, apr_socket_t **newsock)
+{
+	apr_status_t rv;
+
+	while (addr) {
+        if ((rv = apr_socket_create(newsock, addr->family,
+                                    SOCK_STREAM, 0, p)) != APR_SUCCESS) {
+            /*
+             * this could be an IPv6 address from the DNS but the
+             * local machine won't give us an IPv6 socket; hopefully the
+             * DNS returned an additional address to try
+             */
+            addr = addr->next;
+            continue;
+        }
+
+        rv = apr_socket_connect(*newsock, addr);
+
+        /* if an error occurred, loop round and try again */
+        if (rv != APR_SUCCESS) {
+            apr_socket_close(*newsock);
+            addr = addr->next;
+            continue;
+        }
+
+        return 0;
+	}
+
+	// TODO: good error code?
+	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Socket creating failed");
+}
+
+DAV_DECLARE(dav_error *) dav_fetch_create_connection(apr_pool_t *p, apr_uri_t *uri, server_rec *s, conn_rec **conn)
+{
+	apr_status_t rv;
+	apr_sockaddr_t *src_addr;
+	apr_socket_t *socket;
+	dav_error *err;
+
+	rv = apr_sockaddr_info_get(&src_addr, uri->hostname, APR_UNSPEC, uri->port, 0, p);
+    if (APR_SUCCESS != rv) {
+        return dav_new_error(p, HTTP_BAD_REQUEST, 0, apr_pstrcat(p, "DNS lookup failure for: ", uri->hostname, NULL));
+    }
+
+	err = dav_fetch_create_socket(p, src_addr, &socket);
+	if (NULL != err) {
+		return dav_push_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Socket creating failed", err);
+	}
+
+	*conn = (conn_rec *)ap_run_create_connection(p, s, socket, 0, NULL, apr_bucket_alloc_create(p));
+
+	if (!*conn) {
+		// will it have memory to create error message? i guess not, let the caller check!
+		return 0;
+	}
+
+    /* set up the connection filters */
+    rv = ap_run_pre_connection(*conn, socket);
+    if (rv != OK && rv != DONE) {
+        (*conn)->aborted = 1;
+        return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "pre_connection setup failed");
+    }
+
+	return 0;
+}
+
+DAV_DECLARE(dav_error *) dav_fetch_send_request(apr_pool_t *p, request_rec *r, apr_uri_t *uri)
+{
+    apr_status_t rv;
+    conn_rec *conn = r->connection;
+    apr_bucket_alloc_t *bucket_alloc = conn->bucket_alloc;
+    apr_bucket_brigade *request_brigade;
+    apr_bucket *e;
+	char *buf;
+
+	// as I see nobody in httpd check for NULL results of malloc/apr_palloc, so why I should?
+    request_brigade = apr_brigade_create(p, bucket_alloc);
+
+	buf = apr_pstrcat(p, "GET", " ", uri->path, " HTTP/1.1", CRLF, NULL);
+	e = apr_bucket_pool_create(buf, strlen(buf), p, bucket_alloc);
+	ap_xlate_proto_to_ascii(buf, strlen(buf));
+	APR_BRIGADE_INSERT_TAIL(request_brigade, e);
+
+	buf = apr_pstrcat(p, "Host: ", uri->hostname, CRLF, CRLF, NULL);
+	e = apr_bucket_pool_create(buf, strlen(buf), p, bucket_alloc);
+	ap_xlate_proto_to_ascii(buf, strlen(buf));
+	APR_BRIGADE_INSERT_TAIL(request_brigade, e);
+
+    e = apr_bucket_flush_create(bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(request_brigade, e);
+
+	rv = ap_pass_brigade(conn->output_filters, request_brigade);
+	apr_brigade_cleanup(request_brigade);
+
+	if (rv != APR_SUCCESS) {
+		return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Sending request failed");
+	}
+
+	return 0;
+}
+
+
+void dav_fetch_make_fake_request(conn_rec *conn, request_rec *r, request_rec **rp)
+{
+	(*rp) = apr_pcalloc(r->pool, sizeof(*r));
+
+    (*rp)->pool            = r->pool;
+    (*rp)->status          = HTTP_OK;
+
+    (*rp)->headers_in      = apr_table_make(r->pool, 25);
+    (*rp)->subprocess_env  = apr_table_make(r->pool, 25);
+//    *rp->headers_out     = apr_table_make(r->pool, 12);
+//    *rp->err_headers_out = apr_table_make(r->pool, 5);
+    (*rp)->notes           = apr_table_make(r->pool, 5);
+
+    (*rp)->server = r->server;
+    (*rp)->proxyreq = r->proxyreq;
+    (*rp)->request_time = r->request_time;
+    (*rp)->connection      = conn;
+    (*rp)->output_filters  = conn->output_filters;
+    (*rp)->input_filters   = conn->input_filters;
+    (*rp)->proto_output_filters  = conn->output_filters;
+    (*rp)->proto_input_filters   = conn->input_filters;
+
+    (*rp)->request_config = ap_create_request_config(r->pool);
+}
+
+DAV_DECLARE(dav_error *) dav_fetch_read_status_line(apr_pool_t *p, request_rec *r)
+{
+    conn_rec *conn = r->connection;
+    apr_bucket *e;
+    apr_bucket_brigade *response_brigade;
+
+    char buff[HUGE_STRING_LEN];
+	char *buff_p = buff;
+	char keepchar;
+	apr_size_t len;
+
+    response_brigade = apr_brigade_create(r->pool, conn->bucket_alloc);
+    ap_rgetline(&buff_p, sizeof(buff), &len, r, 0, response_brigade);
+
+    if (!apr_date_checkmask(buff, "HTTP/#.# ###*")) {
+    	return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Invalid response status line");
+    }
+
+    int major, minor;
+
+    if (2 != sscanf(buff, "HTTP/%u.%u", &major, &minor)) {
+        major = 1;
+        minor = 1;
+    }
+    else if ((buff[5] != '1') || (len >= sizeof(buff) - 1)) {
+        return dav_new_error(p, HTTP_INTERNAL_SERVER_ERROR, 0, "Invalid response status line");
+    }
+
+    keepchar = buff[12];
+    buff[12] = '\0';
+    r->status = atoi(&buff[9]);
+
+    if (keepchar != '\0') {
+     	buff[12] = keepchar;
+    } else {
+        /* 2616 requires the space in Status-Line; the origin
+         * server may have sent one but ap_rgetline_core will
+         * have stripped it. */
+    	buff[12] = ' ';
+        buff[13] = '\0';
+    }
+    r->status_line = apr_pstrdup(r->pool, &buff[9]);
+
+	// TODO: what with 'moved permanently' and so on?
+    if (r->status != 200) {
+		return dav_new_error(p, r->status, 0, "Response is not 200 OK");
+	}
+
+    return 0;
+}
+
+/* hande the FETCH method */
+static int dav_method_fetch(request_rec *r)
+{
+    dav_resource *resource;
+    int resource_state;
+    dav_auto_version_info av_info;
+    const dav_hooks_locks *locks_hooks = DAV_GET_HOOKS_LOCKS(r);
+    const char *body;
+    dav_error *err;
+    dav_error *err2;
+    dav_stream_mode mode;
+    dav_stream *stream;
+//    dav_response *multi_response;
+//    int has_range;
+//    apr_off_t range_start;
+//    apr_off_t range_end;
+	const char *src;
+	apr_uri_t src_uri;
+	apr_status_t status;
+	conn_rec *conn;
+    request_rec *rp;
+
+    /* get the source URI */
+    src = apr_table_get(r->headers_in, "Source");
+    if (src == NULL) {
+        /* This supplies additional information for the default message. */
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                      "The request is missing a Source header.", NULL);
+        return HTTP_BAD_REQUEST;
+    }
+
+    if (APR_SUCCESS != apr_uri_parse(r->pool, src, &src_uri)) {
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+                      "Source URI cannot be parsed.", NULL);
+    	return HTTP_BAD_REQUEST;
+    }
+
+    if (!src_uri.port) {
+    	src_uri.port = apr_uri_port_of_scheme(src_uri.scheme);
+    }
+
+    /* Ask repository module to resolve the resource */
+    err = dav_get_resource(r, 0 /* label_allowed */, 0 /* use_checked_in */,
+                           &resource);
+    if (err != NULL)
+        return dav_handle_err(r, err, NULL);
+
+    /* If not a file or collection resource, FETCH not allowed */
+    if (resource->type != DAV_RESOURCE_TYPE_REGULAR
+        && resource->type != DAV_RESOURCE_TYPE_WORKING) {
+        body = apr_psprintf(r->pool,
+                            "Cannot create resource %s with FETCH.",
+                            ap_escape_html(r->pool, r->uri));
+        return dav_error_response(r, HTTP_CONFLICT, body);
+    }
+
+    /* Cannot FETCH a collection */
+    if (resource->collection) {
+        return dav_error_response(r, HTTP_CONFLICT,
+                                  "Cannot FETCH to a collection.");
+
+    }
+
+    resource_state = dav_get_resource_state(r, resource);
+
+    /*
+     * Note: depth == 0 normally requires no multistatus response. However,
+     * if we pass DAV_VALIDATE_PARENT, then we could get an error on a URI
+     * other than the Request-URI, thereby requiring a multistatus.
+     *
+     * If the resource does not exist (DAV_RESOURCE_NULL), then we must
+     * check the resource *and* its parent. If the resource exists or is
+     * a locknull resource, then we check only the resource.
+     */
+//    if ((err = dav_validate_request(r, resource, 0, NULL, &multi_response,
+//                                    resource_state == DAV_RESOURCE_NULL ?
+//                                    DAV_VALIDATE_PARENT :
+//                                    DAV_VALIDATE_RESOURCE, NULL)) != NULL) {
+        /* ### add a higher-level description? */
+//        return dav_handle_err(r, err, multi_response);
+//    }
+
+    /* make sure the resource can be modified (if versioning repository) */
+    if ((err = dav_auto_checkout(r, resource,
+                                 0 /* not parent_only */,
+                                 &av_info)) != NULL) {
+        /* ### add a higher-level description? */
+        return dav_handle_err(r, err, NULL);
+    }
+
+    if ((err = dav_fetch_create_connection(r->pool, &src_uri, r->server, &conn)) != NULL) {
+        return dav_handle_err(r, err, NULL);
+    }
+
+    dav_fetch_make_fake_request(conn, r, &rp);
+
+    if ((err = dav_fetch_send_request(rp->pool, rp, &src_uri)) != NULL) {
+        return dav_handle_err(r, err, NULL);
+    }
+
+    if ((err = dav_fetch_read_status_line(rp->pool, rp)) != NULL) {
+    	return dav_handle_err(r, err, NULL);
+    }
+
+    ap_get_mime_headers(rp);
+
+    /* truncate and rewrite the file unless we see a Content-Range */
+    mode = DAV_MODE_WRITE_TRUNC;
+
+//    has_range = dav_parse_range(r, &range_start, &range_end);
+//    if (has_range) {
+//        mode = DAV_MODE_WRITE_SEEKABLE;
+//    }
+
+    /* Create the new file in the repository */
+    if ((err = (*resource->hooks->open_stream)(resource, mode,
+                                               &stream)) != NULL) {
+        /* ### assuming FORBIDDEN is probably not quite right... */
+        err = dav_push_error(r->pool, HTTP_FORBIDDEN, 0,
+                             apr_psprintf(r->pool,
+                                          "Unable to FETCH new contents for %s.",
+                                          ap_escape_html(r->pool, r->uri)),
+                             err);
+    }
+
+//    if (err == NULL && has_range) {
+        /* a range was provided. seek to the start */
+//        err = (*resource->hooks->seek_stream)(stream, range_start);
+//    }
+
+    if (err == NULL) {
+        apr_bucket_brigade *bb;
+        apr_bucket *b;
+        int seen_eos = 0;
+
+        bb = apr_brigade_create(rp->pool, conn->bucket_alloc);
+
+        do {
+            apr_status_t rc;
+
+            rc = ap_get_brigade(rp->input_filters, bb, AP_MODE_READBYTES,
+                                APR_BLOCK_READ, DAV_READ_BLOCKSIZE);
+
+            if (rc != APR_SUCCESS) {
+                err = dav_new_error(rp->pool, HTTP_INTERNAL_SERVER_ERROR, 0,
+                                    "Could not get next bucket brigade");
+                break;
+            }
+
+            for (b = APR_BRIGADE_FIRST(bb);
+                 b != APR_BRIGADE_SENTINEL(bb);
+                 b = APR_BUCKET_NEXT(b))
+            {
+                const char *data;
+                apr_size_t len;
+
+                if (APR_BUCKET_IS_EOS(b)) {
+                    seen_eos = 1;
+                    break;
+                }
+
+                if (APR_BUCKET_IS_METADATA(b)) {
+                    continue;
+                }
+
+                rc = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
+                if (rc != APR_SUCCESS) {
+                    err = dav_new_error(r->pool, HTTP_BAD_REQUEST, 0,
+                                        "An error occurred while reading "
+                                        "the request body.");
+                    break;
+                }
+
+                if (err == NULL) {
+                    /* write whatever we read, until we see an error */
+                    err = (*resource->hooks->write_stream)(stream, data, len);
+                }
+            }
+
+            apr_brigade_cleanup(bb);
+        } while (!seen_eos);
+
+        apr_brigade_destroy(bb);
+
+        err2 = (*resource->hooks->close_stream)(stream,
+                                                err == NULL /* commit */);
+        if (err2 != NULL && err == NULL) {
+            /* no error during the write, but we hit one at close. use it. */
+            err = err2;
+        }
+    }
+
+    /*
+     * Ensure that we think the resource exists now.
+     * ### eek. if an error occurred during the write and we did not commit,
+     * ### then the resource might NOT exist (e.g. dav_fs_repos.c)
+     */
+    if (err == NULL) {
+        resource->exists = 1;
+    }
+
+    /* restore modifiability of resources back to what they were */
+    err2 = dav_auto_checkin(r, resource, err != NULL /* undo if error */,
+                            0 /*unlock*/, &av_info);
+
+    /* check for errors now */
+    if (err != NULL) {
+        return dav_handle_err(r, err, NULL);
+    }
+
+    if (err2 != NULL) {
+        /* just log a warning */
+        err2 = dav_push_error(r->pool, err2->status, 0,
+                              "The FETCH was successful, but there "
+                              "was a problem automatically checking in "
+                              "the resource or its parent collection.",
+                              err2);
+        dav_log_err(r, err2, APLOG_WARNING);
+    }
+
+    /* ### place the Content-Type and Content-Language into the propdb */
+
+    if (locks_hooks != NULL) {
+        dav_lockdb *lockdb;
+
+        if ((err = (*locks_hooks->open_lockdb)(rp, 0, 0, &lockdb)) != NULL) {
+            /* The file creation was successful, but the locking failed. */
+            err = dav_push_error(rp->pool, err->status, 0,
+                                 "The file was PUT successfully, but there "
+                                 "was a problem opening the lock database "
+                                 "which prevents inheriting locks from the "
+                                 "parent resources.",
+                                 err);
+            return dav_handle_err(rp, err, NULL);
+        }
+
+        /* notify lock system that we have created/replaced a resource */
+        err = dav_notify_created(rp, lockdb, resource, resource_state, 0);
+
+        (*locks_hooks->close_lockdb)(lockdb);
+
+        if (err != NULL) {
+            /* The file creation was successful, but the locking failed. */
+            err = dav_push_error(rp->pool, err->status, 0,
+                                 "The file was PUT successfully, but there "
+                                 "was a problem updating its lock "
+                                 "information.",
+                                 err);
+            return dav_handle_err(r, err, NULL);
+        }
+    }
+
+    /* NOTE: WebDAV spec, S8.7.1 states properties should be unaffected */
+
+    /* return an appropriate response (HTTP_CREATED or HTTP_NO_CONTENT) */
+    return dav_created(r, NULL, "Resource", resource_state == DAV_RESOURCE_EXISTS);
+}
+
 /* handle the COPY and MOVE methods */
 static int dav_method_copymove(request_rec *r, int is_move)
 {
@@ -4605,7 +5042,8 @@
         | (AP_METHOD_BIT << M_MKCOL)
         | (AP_METHOD_BIT << M_MOVE)
         | (AP_METHOD_BIT << M_PROPFIND)
-        | (AP_METHOD_BIT << M_PROPPATCH);
+        | (AP_METHOD_BIT << M_PROPPATCH)
+		| (AP_METHOD_BIT << M_FETCH);
 
     /*
      * These are methods that we don't handle directly, but let the
@@ -4665,6 +5103,10 @@
         return dav_method_copymove(r, DAV_DO_MOVE);
     }
 
+	if (r->method_number == M_FETCH) {
+		return dav_method_fetch(r);
+	}
+
     if (r->method_number == M_LOCK) {
         return dav_method_lock(r);
     }
diff -bur httpd-2.2.9-orig/modules/http/http_protocol.c httpd-2.2.9/modules/http/http_protocol.c
--- httpd-2.2.9-orig/modules/http/http_protocol.c	2007-12-14 14:46:43.000000000 +0100
+++ httpd-2.2.9/modules/http/http_protocol.c	2008-07-13 14:09:35.000000000 +0200
@@ -449,6 +449,7 @@
     register_one_method(p, "MKACTIVITY", M_MKACTIVITY);
     register_one_method(p, "BASELINE-CONTROL", M_BASELINE_CONTROL);
     register_one_method(p, "MERGE", M_MERGE);
+    register_one_method(p, "FETCH", M_FETCH);
 }
 
 AP_DECLARE(int) ap_method_register(apr_pool_t *p, const char *methname)
@@ -569,6 +570,9 @@
         case 'A':
             return (memcmp(method, "TRACE", 5) == 0
                     ? M_TRACE : UNKNOWN_METHOD);
+        case 'F':
+            return (memcmp(method, "FETCH", 5) == 0
+            		? M_FETCH : UNKNOWN_METHOD);
         default:
             return UNKNOWN_METHOD;
         }
diff -bur httpd-2.2.9-orig/modules/proxy/ajp_header.c httpd-2.2.9/modules/proxy/ajp_header.c
--- httpd-2.2.9-orig/modules/proxy/ajp_header.c	2007-09-18 15:34:25.000000000 +0200
+++ httpd-2.2.9/modules/proxy/ajp_header.c	2008-07-13 13:58:45.000000000 +0200
@@ -159,6 +159,7 @@
     SC_M_MKACTIVITY,
     SC_M_BASELINE_CONTROL,
     SC_M_MERGE,
+    0,                      /* M_FETCH */
     0                       /* M_INVALID */
 };
 

Reply via email to