Преглед на файлове

WebDAV for Windows Explorer (Step 1/?)

bel2125 преди 3 години
родител
ревизия
c943c2b4fc
променени са 1 файла, в които са добавени 504 реда и са изтрити 34 реда
  1. 504 34
      src/civetweb.c

+ 504 - 34
src/civetweb.c

@@ -2214,7 +2214,7 @@ struct mg_handler_info {
 	/* handler type */
 	int handler_type;
 
-	/* Handler for http/https or authorization requests. */
+	/* Handler for http/https or requests. */
 	mg_request_handler handler;
 	unsigned int refcount;
 	int removing;
@@ -7377,9 +7377,14 @@ is_put_or_delete_method(const struct mg_connection *conn)
 {
 	if (conn) {
 		const char *s = conn->request_info.request_method;
-		return (s != NULL)
-		       && (!strcmp(s, "PUT") || !strcmp(s, "DELETE")
-		           || !strcmp(s, "MKCOL") || !strcmp(s, "PATCH"));
+		if (s != NULL) {
+			/* PUT, DELETE, MKCOL, PATCH, LOCK, UNLOCK, PROPPATCH, MOVE, COPY */
+			return (!strcmp(s, "PUT") || !strcmp(s, "DELETE")
+			        || !strcmp(s, "MKCOL") || !strcmp(s, "PATCH")
+			        || !strcmp(s, "LOCK") || !strcmp(s, "UNLOCK")
+			        || !strcmp(s, "PROPPATCH") || !strcmp(s, "MOVE")
+			        || !strcmp(s, "COPY"));
+		}
 	}
 	return 0;
 }
@@ -10637,9 +10642,10 @@ static const struct mg_http_method_info http_methods[] = {
     {"LOCK", 1, 1, 0, 0, 0},
     {"UNLOCK", 1, 0, 0, 0, 0},
     {"PROPPATCH", 1, 1, 0, 0, 0},
+    {"COPY", 1, 0, 0, 0, 0},
+    {"MOVE", 1, 1, 0, 0, 0},
 
     /* Unsupported WEBDAV Methods: */
-    /* COPY, MOVE (RFC 2518) */
     /* + 11 methods from RFC 3253 */
     /* ORDERPATCH (RFC 3648) */
     /* ACL (RFC 3744) */
@@ -11692,7 +11698,7 @@ done:
 
 #if !defined(NO_FILES)
 static void
-mkcol(struct mg_connection *conn, const char *path)
+dav_mkcol(struct mg_connection *conn, const char *path)
 {
 	int rc, body_len;
 	struct de de;
@@ -11730,9 +11736,7 @@ mkcol(struct mg_connection *conn, const char *path)
 	}
 
 	rc = mg_mkdir(conn, path, 0755);
-
 	if (rc == 0) {
-
 		/* Create 201 "Created" response */
 		mg_response_header_start(conn, 201);
 		send_static_cache_header(conn);
@@ -11741,21 +11745,170 @@ mkcol(struct mg_connection *conn, const char *path)
 
 		/* Send all headers - there is no body */
 		mg_response_header_send(conn);
-
 	} else {
-		if (errno == EEXIST) {
-			mg_send_http_error(
-			    conn, 405, "Error: mkcol(%s): %s", path, strerror(ERRNO));
-		} else if (errno == EACCES) {
-			mg_send_http_error(
-			    conn, 403, "Error: mkcol(%s): %s", path, strerror(ERRNO));
-		} else if (errno == ENOENT) {
-			mg_send_http_error(
-			    conn, 409, "Error: mkcol(%s): %s", path, strerror(ERRNO));
+		int http_status = 500;
+		switch (errno) {
+		case EEXIST:
+			http_status = 405;
+			break;
+		case EACCES:
+			http_status = 403;
+			break;
+		case ENOENT:
+			http_status = 409;
+			break;
+		}
+
+		mg_send_http_error(conn,
+		                   http_status,
+		                   "Error processing %s: %s",
+		                   path,
+		                   strerror(ERRNO));
+	}
+}
+
+
+/* Forward decrlaration */
+static int get_uri_type(const char *uri);
+static const char *
+get_rel_url_at_current_server(const char *uri,
+                              const struct mg_connection *conn);
+
+
+static void
+dav_move_file(struct mg_connection *conn, const char *path, int do_copy)
+{
+	const char *overwrite;
+	const char *destination;
+	const char *root;
+	int rc, dest_uri_type;
+	int http_status = 400;
+	int do_override = 0;
+	int destination_ok = 0;
+	char dest_path[UTF8_PATH_MAX];
+
+	if (conn == NULL) {
+		return;
+	}
+
+	root = conn->dom_ctx->config[DOCUMENT_ROOT];
+	overwrite = mg_get_header(conn, "Overwrite");
+	destination = mg_get_header(conn, "Destination");
+	if ((overwrite != NULL) && (toupper(overwrite[0]) == 'T')) {
+		do_override = 1;
+	}
+
+	if ((destination == NULL) || (destination[0] == 0)) {
+		mg_send_http_error(conn, 400, "%s", "Missing destination");
+		return;
+	}
+
+	if (root != NULL) {
+		char *local_dest = NULL;
+		dest_uri_type = get_uri_type(destination);
+		if (dest_uri_type == 2) {
+			local_dest = mg_strdup_ctx(destination, conn->phys_ctx);
+		} else if ((dest_uri_type == 3) || (dest_uri_type == 4)) {
+			const char *h = get_rel_url_at_current_server(destination, conn);
+			if (h) {
+				local_dest = mg_strdup_ctx(h, conn->phys_ctx);
+			}
+		}
+		if (local_dest != NULL) {
+			remove_dot_segments(local_dest);
+			if (local_dest[0] == '/') {
+				int trunc_check = 0;
+				mg_snprintf(conn,
+				            &trunc_check,
+				            dest_path,
+				            sizeof(dest_path),
+				            "%s/%s",
+				            root,
+				            local_dest);
+				if (trunc_check == 0) {
+					destination_ok = 1;
+				}
+			}
+			mg_free(local_dest);
+		}
+	}
+
+	if (!destination_ok) {
+		mg_send_http_error(conn, 502, "%s", "Illegal destination");
+		return;
+	}
+
+
+#if defined(_WIN32)
+	{
+		wchar_t wSource[UTF16_PATH_MAX];
+		wchar_t wDest[UTF16_PATH_MAX];
+		BOOL ok;
+
+		path_to_unicode(conn, path, wSource, ARRAY_SIZE(wSource));
+		path_to_unicode(conn, dest_path, wDest, ARRAY_SIZE(wDest));
+		if (do_copy) {
+			ok = CopyFileW(wSource, wDest, do_override ? FALSE : TRUE);
 		} else {
-			mg_send_http_error(
-			    conn, 500, "fopen(%s): %s", path, strerror(ERRNO));
+			ok = MoveFileExW(wSource,
+			                 wDest,
+			                 do_override ? MOVEFILE_REPLACE_EXISTING : 0);
 		}
+		if (ok) {
+			rc = 0;
+		} else {
+			DWORD lastErr = GetLastError();
+			if (lastErr == ERROR_ALREADY_EXISTS) {
+				http_status = 412;
+			}
+			rc = -1;
+		}
+	}
+
+#else
+
+	if (do_copy) {
+		/* TODO: COPY for Linux. */
+		mg_send_http_error(conn, 403, "%s", "COPY forbidden");
+		return;
+	}
+
+	/* Linux is already UTF-8 */
+	rc = rename(path, dest_path);
+	if (rc) {
+		switch (errno) {
+		case EEXIST:
+			http_status = 412;
+			break;
+		case EACCES:
+			http_status = 403;
+			break;
+		case ENOENT:
+			http_status = 409;
+			break;
+		}
+	}
+#endif
+
+	if (rc == 0) {
+		/* Create 201 "Created" response */
+		mg_response_header_start(conn, 201);
+		send_static_cache_header(conn);
+		send_additional_header(conn);
+		mg_response_header_add(conn, "Content-Length", "0", -1);
+
+		/* Send all headers - there is no body */
+		mg_response_header_send(conn);
+	} else if (http_status == 412) {
+		mg_send_http_error(conn,
+		                   412,
+		                   "Destination already exists: %s",
+		                   dest_path);
+	} else {
+		mg_send_http_error(conn,
+		                   http_status,
+		                   "Operation failed with code %i",
+		                   rc);
 	}
 }
 
@@ -12240,6 +12393,9 @@ handle_ssi_file_request(struct mg_connection *conn,
 static void
 send_options(struct mg_connection *conn)
 {
+	int i;
+	char methods[256] = {0};
+
 	if (!conn) {
 		return;
 	}
@@ -12250,13 +12406,19 @@ send_options(struct mg_connection *conn)
 
 	mg_response_header_start(conn, 200);
 	mg_response_header_add(conn, "Content-Type", "text/html", -1);
+
+	for (i = 0; http_methods[i].name != NULL; i++) {
+		if (i > 0) {
+			strcat(methods, ", ");
+			strcat(methods, http_methods[i].name);
+		} else {
+			strcpy(methods, http_methods[i].name);
+		}
+	}
+
 	if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) {
 		/* Use the same as before */
-		mg_response_header_add(
-		    conn,
-		    "Allow",
-		    "GET, POST, HEAD, CONNECT, PUT, DELETE, OPTIONS, PROPFIND, MKCOL",
-		    -1);
+		mg_response_header_add(conn, "Allow", methods, -1);
 		mg_response_header_add(conn, "DAV", "1", -1);
 	} else {
 		/* TODO: Check this later for HTTP/2 */
@@ -12339,28 +12501,40 @@ print_dav_dir_entry(struct de *de, void *data)
 }
 
 
+#define NUM_WEBDAV_LOCKS 10
+uint64_t dav_lock_counter = 0;
+struct tdav_lock {
+	uint64_t counter;
+	char token[33];
+	char path[UTF8_PATH_MAX * 2];
+	char user[UTF8_PATH_MAX * 2];
+} dav_lock[NUM_WEBDAV_LOCKS];
+
+
 static void
 handle_propfind(struct mg_connection *conn,
                 const char *path,
                 struct mg_file_stat *filep)
 {
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
 	const char *depth = mg_get_header(conn, "Depth");
-	char date[64];
-	time_t curtime = time(NULL);
-
-	gmt_time_string(date, sizeof(date), &curtime);
+	int i;
 
 	if (!conn || !path || !filep || !conn->dom_ctx) {
 		return;
 	}
 
-	conn->must_close = 1;
+	mg_get_request_link(conn, link_buf, sizeof(link_buf));
 
 	/* return 207 "Multi-Status" */
+	conn->must_close = 1;
 	mg_response_header_start(conn, 207);
 	send_static_cache_header(conn);
 	send_additional_header(conn);
-	mg_response_header_add(conn, "Content-Type", "text/xml; charset=utf-8", -1);
+	mg_response_header_add(conn,
+	                       "Content-Type",
+	                       "application/xml; charset=utf-8",
+	                       -1);
 	mg_response_header_send(conn);
 
 	/* Content */
@@ -12380,8 +12554,239 @@ handle_propfind(struct mg_connection *conn,
 		scan_directory(conn, path, conn, &print_dav_dir_entry);
 	}
 
+	/* add lock discovery data */
+	if (!filep->is_directory) {
+		mg_printf(conn,
+		          "<d:response>"
+		          "<d:href>%s</d:href>"
+		          "<d:propstat>"
+		          "<d:prop>"
+		          "<d:lockdiscovery>\n",
+		          link_buf);
+
+		for (i = 0; i < NUM_WEBDAV_LOCKS; i++) {
+			if (!strcmp(dav_lock[i].path, link_buf)) {
+				mg_printf(conn,
+				          "<d:activelock>"
+				          "<d:locktype><d:write/></d:locktype>"
+				          "<d:lockscope><d:exclusive/></d:lockscope>"
+				          "<d:depth>0</d:depth>"
+				          "<d:owner>%s</d:owner>"
+				          "<d:timeout>Second-60</d:timeout>"
+				          "<d:locktoken>"
+				          "<d:href>%s</d:href>"
+				          "</d:locktoken>"
+				          "</d:activelock>\n",
+				          dav_lock[i].user,
+				          dav_lock[i].token);
+			}
+		}
+
+		mg_printf(conn,
+		          "</d:lockdiscovery>"
+		          "</d:prop>"
+		          "<d:status>HTTP/1.1 200 OK</d:status>"
+		          "</d:propstat>"
+		          "</d:response>\n");
+	}
+
 	mg_printf(conn, "%s\n", "</d:multistatus>");
 }
+
+
+static void
+dav_lock_file(struct mg_connection *conn, const char *path)
+{
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
+	int lock_index = -1;
+	int i;
+	if (!conn || !path || !conn->dom_ctx || !conn->request_info.remote_user) {
+		return;
+	}
+	mg_get_request_link(conn, link_buf, sizeof(link_buf));
+
+	/* const char *refresh = mg_get_header(conn, "If"); */
+	/* Link refresh should have an "If" header:
+	 * http://www.webdav.org/specs/rfc2518.html#n-example---refreshing-a-write-lock
+	 * But it seems Windows Explorer does not send them.
+	 */
+
+	mg_lock_context(conn->phys_ctx);
+	/* find existing lock */
+	for (i = 0; i < NUM_WEBDAV_LOCKS; i++) {
+		if (!strcmp(dav_lock[i].path, link_buf)) {
+			if (!strcmp(conn->request_info.remote_user, dav_lock[i].user)) {
+				/* locked by the same user */
+				dav_lock_counter++;
+				dav_lock[i].counter = dav_lock_counter;
+				lock_index = i;
+				break;
+			} else {
+				/* already locked by someone else */
+				mg_unlock_context(conn->phys_ctx);
+				mg_send_http_error(conn, 423, "%s", "Already locked");
+				return;
+			}
+		}
+	}
+
+	/* create new lock token */
+	for (i = 0; i < NUM_WEBDAV_LOCKS; i++) {
+		if (dav_lock[i].path[0] == 0) {
+			char s[32];
+			dav_lock_counter++;
+			dav_lock[i].counter = dav_lock_counter;
+			sprintf(s, "%" UINT64_FMT, (uint64_t)dav_lock_counter);
+			mg_md5(dav_lock[i].token,
+			       link_buf,
+			       "\x01",
+			       s,
+			       "\x01",
+			       conn->request_info.remote_user,
+			       NULL);
+			strncpy(dav_lock[i].path, link_buf, sizeof(dav_lock[i].path) - 1);
+			strncpy(dav_lock[i].user,
+			        conn->request_info.remote_user,
+			        sizeof(dav_lock[i].user) - 1);
+			lock_index = i;
+			break;
+		}
+	}
+	if (lock_index < 0) {
+		char s[32];
+		/* too many locks. Find oldest lock */
+		uint64_t oldest_counter = dav_lock[0].counter;
+		lock_index = 0;
+		for (i = 1; i < NUM_WEBDAV_LOCKS; i++) {
+			if (dav_lock[i].counter < oldest_counter) {
+				oldest_counter = dav_lock[i].counter;
+				lock_index = i;
+			}
+		}
+		/* invalidate oldest lock */
+		dav_lock[lock_index].path[0] = 0;
+
+		/* Reuse it (TODO: remove code duplication) */
+		dav_lock_counter++;
+		dav_lock[lock_index].counter = dav_lock_counter;
+		sprintf(s, "%" UINT64_FMT, (uint64_t)dav_lock_counter);
+		mg_md5(dav_lock[lock_index].token,
+		       link_buf,
+		       "\x01",
+		       s,
+		       "\x01",
+		       conn->request_info.remote_user,
+		       NULL);
+		strncpy(dav_lock[lock_index].path,
+		        link_buf,
+		        sizeof(dav_lock[lock_index].path) - 1);
+		strncpy(dav_lock[lock_index].user,
+		        conn->request_info.remote_user,
+		        sizeof(dav_lock[lock_index].user) - 1);
+	}
+	mg_unlock_context(conn->phys_ctx);
+
+	/* return 200 "OK" */
+	conn->must_close = 1;
+	mg_response_header_start(conn, 200);
+	send_static_cache_header(conn);
+	send_additional_header(conn);
+	mg_response_header_add(conn,
+	                       "Content-Type",
+	                       "application/xml; charset=utf-8",
+	                       -1);
+	mg_response_header_add(conn, "Lock-Token", dav_lock[lock_index].token, -1);
+	mg_response_header_send(conn);
+
+	/* Content */
+	mg_printf(conn,
+	          "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+	          "<d:prop xmlns:d=\"DAV:\">\n"
+	          "     <d:lockdiscovery>\n"
+	          "       <d:activelock>\n"
+	          "         <d:lockscope><d:exclusive/></d:lockscope>\n"
+	          "         <d:locktype><d:write/></d:locktype>\n"
+	          "         <d:owner>\n"
+	          "           <d:href>user</d:href>\n"
+	          "         </d:owner>\n"
+	          "         <d:timeout>Second-60</d:timeout>\n"
+	          "         <d:locktoken><d:href>%s</d:href></d:locktoken>\n"
+	          "         <d:lockroot>\n"
+	          "           <d:href>%s</d:href>\n"
+	          "         </d:lockroot>\n"
+	          "       </d:activelock>\n"
+	          "     </d:lockdiscovery>\n"
+	          "   </d:prop>\n",
+	          dav_lock[lock_index].token,
+	          dav_lock[lock_index].path);
+}
+
+
+static void
+dav_unlock_file(struct mg_connection *conn, const char *path)
+{
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
+	int lock_index;
+	if (!conn || !path || !conn->dom_ctx || !conn->request_info.remote_user) {
+		return;
+	}
+	mg_get_request_link(conn, link_buf, sizeof(link_buf));
+
+	mg_lock_context(conn->phys_ctx);
+	/* find existing lock */
+	for (lock_index = 0; lock_index < NUM_WEBDAV_LOCKS; lock_index++) {
+		if (!strcmp(dav_lock[lock_index].path, link_buf)) {
+			/* Success: return 204 "No Content" */
+			mg_unlock_context(conn->phys_ctx);
+			conn->must_close = 1;
+			mg_response_header_start(conn, 204);
+			mg_response_header_send(conn);
+			return;
+		}
+	}
+	mg_unlock_context(conn->phys_ctx);
+
+	/* Error: Cannot unlock a resource that is not locked */
+	mg_send_http_error(conn, 423, "%s", "Lock not found");
+}
+
+
+static void
+dav_proppatch(struct mg_connection *conn, const char *path)
+{
+	char link_buf[UTF8_PATH_MAX * 2]; /* Path + server root */
+
+	if (!conn || !path || !conn->dom_ctx) {
+		return;
+	}
+
+	/* return 207 "Multi-Status" */
+	conn->must_close = 1;
+	mg_response_header_start(conn, 207);
+	send_static_cache_header(conn);
+	send_additional_header(conn);
+	mg_response_header_add(conn,
+	                       "Content-Type",
+	                       "application/xml; charset=utf-8",
+	                       -1);
+	mg_response_header_send(conn);
+
+
+	mg_get_request_link(conn, link_buf, sizeof(link_buf));
+
+	/* Content */
+	mg_printf(conn,
+	          "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+	          "<d:multistatus xmlns:d='DAV:'>\n"
+	          "<d:response>\n<d:href>%s</d:href>\n",
+	          link_buf);
+	mg_printf(conn,
+	          "<d:propstat><d:status>HTTP/1.1 403 "
+	          "Forbidden</d:status></d:propstat>\n");
+	mg_printf(conn, "%s\n", "</d:response></d:multistatus>");
+}
+
+
 #endif
 
 void
@@ -14151,7 +14556,7 @@ handle_request(struct mg_connection *conn)
 	ri->local_uri = tmp;
 
 	/* step 1. completed, the url is known now */
-	DEBUG_TRACE("URL: %s", ri->local_uri);
+	DEBUG_TRACE("REQUEST: %s %s", ri->request_method, ri->local_uri);
 
 	/* 2. if this ip has limited speed, set it for this connection */
 	conn->throttle = set_throttle(conn->dom_ctx->config[THROTTLE],
@@ -14507,10 +14912,34 @@ handle_request(struct mg_connection *conn)
 		}
 		/* 11.3. MKCOL method */
 		if (!strcmp(ri->request_method, "MKCOL")) {
-			mkcol(conn, path);
+			dav_mkcol(conn, path);
+			return;
+		}
+		/* 11.4. MOVE method */
+		if (!strcmp(ri->request_method, "MOVE")) {
+			dav_move_file(conn, path, 0);
+			return;
+		}
+		if (!strcmp(ri->request_method, "COPY")) {
+			dav_move_file(conn, path, 1);
+			return;
+		}
+		/* 11.5. LOCK method */
+		if (!strcmp(ri->request_method, "LOCK")) {
+			dav_lock_file(conn, path);
+			return;
+		}
+		/* 11.6. UNLOCK method */
+		if (!strcmp(ri->request_method, "UNLOCK")) {
+			dav_unlock_file(conn, path);
 			return;
 		}
-		/* 11.4. PATCH method
+		/* 11.7. PROPPATCH method */
+		if (!strcmp(ri->request_method, "PROPPATCH")) {
+			dav_proppatch(conn, path);
+			return;
+		}
+		/* 11.8. Other methods, e.g.: PATCH
 		 * This method is not supported for static resources,
 		 * only for scripts (Lua, CGI) and callbacks. */
 		mg_send_http_error(conn,
@@ -21415,6 +21844,47 @@ mg_get_connection_info(const struct mg_context *ctx,
 
 	return (int)connection_info_length;
 }
+
+#if 0
+/* Get handler information. It can be printed or stored by the caller.
+ * Return the size of available information. */
+int
+mg_get_handler_info(struct mg_context *ctx,
+                       char *buffer,
+                       int buflen)
+{
+    int handler_info_len = 0;
+    struct mg_handler_info *tmp_rh;
+    mg_lock_context(ctx);
+
+    for (tmp_rh = ctx->dd.handlers; tmp_rh != NULL; tmp_rh = tmp_rh->next) {
+                
+        if (buflen > handler_info_len+ tmp_rh->uri_len) {
+        memcpy(buffer+handler_info_len, tmp_rh->uri, tmp_rh->uri_len);
+        }
+        handler_info_len += tmp_rh->uri_len;
+        
+        switch (tmp_rh->handler_type) {
+            case REQUEST_HANDLER:
+                (void)tmp_rh->handler;
+            break;
+            case WEBSOCKET_HANDLER:
+        (void)tmp_rh->connect_handler;
+       (void) tmp_rh->ready_handler;
+       (void) tmp_rh->data_handler;
+       (void) tmp_rh->close_handler; 
+            break;
+            case AUTH_HANDLER:
+             (void) tmp_rh->auth_handler; 
+            break;
+        }      
+        (void)cbdata;
+    }
+
+    mg_unlock_context(ctx);
+    return handler_info_len;
+}
+#endif
 #endif