net/
local_directory_listing.rs1use std::fs::{DirEntry, Metadata, ReadDir};
6use std::path::PathBuf;
7
8use chrono::{DateTime, Local};
9use embedder_traits::resources::{Resource, read_string};
10use headers::{ContentType, HeaderMapExt};
11use net_traits::request::Request;
12use net_traits::response::{Response, ResponseBody};
13use net_traits::{NetworkError, ResourceFetchTiming};
14use servo_config::pref;
15use servo_url::ServoUrl;
16use url::Url;
17
18pub fn fetch(request: &mut Request, url: ServoUrl, path_buf: PathBuf) -> Response {
19 if !pref!(network_local_directory_listing_enabled) {
20 return Response::network_error(NetworkError::Internal(
23 "Local directory listing feature has not been enabled in preferences".into(),
24 ));
25 }
26
27 if !request.origin.is_opaque() {
28 return Response::network_error(NetworkError::Internal(
32 "Cannot request local directory listing from non-local origin.".into(),
33 ));
34 }
35
36 let directory_contents = match std::fs::read_dir(path_buf.clone()) {
37 Ok(directory_contents) => directory_contents,
38 Err(error) => {
39 return Response::network_error(NetworkError::Internal(format!(
40 "Unable to access directory: {error}"
41 )));
42 },
43 };
44
45 let output = build_html_directory_listing(url.as_url(), path_buf, directory_contents);
46
47 let mut response = Response::new(url, ResourceFetchTiming::new(request.timing_type()));
48 response.headers.typed_insert(ContentType::html());
49 *response.body.lock().unwrap() = ResponseBody::Done(output.into_bytes());
50
51 response
52}
53
54pub fn build_html_directory_listing(
63 url: &Url,
64 path: PathBuf,
65 directory_contents: ReadDir,
66) -> String {
67 let mut page_html = String::with_capacity(1024);
68 page_html.push_str("<!DOCTYPE html>");
69
70 let mut parent_url_string = String::new();
71 if path.parent().is_some() {
72 let mut parent_url = url.clone();
73 if let Ok(mut path_segments) = parent_url.path_segments_mut() {
74 path_segments.pop();
75 }
76 parent_url.as_str().clone_into(&mut parent_url_string);
77 }
78
79 page_html.push_str(&read_string(Resource::DirectoryListingHTML));
80
81 page_html.push_str("<script>\n");
82 page_html.push_str(&format!(
83 "setData({:?}, {:?}, [",
84 url.as_str(),
85 parent_url_string
86 ));
87
88 for directory_entry in directory_contents {
89 let Ok(directory_entry) = directory_entry else {
90 continue;
91 };
92 let Ok(metadata) = directory_entry.metadata() else {
93 continue;
94 };
95 write_directory_entry(directory_entry, metadata, url, &mut page_html);
96 }
97
98 page_html.push_str("]);");
99 page_html.push_str("</script>\n");
100
101 page_html
102}
103
104fn write_directory_entry(entry: DirEntry, metadata: Metadata, url: &Url, output: &mut String) {
105 let Ok(name) = entry.file_name().into_string() else {
106 return;
107 };
108
109 let mut file_url = url.clone();
110 {
111 let Ok(mut path_segments) = file_url.path_segments_mut() else {
112 return;
113 };
114 path_segments.push(&name);
115 }
116
117 let class = if metadata.is_dir() {
118 "directory"
119 } else if metadata.is_symlink() {
120 "symlink"
121 } else {
122 "file"
123 };
124
125 let file_url_string = &file_url.to_string();
126 let file_size = metadata_to_file_size_string(&metadata);
127 let last_modified = metadata
128 .modified()
129 .map(DateTime::<Local>::from)
130 .map(|time| time.format("%F %r").to_string())
131 .unwrap_or_default();
132
133 output.push_str(&format!(
134 "[{class:?}, {name:?}, {file_url_string:?}, {file_size:?}, {last_modified:?}],"
135 ));
136}
137
138pub fn metadata_to_file_size_string(metadata: &Metadata) -> String {
139 if !metadata.is_file() {
140 return String::new();
141 }
142
143 let mut float_size = metadata.len() as f64;
144 let mut prefix_power = 0;
145 while float_size > 1000.0 && prefix_power < 3 {
146 float_size /= 1000.0;
147 prefix_power += 1;
148 }
149
150 let prefix = match prefix_power {
151 0 => "B",
152 1 => "KB",
153 2 => "MB",
154 _ => "GB",
155 };
156
157 format!("{:.2} {prefix}", float_size)
158}