From ab4960c79cdc1e8d46fbb1b8218e87e5de1d3f67 Mon Sep 17 00:00:00 2001 From: Florian THIERRY Date: Mon, 13 Jul 2026 22:52:25 +0200 Subject: [PATCH] Test --- backend/application/src/lib.rs | 1 + backend/application/src/video/api.rs | 99 ++++++++++++++++++++++++ backend/application/src/video/mod.rs | 1 + backend/application/src/video/service.rs | 73 +++++++++++++++++ backend/domain/src/lib.rs | 3 +- backend/domain/src/video/mod.rs | 1 + backend/domain/src/video/model.rs | 4 + 7 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 backend/application/src/video/api.rs create mode 100644 backend/application/src/video/mod.rs create mode 100644 backend/application/src/video/service.rs create mode 100644 backend/domain/src/video/mod.rs create mode 100644 backend/domain/src/video/model.rs diff --git a/backend/application/src/lib.rs b/backend/application/src/lib.rs index 2cd9f52..d82a933 100644 --- a/backend/application/src/lib.rs +++ b/backend/application/src/lib.rs @@ -1,2 +1,3 @@ pub mod configuration; pub mod user; +pub mod video; \ No newline at end of file diff --git a/backend/application/src/video/api.rs b/backend/application/src/video/api.rs new file mode 100644 index 0000000..2d41d87 --- /dev/null +++ b/backend/application/src/video/api.rs @@ -0,0 +1,99 @@ +// Video API endpoints for streaming video files via HTTP + +use std::fs::File; +use std::io::Read; +use std::path::Path; +use axum::{ + body::StreamBody, + extract::Path, + http::{header, HeaderValue, Response, StatusCode}, + response::IntoResponse, +}; +use tokio_util::io::ReaderStream; + +/// Video streaming endpoint handler +/// Returns video content with appropriate Content-Type header +pub async fn stream_video( + Path(video_path): Path, +) -> Result>>, StatusCode> { + // Validate and sanitize the path to prevent directory traversal + let full_path = sanitize_and_resolve_path(&video_path)?; + + // Open the file + let file = File::open(&full_path) + .map_err(|_| StatusCode::NOT_FOUND)?; + + // Determine content type based on file extension + let content_type = get_content_type(&full_path); + + // Create a stream from the file + let stream = ReaderStream::new(file); + let body = StreamBody::new(stream); + + // Build response with appropriate headers + let mut response = Response::builder() + .header(header::CONTENT_TYPE, content_type) + .header(header::ACCEPT_RANGES, "bytes") + .header( + header::CONTENT_DISPOSITION, + format!("inline; filename="{}"", full_path.file_name().unwrap_or_default().to_string_lossy()), + ) + .body(body) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(response) +} + +/// Sanitize and resolve the video path to prevent directory traversal attacks +fn sanitize_and_resolve_path(video_path: &str) -> Result { + let base_dir = Path::new("/opt/lumière/files"); // Your root folder from .env + + if !base_dir.exists() { + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + + let requested_path = Path::new(video_path); + + // Normalize the path and check it's within base_dir + let full_path = base_dir.join(requested_path); + let full_path = full_path.canonicalize() + .map_err(|_| StatusCode::BAD_REQUEST)?; + + // Security check: ensure the final path is still within base_dir + if !full_path.starts_with(base_dir) { + return Err(StatusCode::FORBIDDEN); + } + + Ok(full_path) +} + +/// Determine Content-Type based on file extension +fn get_content_type(path: &Path) -> &'static str { + match path.extension().and_then(|ext| ext.to_str()) { + Some("avi") => "video/x-msvideo", + Some("mkv") | Some("mp4") | Some("mov") | Some("webm") => "video/mp4", // Generic video type + _ => "application/octet-stream", // Fallback for unknown types + } +} + +/// Register video routes +pub fn register_video_routes(app: axum::Router) -> axum::Router { + app.route( + "/videos/:video_path*", + axum::routing::get(stream_video), + ) +} + +// Example usage in main.rs: +/* +pub async fn main() { + let app = axum::Router::new() + .nest("/api/v1", register_video_routes(axum::Router::new())); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:3000") + .await + .unwrap(); + + axum::serve(listener, app).await.unwrap(); +} +*/ \ No newline at end of file diff --git a/backend/application/src/video/mod.rs b/backend/application/src/video/mod.rs new file mode 100644 index 0000000..cc097b5 --- /dev/null +++ b/backend/application/src/video/mod.rs @@ -0,0 +1 @@ +pub mod service; \ No newline at end of file diff --git a/backend/application/src/video/service.rs b/backend/application/src/video/service.rs new file mode 100644 index 0000000..da68311 --- /dev/null +++ b/backend/application/src/video/service.rs @@ -0,0 +1,73 @@ +// Video service implementations for streaming files chunk by chunk + +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; +use std::io::Read; + +/// Basic example: Read file line by line using BufReader +pub fn read_file_line_by_line(path: &str) -> std::io::Result<()> { + let path = Path::new(path); + let file = File::open(path)?; + let reader = BufReader::new(file); + + // Read line by line + for line in reader.lines() { + let line = line?; + println!("Line: {}", line); + } + + Ok(()) +} + +/// Read file in fixed-size chunks +pub fn read_file_in_chunks(path: &str, chunk_size: usize) -> std::io::Result<()> { + let mut file = File::open(path)?; + let mut buffer = vec![0; chunk_size]; + + loop { + let n = file.read(&mut buffer)?; + if n == 0 { + break; // EOF + } + println!("Read {} bytes: {:?}", n, &buffer[..n]); + } + + Ok(()) +} + +/// Using std::io::copy for streaming between files +pub fn copy_file_streaming(input_path: &str, output_path: &str) -> std::io::Result<()> { + use std::io; + + let mut input = File::open(input_path)?; + let mut output = File::create(output_path)?; + + // Copy in chunks without loading entire file into memory + io::copy(&mut input, &mut output)?; + + Ok(()) +} + +/// Process large files efficiently with configurable buffer size +pub fn process_large_file(path: &str, buffer_size: usize) -> std::io::Result<()> { + let file = File::open(path)?; + let mut reader = BufReader::new(file); + let mut buffer = vec![0; buffer_size]; + + loop { + let bytes_read = reader.read(&mut buffer)?; + if bytes_read == 0 { + break; + } + process_chunk(&buffer[..bytes_read]); + } + + Ok(()) +} + +/// Example chunk processing function +fn process_chunk(chunk: &[u8]) { + // Process your chunk here + println!("Processing {} bytes", chunk.len()); +} \ No newline at end of file diff --git a/backend/domain/src/lib.rs b/backend/domain/src/lib.rs index 05cd6fe..d82a933 100644 --- a/backend/domain/src/lib.rs +++ b/backend/domain/src/lib.rs @@ -1,2 +1,3 @@ pub mod configuration; -pub mod user; \ No newline at end of file +pub mod user; +pub mod video; \ No newline at end of file diff --git a/backend/domain/src/video/mod.rs b/backend/domain/src/video/mod.rs new file mode 100644 index 0000000..99bbd12 --- /dev/null +++ b/backend/domain/src/video/mod.rs @@ -0,0 +1 @@ +pub mod model; \ No newline at end of file diff --git a/backend/domain/src/video/model.rs b/backend/domain/src/video/model.rs new file mode 100644 index 0000000..3fa774b --- /dev/null +++ b/backend/domain/src/video/model.rs @@ -0,0 +1,4 @@ +pub struct Video { + filename: String, + +} \ No newline at end of file