adding documentation

This commit is contained in:
DioCrafts
2025-04-04 01:48:55 +02:00
parent 01c81bfb1a
commit 868119e405
5 changed files with 2882 additions and 0 deletions
@@ -0,0 +1,340 @@
/**
* WebDAV Handler Module
*
* This module implements the WebDAV protocol (RFC 4918) endpoints for OxiCloud.
* It provides a complete WebDAV server implementation that allows clients to
* perform file operations over HTTP, including reading, writing, and manipulating
* files and directories.
*
* The WebDAV protocol extends HTTP to provide file system-like functionality, enabling:
* - File/folder listing (PROPFIND)
* - Creation of collections/directories (MKCOL)
* - Retrieving and updating resources (GET, PUT)
* - Moving and copying resources (MOVE, COPY)
* - Resource locking for concurrency control (LOCK, UNLOCK)
*
* This implementation leverages OxiCloud's existing file and folder services
* through the application's port interfaces.
*/
use std::sync::Arc;
use axum::{
Router,
routing::get,
extract::{Path, State, Request, Extension},
http::StatusCode,
response::Response,
};
use http::{Method, header};
use crate::common::di::AppState;
use crate::interfaces::middleware::auth::CurrentUser;
use crate::application::ports::file_ports::{FileRetrievalUseCase, FileUploadUseCase};
use crate::application::ports::folder_ports::FolderUseCase;
use crate::common::errors::AppError;
use crate::application::adapters::webdav_adapter::WebDavAdapter;
/**
* Creates and returns the WebDAV router with all required endpoints.
*
* This function sets up all WebDAV method handlers following RFC 4918,
* mapping HTTP methods to appropriate WebDAV operations.
*
* @return Router configured with WebDAV endpoints
*/
pub fn webdav_routes() -> Router<Arc<AppState>> {
Router::new()
// Standard HTTP methods used in WebDAV
.route("/webdav/*path", get(handle_get))
.route("/webdav/*path", axum::routing::head(handle_head))
.route("/webdav/*path", axum::routing::put(handle_put))
.route("/webdav/*path", axum::routing::delete(handle_delete))
// WebDAV-specific methods
.route_with_tsr("/webdav/*path", axum::routing::on(
Method::OPTIONS, handle_options,
Method::PROPFIND, handle_propfind,
Method::PROPPATCH, handle_proppatch,
Method::MKCOL, handle_mkcol,
Method::COPY, handle_copy,
Method::MOVE, handle_move,
Method::LOCK, handle_lock,
Method::UNLOCK, handle_unlock,
))
}
/**
* Handles OPTIONS requests to advertise WebDAV capabilities.
*
* This handler responds with the DAV header indicating WebDAV compliance
* level and the methods supported by this WebDAV server.
*
* @param state The application state containing service dependencies
* @param path The requested resource path
* @return HTTP response with appropriate WebDAV headers
*/
async fn handle_options(
State(_state): State<Arc<AppState>>,
Path(_path): Path<String>,
) -> Response {
Response::builder()
.status(StatusCode::OK)
.header(header::DAV, "1, 2") // Class 1 and 2 WebDAV support
.header(header::ALLOW, "OPTIONS, GET, HEAD, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK")
.body(axum::body::Body::empty())
.unwrap()
}
/**
* Handles PROPFIND requests to retrieve resource properties.
*
* This handler processes WebDAV PROPFIND requests, which are used to retrieve
* properties for one or more resources. It supports different depths (0, 1, infinity)
* and can return all properties or a specified subset based on the request.
*
* @param state The application state containing service dependencies
* @param user The authenticated user making the request
* @param path The requested resource path
* @param request The full HTTP request containing headers and body
* @return XML response with requested properties
*/
async fn handle_propfind(
State(state): State<Arc<AppState>>,
Extension(user): Extension<CurrentUser>,
Path(path): Path<String>,
request: Request<axum::body::Body>,
) -> Result<Response, AppError> {
// Extract depth header (0, 1, or infinity)
let depth = request
.headers()
.get(header::from_str("Depth").unwrap())
.and_then(|v| v.to_str().ok())
.unwrap_or("infinity");
// Read request body to determine which properties are requested
let body_bytes = hyper::body::to_bytes(request.into_body()).await
.map_err(|e| AppError::bad_request(format!("Failed to read request body: {}", e)))?;
// Use the adapter to parse the PROPFIND request
let prop_find_request = WebDavAdapter::parse_propfind(&body_bytes[..])
.map_err(|e| AppError::bad_request(format!("Invalid PROPFIND request: {}", e)))?;
// Determine if the path is a file or directory
let is_file = if path.ends_with('/') {
false
} else {
// Check if path exists as a file
let file_service = state.file_service.as_ref()
.ok_or_else(|| AppError::internal_error("File service not configured"))?;
match file_service.file_retrieval_service.get_file_by_path(&path, &user.id).await {
Ok(_) => true,
Err(_) => false,
}
};
if is_file {
// Handle PROPFIND for a file
let file_service = state.file_service.as_ref()
.ok_or_else(|| AppError::internal_error("File service not configured"))?;
let file = file_service.file_retrieval_service.get_file_by_path(&path, &user.id).await?;
// Generate XML response
let mut xml_buffer = Vec::new();
WebDavAdapter::generate_propfind_response_for_file(
&mut xml_buffer,
&file,
&prop_find_request,
depth,
&format!("/webdav/{}", path),
).map_err(|e| AppError::internal_error(format!("Failed to generate XML response: {}", e)))?;
// Return response with appropriate headers
Ok(Response::builder()
.status(StatusCode::MULTI_STATUS)
.header(header::CONTENT_TYPE, "application/xml; charset=utf-8")
.body(axum::body::Body::from(xml_buffer))
.unwrap())
} else {
// Handle PROPFIND for a directory
let folder_service = state.folder_service.as_ref()
.ok_or_else(|| AppError::internal_error("Folder service not configured"))?;
let folder_path = if path.ends_with('/') { path.clone() } else { format!("{}/", path) };
let folder = folder_service.get_folder_by_path(&folder_path, &user.id).await?;
// Fetch children if depth > 0
let (files, folders) = if depth == "0" {
(Vec::new(), Vec::new())
} else {
let files = file_service.file_retrieval_service.get_files_in_folder(
Some(&folder.id.to_string()),
&user.id,
).await?;
let folders = folder_service.get_subfolders(
Some(&folder.id.to_string()),
&user.id,
).await?;
(files, folders)
};
// Generate XML response
let mut xml_buffer = Vec::new();
WebDavAdapter::generate_propfind_response(
&mut xml_buffer,
Some(&folder),
&files,
&folders,
&prop_find_request,
depth,
&format!("/webdav/{}", folder_path),
).map_err(|e| AppError::internal_error(format!("Failed to generate XML response: {}", e)))?;
// Return response with appropriate headers
Ok(Response::builder()
.status(StatusCode::MULTI_STATUS)
.header(header::CONTENT_TYPE, "application/xml; charset=utf-8")
.body(axum::body::Body::from(xml_buffer))
.unwrap())
}
}
/**
* Handles GET requests to retrieve file contents.
*
* This handler streams file contents to the client with appropriate
* content type and other metadata headers.
*
* @param state The application state containing service dependencies
* @param user The authenticated user making the request
* @param path The requested file path
* @return Streaming response with file contents
*/
async fn handle_get(
State(state): State<Arc<AppState>>,
Extension(user): Extension<CurrentUser>,
Path(path): Path<String>,
) -> Result<Response, AppError> {
// Ensure this is a file request (not a directory)
if path.ends_with('/') {
return Err(AppError::bad_request("Cannot GET a directory"));
}
let file_service = state.file_service.as_ref()
.ok_or_else(|| AppError::internal_error("File service not configured"))?;
// Get file metadata
let file = file_service.file_retrieval_service.get_file_by_path(&path, &user.id).await?;
// Stream file content
let stream = file_service.file_retrieval_service.get_file_stream(&file.id, &user.id).await?;
// Return streamed response with appropriate headers
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, file.mime_type)
.header(header::CONTENT_LENGTH, file.size.to_string())
.header(header::ETAG, format!("\"{}\"", file.id))
.header(header::LAST_MODIFIED, file.updated_at.to_rfc2822())
.body(axum::body::Body::from_stream(stream))
.unwrap())
}
// Implement remaining WebDAV method handlers...
/**
* Handles HEAD requests to retrieve file metadata without content.
* Similar to GET but without returning the file body.
*/
async fn handle_head(
State(state): State<Arc<AppState>>,
Extension(user): Extension<CurrentUser>,
Path(path): Path<String>,
) -> Result<Response, AppError> {
// Implementation similar to handle_get but without body
// ...
todo!()
}
/**
* Handles PUT requests to create or update files.
* Streams the request body to create or replace a file.
*/
async fn handle_put(
// ...
) -> Result<Response, AppError> {
todo!()
}
/**
* Handles PROPPATCH requests to update resource properties.
* Processes property updates and removals.
*/
async fn handle_proppatch(
// ...
) -> Result<Response, AppError> {
todo!()
}
/**
* Handles MKCOL requests to create directories.
* Creates a new collection (directory) at the specified path.
*/
async fn handle_mkcol(
// ...
) -> Result<Response, AppError> {
todo!()
}
/**
* Handles DELETE requests to remove resources.
* Deletes the specified file or recursively deletes a directory.
*/
async fn handle_delete(
// ...
) -> Result<Response, AppError> {
todo!()
}
/**
* Handles COPY requests to duplicate resources.
* Copies a file or recursively copies a directory.
*/
async fn handle_copy(
// ...
) -> Result<Response, AppError> {
todo!()
}
/**
* Handles MOVE requests to relocate resources.
* Moves or renames a file or directory.
*/
async fn handle_move(
// ...
) -> Result<Response, AppError> {
todo!()
}
/**
* Handles LOCK requests for concurrency control.
* Locks a resource for exclusive access by a client.
*/
async fn handle_lock(
// ...
) -> Result<Response, AppError> {
todo!()
}
/**
* Handles UNLOCK requests to release locks.
* Releases a previously acquired lock on a resource.
*/
async fn handle_unlock(
// ...
) -> Result<Response, AppError> {
todo!()
}