<body>
<div class="head">
<div class="breadcrumb"></div>
- <div>
- <a href="?zip" title="Download folder as a .zip file">
- <svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
- </a>
+ <div class="toolbox">
+ <div>
+ <a href="?zip" title="Download folder as a .zip file">
+ <svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/></svg>
+ </a>
+ </div>
</div>
+ <form class="searchbar">
+ <div class="icon">
+ <svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/></svg>
+ </div>
+ <input id="search" name="q" type="text" maxlength="128" autocomplete="off" tabindex="1">
+ <input type="submit" hidden />
+ </form>
</div>
<div class="main">
<div class="uploaders">
</div>
<script>
- const $head = document.querySelector(".head");
+ const $toolbox = document.querySelector(".toolbox");
const $tbody = document.querySelector(".main tbody");
const $breadcrumb = document.querySelector(".breadcrumb");
const $uploaders = document.querySelector(".uploaders");
upload() {
const { file, idx } = this;
- let url = location.href.split('?')[0];
- if (!url.endsWith("/")) url += "/";
- url += encodeURI(file.name);
+ const url = getUrl(file.name);
$uploaders.insertAdjacentHTML("beforeend", `
<div class="uploader path">
<div><svg height="16" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg></div>
}
function addPath(file, index) {
- const url = encodeURI(file.path);
+ const url = getUrl(file.name)
let actionDelete = "";
let actionDownload = "";
if (file.path_type.endsWith("Dir")) {
if (!file) return;
const ajax = new XMLHttpRequest();
- ajax.open("DELETE", encodeURI(file.path));
+ ajax.open("DELETE", getUrl(file.name));
ajax.addEventListener("readystatechange", function() {
if(ajax.readyState === 4 && ajax.status === 200) {
document.getElementById(`addPath${index}`).remove();
}
function addUploadControl() {
- $head.insertAdjacentHTML("beforeend", `
+ $toolbox.insertAdjacentHTML("beforeend", `
<div class="upload-control" title="Upload file">
<label for="file">
<svg width="16" height="16" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/></svg>
`);
}
+ function getUrl(name) {
+ let url = location.href.split('?')[0];
+ if (!url.endsWith("/")) url += "/";
+ url += encodeURI(name);
+ return url;
+ }
+
function getSvg(path_type) {
switch (path_type) {
case "Dir":
match fs::metadata(&path).await {
Ok(meta) => {
if meta.is_dir() {
- if req.uri().query().map(|v| v == "zip").unwrap_or_default() {
- self.handle_send_dir_zip(path.as_path()).await
- } else {
- self.handle_send_dir(path.as_path(), true).await
+ let req_query = req.uri().query().unwrap_or_default();
+ if req_query == "zip" {
+ return self.handle_send_dir_zip(path.as_path()).await;
}
+ if let Some(q) = req_query.strip_prefix("q=") {
+ return self.handle_query_dir(path.as_path(), q).await;
+ }
+ self.handle_ls_dir(path.as_path(), true).await
} else {
self.handle_send_file(path.as_path()).await
}
}
Err(_) => {
if req_path.ends_with('/') {
- self.handle_send_dir(path.as_path(), false).await
+ self.handle_ls_dir(path.as_path(), false).await
} else {
Ok(status_code!(StatusCode::NOT_FOUND))
}
Ok(status_code!(StatusCode::OK))
}
- async fn handle_send_dir(&self, path: &Path, exist: bool) -> BoxResult<Response> {
+ async fn handle_ls_dir(&self, path: &Path, exist: bool) -> BoxResult<Response> {
let mut paths: Vec<PathItem> = vec![];
if exist {
let mut rd = fs::read_dir(path).await?;
while let Some(entry) = rd.next_entry().await? {
let entry_path = entry.path();
- if let Ok(item) = self.get_path_item(entry_path).await {
+ if let Ok(item) = get_path_item(entry_path, path.to_path_buf()).await {
paths.push(item);
}
}
- paths.sort_unstable();
}
- let breadcrumb = self.get_breadcrumb(path);
- let data = SendDirData {
- breadcrumb,
- paths,
- readonly: self.args.readonly,
- };
- let data = serde_json::to_string(&data).unwrap();
-
- let mut output =
- INDEX_HTML.replace("__STYLE__", &format!("<style>\n{}</style>", INDEX_CSS));
- output = output.replace("__DATA__", &data);
+ self.send_index(path, paths)
+ }
- Ok(hyper::Response::builder().body(output.into()).unwrap())
+ async fn handle_query_dir(&self, path: &Path, q: &str) -> BoxResult<Response> {
+ let mut paths: Vec<PathItem> = vec![];
+ let mut walkdir = WalkDir::new(path);
+ while let Some(entry) = walkdir.next().await {
+ if let Ok(entry) = entry {
+ if !entry
+ .file_name()
+ .to_string_lossy()
+ .to_lowercase()
+ .contains(&q.to_lowercase())
+ {
+ continue;
+ }
+ if fs::symlink_metadata(entry.path()).await.is_err() {
+ continue;
+ }
+ if let Ok(item) = get_path_item(entry.path(), path.to_path_buf()).await {
+ paths.push(item);
+ }
+ }
+ }
+ self.send_index(path, paths)
}
async fn handle_send_dir_zip(&self, path: &Path) -> BoxResult<Response> {
Ok(Response::new(body))
}
+ fn send_index(&self, path: &Path, mut paths: Vec<PathItem>) -> BoxResult<Response> {
+ paths.sort_unstable();
+ let breadcrumb = self.get_breadcrumb(path);
+ let data = IndexData {
+ breadcrumb,
+ paths,
+ readonly: self.args.readonly,
+ };
+ let data = serde_json::to_string(&data).unwrap();
+ let mut output =
+ INDEX_HTML.replace("__STYLE__", &format!("<style>\n{}</style>", INDEX_CSS));
+ output = output.replace("__DATA__", &data);
+
+ Ok(hyper::Response::builder().body(output.into()).unwrap())
+ }
+
fn auth_guard(&self, req: &Request) -> BoxResult<bool> {
if let Some(auth) = &self.args.auth {
if let Some(value) = req.headers().get("Authorization") {
Ok(true)
}
- async fn get_path_item<P: AsRef<Path>>(&self, path: P) -> BoxResult<PathItem> {
- let path = path.as_ref();
- let base_path = &self.args.path;
- let rel_path = path.strip_prefix(base_path).unwrap();
- let meta = fs::metadata(&path).await?;
- let s_meta = fs::symlink_metadata(&path).await?;
- let is_dir = meta.is_dir();
- let is_symlink = s_meta.file_type().is_symlink();
- let path_type = match (is_symlink, is_dir) {
- (true, true) => PathType::SymlinkDir,
- (false, true) => PathType::Dir,
- (true, false) => PathType::SymlinkFile,
- (false, false) => PathType::File,
- };
- let mtime = meta
- .modified()?
- .duration_since(SystemTime::UNIX_EPOCH)
- .ok()
- .map(|v| v.as_millis() as u64);
- let size = match path_type {
- PathType::Dir | PathType::SymlinkDir => None,
- PathType::File | PathType::SymlinkFile => Some(meta.len()),
- };
- let name = rel_path
- .file_name()
- .and_then(|v| v.to_str())
- .unwrap_or_default()
- .to_owned();
- Ok(PathItem {
- path_type,
- name,
- path: format!("/{}", normalize_path(rel_path)),
- mtime,
- size,
- })
- }
-
fn get_breadcrumb(&self, path: &Path) -> String {
let path = match self.args.path.parent() {
Some(p) => path.strip_prefix(p).unwrap(),
}
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]
-struct SendDirData {
+struct IndexData {
breadcrumb: String,
paths: Vec<PathItem>,
readonly: bool,
struct PathItem {
path_type: PathType,
name: String,
- path: String,
mtime: Option<u64>,
size: Option<u64>,
}
SymlinkFile,
}
+async fn get_path_item<P: AsRef<Path>>(path: P, base_path: P) -> BoxResult<PathItem> {
+ let path = path.as_ref();
+ let rel_path = path.strip_prefix(base_path).unwrap();
+ let meta = fs::metadata(&path).await?;
+ let s_meta = fs::symlink_metadata(&path).await?;
+ let is_dir = meta.is_dir();
+ let is_symlink = s_meta.file_type().is_symlink();
+ let path_type = match (is_symlink, is_dir) {
+ (true, true) => PathType::SymlinkDir,
+ (false, true) => PathType::Dir,
+ (true, false) => PathType::SymlinkFile,
+ (false, false) => PathType::File,
+ };
+ let mtime = meta
+ .modified()?
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .ok()
+ .map(|v| v.as_millis() as u64);
+ let size = match path_type {
+ PathType::Dir | PathType::SymlinkDir => None,
+ PathType::File | PathType::SymlinkFile => Some(meta.len()),
+ };
+ let name = normalize_path(rel_path);
+ Ok(PathItem {
+ path_type,
+ name,
+ mtime,
+ size,
+ })
+}
+
fn normalize_path<P: AsRef<Path>>(path: P) -> String {
let path = path.as_ref().to_str().unwrap_or_default();
if cfg!(windows) {
let mut walkdir = WalkDir::new(dir);
while let Some(entry) = walkdir.next().await {
if let Ok(entry) = entry {
- let meta = fs::symlink_metadata(entry.path()).await?;
+ let meta = match fs::symlink_metadata(entry.path()).await {
+ Ok(meta) => meta,
+ Err(_) => continue,
+ };
if meta.is_file() {
let filepath = entry.path();
let filename = match filepath.strip_prefix(dir).ok().and_then(|v| v.to_str()) {