1use anyhow::Result;
2use std::path::{Path, PathBuf};
3use tokio::io::{AsyncReadExt, AsyncWriteExt};
4
5use crate::utils::{
6 client::Consts,
7 http::{construct_raw_http_resp, write_http_resp},
8};
9
10pub async fn serve_files<S>(stream: &mut S, files_index: bool, consts: &Consts) -> Result<()>
13where
14 S: AsyncReadExt + AsyncWriteExt + Unpin,
15{
16 let mut buffer = [0u8; 1];
18 let mut parts = String::new();
19 loop {
20 stream.read_exact(&mut buffer).await?;
21 if buffer[0] == 0x0A {
22 break;
23 }
24 parts.push(buffer[0] as char);
25 }
26
27 let parts = parts.trim().split(" ").collect::<Vec<&str>>();
28
29 let path = parts[1].trim_start_matches("/").trim_end_matches("/");
30 let local_path = std::env::current_dir()
31 .unwrap_or(PathBuf::from("/tmp"))
32 .join(path);
33
34 if local_path.exists() {
35 if let Ok(metadata) = local_path.metadata() {
36 if metadata.is_dir() {
37 let index_file = local_path.join("index.html");
38 if index_file.exists() {
39 serve_file(stream, &index_file, consts).await?;
40 } else if files_index {
41 let mut generated = String::new();
42 let mut directories = Vec::new();
43 let mut files = Vec::new();
44
45 if let Ok(dir) = local_path.read_dir() {
46 for entry in dir.flatten() {
47 if let Ok(metadata) = entry.metadata() {
48 let filename = entry.file_name();
49 let filename = filename.to_str().unwrap_or("---").to_string();
50 let file_path = if path.is_empty() {
51 format!("/{filename}")
52 } else {
53 format!("/{path}/{filename}")
54 };
55
56 let last_modified = metadata
57 .modified()
58 .map(|t| {
59 let datetime: chrono::DateTime<chrono::Local> = t.into();
60 datetime.format("%Y-%m-%d %H:%M:%S").to_string()
61 })
62 .unwrap_or("---".to_string());
63
64 let file_size = if metadata.is_file() {
65 let size = metadata.len();
66 if size < 1024 {
67 format!("{size} B")
68 } else if size < 1024 * 1024 {
69 format!("{:.1} KB", size as f64 / 1024.0)
70 } else {
71 format!("{:.1} MB", size as f64 / (1024.0 * 1024.0))
72 }
73 } else {
74 "-".to_string()
75 };
76
77 let entry_data = (file_path, filename, last_modified, file_size);
78 if metadata.is_dir() {
79 directories.push(entry_data);
80 } else if metadata.is_file() {
81 files.push(entry_data);
82 }
83 }
84 }
85 }
86
87 directories.sort_by(|a, b| a.1.cmp(&b.1));
88 files.sort_by(|a, b| a.1.cmp(&b.1));
89
90 let parent_path = if let Some(parent) = Path::new(&path).parent() {
91 &format!("/{}", parent.to_str().unwrap_or(""))
92 } else {
93 "/"
94 };
95
96 generated += &format!(
97 "<tr>\n <td><a href=\"{}\">..</a></td>\n <td></td>\n <td></td>\n</tr>\n",
98 html_escape::encode_text(parent_path),
99 );
100
101 for (file_path, filename, last_modified, file_size) in directories {
102 generated += &format!(
103 "<tr>\n <td><a href=\"{}\">{}</a></td>\n <td>{}</td>\n <td>{}</td>\n</tr>\n",
104 html_escape::encode_text(&file_path),
105 html_escape::encode_text(&filename),
106 html_escape::encode_text(&last_modified),
107 html_escape::encode_text(&file_size)
108 );
109 }
110 for (file_path, filename, last_modified, file_size) in files {
111 generated += &format!(
112 "<tr>\n <td><a href=\"{}\">{}</a></td>\n <td>{}</td>\n <td>{}</td>\n</tr>\n",
113 html_escape::encode_text(&file_path),
114 html_escape::encode_text(&filename),
115 html_escape::encode_text(&last_modified),
116 html_escape::encode_text(&file_size)
117 );
118 }
119
120 write_http_resp(
121 stream,
122 200,
123 &consts
124 .list_html
125 .replace("{CONTENT}", &generated)
126 .replace("{DIR_PATH}", &format!("/{path}")),
127 "text/html",
128 )
129 .await?;
130 } else {
131 write_http_resp(
132 stream,
133 404,
134 &consts.error_html.replace("{MSG}", "Local file not found!"),
135 "text/html",
136 )
137 .await?;
138 }
139 } else if metadata.is_file() {
140 serve_file(stream, &local_path, consts).await?;
141 }
142 } else {
143 write_http_resp(
144 stream,
145 500,
146 &consts
147 .error_html
148 .replace("{MSG}", "File metadata not found!"),
149 "text/html",
150 )
151 .await?;
152 }
153 } else {
154 write_http_resp(
155 stream,
156 404,
157 &consts.error_html.replace("{MSG}", "Local file not found!"),
158 "text/html",
159 )
160 .await?;
161 }
162
163 Ok(())
164}
165
166async fn serve_file<S>(stream: &mut S, path: &PathBuf, consts: &Consts) -> Result<()>
167where
168 S: AsyncReadExt + AsyncWriteExt + Unpin,
169{
170 let file_contents = tokio::fs::read(path).await;
171 if let Ok(content) = file_contents {
172 let resp = construct_raw_http_resp(
173 200,
174 &content,
175 mime_guess::from_path(path)
176 .first_raw()
177 .unwrap_or("text/plain"),
178 );
179
180 stream.write_all(&resp).await?;
181 } else {
182 write_http_resp(
183 stream,
184 500,
185 &consts.error_html.replace("{MSG}", "Local file read error!"),
186 "text/html",
187 )
188 .await?;
189 }
190
191 Ok(())
192}