pub(super) async fn file_response( service: &CompiledService, opened: Opened, path: &Path, method: &Method, headers: &HeaderMap, content_encoding: Option<&'static str>, ) -> Response { let length = opened.metadata.len(); let (etag, modified) = metadata_validators(&opened.metadata); let mime = content_type(service, path); if not_modified(headers, etag.as_deref(), modified) { let mut response = Response::new(Body::empty()); *response.status_mut() = StatusCode::NOT_MODIFIED; add_validators(&mut response, etag.as_deref(), modified); add_cache_control(&mut response, service, path, &mime); add_download_header(&mut response, service, path); if let Some(encoding) = content_encoding { response .headers_mut() .insert(CONTENT_ENCODING, HeaderValue::from_static(encoding)); response .headers_mut() .insert(VARY, HeaderValue::from_static("Accept-Encoding")); } response .headers_mut() .insert(ACCEPT_RANGES, HeaderValue::from_static("bytes")); return response; } let range_header = headers.get(RANGE).and_then(|value| value.to_str().ok()); let range = if range_header.is_some() && if_range_allows(headers, etag.as_deref(), modified) { match range_header.and_then(|value| parse_range(value, length)) { Some(range) => Some(range), None => { let mut response = Response::new(Body::empty()); *response.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE; response.headers_mut().insert( CONTENT_RANGE, HeaderValue::from_str(&format!("bytes */{length}")).unwrap(), ); response .headers_mut() .insert(ACCEPT_RANGES, HeaderValue::from_static("bytes")); response .headers_mut() .insert(CONTENT_LENGTH, HeaderValue::from_static("0")); add_validators(&mut response, etag.as_deref(), modified); add_cache_control(&mut response, service, path, &mime); add_download_header(&mut response, service, path); if let Some(encoding) = content_encoding { response .headers_mut() .insert(CONTENT_ENCODING, HeaderValue::from_static(encoding)); response .headers_mut() .insert(VARY, HeaderValue::from_static("Accept-Encoding")); } return response; } } } else { None }; let (status, start, body_length) = match range { Some(range) => ( StatusCode::PARTIAL_CONTENT, range.start, range.end - range.start + 1, ), None => (StatusCode::OK, 0, length), }; let mut response = Response::new(Body::empty()); *response.status_mut() = status; response.headers_mut().insert(CONTENT_TYPE, mime.clone()); response.headers_mut().insert( CONTENT_LENGTH, HeaderValue::from_str(&body_length.to_string()).unwrap(), ); response .headers_mut() .insert(ACCEPT_RANGES, HeaderValue::from_static("bytes")); if let Some(range) = range { response.headers_mut().insert( CONTENT_RANGE, HeaderValue::from_str(&format!("bytes {}-{}/{length}", range.start, range.end)) .unwrap(), ); } add_validators(&mut response, etag.as_deref(), modified); add_cache_control(&mut response, service, path, &mime); add_download_header(&mut response, service, path); if let Some(encoding) = content_encoding { response .headers_mut() .insert(CONTENT_ENCODING, HeaderValue::from_static(encoding)); response .headers_mut() .insert(VARY, HeaderValue::from_static("Accept-Encoding")); } if method == Method::HEAD || body_length == 0 { return response; } let mut file = tokio::fs::File::from_std(opened.file); if start != 0 { if file.seek(std::io::SeekFrom::Start(start)).await.is_err() { return status_response( StatusCode::INTERNAL_SERVER_ERROR, "RustProxy: Web Server seek failed\n", ); } } let stream = ReaderStream::new(file.take(body_length)); *response.body_mut() = Body::from_stream(stream); response } fn percent_encode_segment(value: &str) -> String { let mut out = String::with_capacity(value.len()); for byte in value.as_bytes() { if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') { out.push(*byte as char); } else { use std::fmt::Write; let _ = write!(&mut out, "%{byte:02X}"); } } out } fn html_escape(value: &str) -> String { let mut out = String::with_capacity(value.len()); for ch in value.chars() { match ch { '&' => out.push_str("&"), '<' => out.push_str("<"), '>' => out.push_str(">"), '"' => out.push_str("""), '\'' => out.push_str("'"), _ => out.push(ch), } } out } pub(super) fn directory_listing(path: &str, entries: &[DirectoryEntry]) -> Vec { let title = html_escape(path); let mut html = String::with_capacity(4096 + entries.len() * 180); html.push_str(r#"Index of "#); html.push_str(&title); html.push_str(r#" · RustProxy
RustProxyWEB SERVER
"#); html.push_str(&title); html.push_str(r#"
DIRECTORY LISTING

Index of "#); html.push_str(&title); html.push_str("

"); html.push_str(&format!("{} entries", entries.len())); html.push_str("
"); if path != "/" { html.push_str(r#"UP../Parent"#); } for entry in entries { let label = html_escape(&entry.name); let mut href = percent_encode_segment(&entry.name); if entry.directory { href.push('/'); } let href = html_escape(&href); let slash = if entry.directory { "/" } else { "" }; let class = if entry.directory { "entry directory" } else { "entry file" }; let kind = if entry.directory { "Directory" } else { "File" }; let type_label = if entry.directory { "DIR" } else { "FILE" }; html.push_str(&format!( "{type_label}{label}{slash}{kind}" )); } html.push_str(r#"
"#); html.into_bytes() } pub(super) fn directory_redirect(uri: &Uri, method: &Method) -> Response { let mut target = uri.path().to_owned(); if !target.ends_with('/') { target.push('/'); } if let Some(query) = uri.query() { target.push('?'); target.push_str(query); } let mut response = Response::new(Body::empty()); *response.status_mut() = if *method == Method::GET || *method == Method::HEAD { StatusCode::MOVED_PERMANENTLY } else { StatusCode::PERMANENT_REDIRECT }; if let Ok(value) = HeaderValue::from_str(&target) { response.headers_mut().insert(LOCATION, value); } response }