Skip to content

Commit 3e192ae

Browse files
committed
move safe_join_paths to responders
1 parent ac6d02f commit 3e192ae

File tree

2 files changed

+46
-51
lines changed

2 files changed

+46
-51
lines changed

src/http_responders/file.rs

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,46 @@
1-
use std::{fs, path::PathBuf};
1+
use std::{
2+
fs,
3+
path::{Path, PathBuf},
4+
};
5+
6+
/// Attempts to safely join a root directory and a requested relative path.
7+
///
8+
/// Ensures that the resulting path:
9+
/// - Resolves symbolic links and `..` segments via `canonicalize`
10+
/// - Remains within the bounds of the specified root directory
11+
/// - Actually exists on disk
12+
///
13+
/// This protects against directory traversal vulnerabilities, such as accessing
14+
/// files outside of the intended root (e.g., `/etc/passwd`).
15+
///
16+
/// # Arguments
17+
/// * `root` - The root directory from which serving is allowed.
18+
/// * `requested_path` - The path requested by the client (usually from the URL).
19+
///
20+
/// # Returns
21+
/// `Some(PathBuf)` if the resolved path exists and is within the root. `None` otherwise.
22+
///
23+
/// # Example
24+
/// ```
25+
/// let safe_path = safe_join_paths("/var/www", "/index.html");
26+
/// assert!(safe_path.unwrap().ends_with("index.html"));
27+
/// ```
28+
pub fn safe_join_paths(root: &str, requested_path: &str) -> Option<PathBuf> {
29+
let root_path = Path::new(root).canonicalize().ok()?;
30+
let requested_full_path = root_path.join(requested_path.trim_start_matches("/"));
31+
32+
if !requested_full_path.exists() {
33+
return None;
34+
}
35+
36+
let canonical_path = requested_full_path.canonicalize().ok()?;
37+
38+
if canonical_path.starts_with(&root_path) {
39+
Some(canonical_path)
40+
} else {
41+
None
42+
}
43+
}
244

345
/// Reads the content of a file from the filesystem.
446
///
@@ -14,13 +56,5 @@ use std::{fs, path::PathBuf};
1456
/// # See Also
1557
/// [`std::fs::read`](https://doc.rust-lang.org/std/fs/fn.read.html)
1658
pub fn serve_file(path: &PathBuf) -> Option<Vec<u8>> {
17-
let r = fs::read(path);
18-
if r.is_ok() { Some(r.unwrap()) } else { None }
59+
fs::read(path).ok()
1960
}
20-
//
21-
// Suggest to use .ok()? instead of manual unwrap/if is_ok for more idiomatic error handling:
22-
// fn serve_file(path: &PathBuf) -> Option<Vec<u8>> {
23-
// fs::read(path).ok()
24-
// }
25-
//
26-
//

src/main.rs

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ mod shutdown;
4343
mod utils;
4444

4545
use std::fs;
46+
use std::io;
4647
use std::path::Path;
4748
use std::sync::Mutex;
48-
use std::{io, path::PathBuf};
4949

5050
use cache::Cache;
5151
use hteapot::{Hteapot, HttpRequest, HttpResponse, HttpStatus};
@@ -54,48 +54,9 @@ use utils::get_mime_tipe;
5454
use logger::{LogLevel, Logger};
5555
use std::time::Instant;
5656

57-
use http_responders::file::serve_file;
57+
use http_responders::file::{safe_join_paths, serve_file};
5858
use http_responders::proxy::is_proxy;
5959

60-
/// Attempts to safely join a root directory and a requested relative path.
61-
///
62-
/// Ensures that the resulting path:
63-
/// - Resolves symbolic links and `..` segments via `canonicalize`
64-
/// - Remains within the bounds of the specified root directory
65-
/// - Actually exists on disk
66-
///
67-
/// This protects against directory traversal vulnerabilities, such as accessing
68-
/// files outside of the intended root (e.g., `/etc/passwd`).
69-
///
70-
/// # Arguments
71-
/// * `root` - The root directory from which serving is allowed.
72-
/// * `requested_path` - The path requested by the client (usually from the URL).
73-
///
74-
/// # Returns
75-
/// `Some(PathBuf)` if the resolved path exists and is within the root. `None` otherwise.
76-
///
77-
/// # Example
78-
/// ```
79-
/// let safe_path = safe_join_paths("/var/www", "/index.html");
80-
/// assert!(safe_path.unwrap().ends_with("index.html"));
81-
/// ```
82-
fn safe_join_paths(root: &str, requested_path: &str) -> Option<PathBuf> {
83-
let root_path = Path::new(root).canonicalize().ok()?;
84-
let requested_full_path = root_path.join(requested_path.trim_start_matches("/"));
85-
86-
if !requested_full_path.exists() {
87-
return None;
88-
}
89-
90-
let canonical_path = requested_full_path.canonicalize().ok()?;
91-
92-
if canonical_path.starts_with(&root_path) {
93-
Some(canonical_path)
94-
} else {
95-
None
96-
}
97-
}
98-
9960
/// Main entry point of the Hteapot server.
10061
///
10162
/// Handles command-line interface, config file parsing, optional file-serving mode,

0 commit comments

Comments
 (0)