fkm_proxy/utils/
serve.rs

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
10// TODO: cleanup this fucking mess LMAO
11
12pub async fn serve_files<S>(stream: &mut S, files_index: bool, consts: &Consts) -> Result<()>
13where
14    S: AsyncReadExt + AsyncWriteExt + Unpin,
15{
16    // for example: "GET / HTTP1.1"
17    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}