.allow_invalid_utf8(true)
.help("Path to a directory for serving files");
+ let arg_readonly = Arg::new("readonly")
+ .short('r')
+ .long("readonly")
+ .help("Only serve static files, no operations like upload and delete");
+
+ let arg_auth = Arg::new("auth")
+ .short('a')
+ .long("auth")
+ .help("Authenticate with user and pass")
+ .value_name("user:pass");
+
clap::command!()
.about(ABOUT)
.arg(arg_address)
.arg(arg_port)
.arg(arg_path)
+ .arg(arg_readonly)
+ .arg(arg_auth)
}
pub fn matches() -> ArgMatches {
pub address: String,
pub port: u16,
pub path: PathBuf,
+ pub readonly: bool,
+ pub auth: Option<String>,
}
impl Args {
let port = matches.value_of_t::<u16>("port")?;
let path = matches.value_of_os("path").unwrap_or_default();
let path = Args::parse_path(path)?;
+ let readonly = matches.is_present("readonly");
+ let auth = matches.value_of("auth").map(|v| v.to_owned());
Ok(Args {
address,
port,
path,
+ readonly,
+ auth,
})
}
<body>
<div class="head">
<div class="breadcrumb"></div>
- <div class="action" title="Upload file">
- <label for="file">
- <svg viewBox="0 0 384.97 384.97" width="14" height="14"><path d="M372.939,264.641c-6.641,0-12.03,5.39-12.03,12.03v84.212H24.061v-84.212c0-6.641-5.39-12.03-12.03-12.03 S0,270.031,0,276.671v96.242c0,6.641,5.39,12.03,12.03,12.03h360.909c6.641,0,12.03-5.39,12.03-12.03v-96.242 C384.97,270.019,379.58,264.641,372.939,264.641z"></path><path d="M117.067,103.507l63.46-62.558v235.71c0,6.641,5.438,12.03,12.151,12.03c6.713,0,12.151-5.39,12.151-12.03V40.95 l63.46,62.558c4.74,4.704,12.439,4.704,17.179,0c4.74-4.704,4.752-12.319,0-17.011l-84.2-82.997 c-4.692-4.656-12.584-4.608-17.191,0L99.888,86.496c-4.752,4.704-4.74,12.319,0,17.011 C104.628,108.211,112.327,108.211,117.067,103.507z"></path></svg>
- </label>
- <input type="file" id="file" name="file" multiple>
- </div>
</div>
<div class="main">
<div class="uploaders">
</div>
<script>
+ const $head = document.querySelector(".head");
const $tbody = document.querySelector(".main tbody");
const $breadcrumb = document.querySelector(".breadcrumb");
- const $fileInput = document.getElementById("file");
const $uploaders = document.querySelector(".uploaders");
- const { breadcrumb, paths } = __DATA__;
+ const $uploadControl = document.querySelector(".upload-control");
+ const { breadcrumb, paths, readonly } = __DATA__;
let uploaderIdx = 0;
class Uploader {
`)
}
+ function addUploadControl() {
+ $head.insertAdjacentHTML("beforeend", `
+ <div class="upload-control" title="Upload file">
+ <label for="file">
+ <svg viewBox="0 0 384.97 384.97" width="14" height="14"><path d="M372.939,264.641c-6.641,0-12.03,5.39-12.03,12.03v84.212H24.061v-84.212c0-6.641-5.39-12.03-12.03-12.03 S0,270.031,0,276.671v96.242c0,6.641,5.39,12.03,12.03,12.03h360.909c6.641,0,12.03-5.39,12.03-12.03v-96.242 C384.97,270.019,379.58,264.641,372.939,264.641z"></path><path d="M117.067,103.507l63.46-62.558v235.71c0,6.641,5.438,12.03,12.151,12.03c6.713,0,12.151-5.39,12.151-12.03V40.95 l63.46,62.558c4.74,4.704,12.439,4.704,17.179,0c4.74-4.704,4.752-12.319,0-17.011l-84.2-82.997 c-4.692-4.656-12.584-4.608-17.191,0L99.888,86.496c-4.752,4.704-4.74,12.319,0,17.011 C104.628,108.211,112.327,108.211,117.067,103.507z"></path></svg>
+ </label>
+ <input type="file" id="file" name="file" multiple>
+ </div>
+`);
+ }
+
function getSvg(path_type) {
switch (path_type) {
case "Dir":
document.addEventListener('DOMContentLoaded', () => {
addBreadcrumb(breadcrumb);
paths.forEach(file => addFile(file));
- $fileInput.addEventListener("change", e => {
- const files = e.target.files;
- for (const file of files) {
- uploaderIdx += 1;
- const uploader = new Uploader(uploaderIdx, file);
- uploader.upload();
- }
- });
+ if (readonly) {
+ addUploadControl();
+ document.getElementById("file").addEventListener("change", e => {
+ const files = e.target.files;
+ for (const file of files) {
+ uploaderIdx += 1;
+ const uploader = new Uploader(uploaderIdx, file);
+ uploader.upload();
+ }
+ });
+ }
});
</script>
</body>
use crate::{Args, BoxResult};
use futures::TryStreamExt;
+use hyper::header::HeaderValue;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, StatusCode};
use percent_encoding::percent_decode;
let res = if req.method() == Method::GET {
self.handle_static(req).await
} else if req.method() == Method::PUT {
+ if self.args.readonly {
+ return Ok(status_code!(StatusCode::FORBIDDEN));
+ }
self.handle_upload(req).await
} else {
return Ok(status_code!(StatusCode::NOT_FOUND));
}
async fn handle_static(&self, req: Request) -> BoxResult<Response> {
+ if !self.auth_guard(&req).unwrap_or_default() {
+ let mut res = status_code!(StatusCode::UNAUTHORIZED);
+ res.headers_mut().insert("WWW-Authenticate" , HeaderValue::from_static("Basic"));
+ return Ok(res)
+ }
let path = match self.get_file_path(req.uri().path())? {
Some(path) => path,
None => return Ok(status_code!(StatusCode::FORBIDDEN)),
}
async fn handle_upload(&self, mut req: Request) -> BoxResult<Response> {
+ if !self.auth_guard(&req).unwrap_or_default() {
+ let mut res = status_code!(StatusCode::UNAUTHORIZED);
+ res.headers_mut().insert("WWW-Authenticate" , HeaderValue::from_static("Basic"));
+ return Ok(res)
+ }
let path = match self.get_file_path(req.uri().path())? {
Some(path) => path,
None => return Ok(status_code!(StatusCode::FORBIDDEN)),
paths.sort_unstable();
let breadcrumb = self.get_breadcrumb(path);
- let data = SendDirData { breadcrumb, paths };
+ let data = SendDirData { breadcrumb, paths, readonly: !self.args.readonly };
let data = serde_json::to_string(&data).unwrap();
let mut output =
Ok(Response::new(body))
}
+ fn auth_guard(&self, req: &Request) -> BoxResult<bool> {
+ if let Some(auth) = &self.args.auth {
+ if let Some(value) = req.headers().get("Authorization") {
+ let value = value.to_str()?;
+ let value = if value.contains("Basic ") {
+ &value[6..]
+ } else {
+ return Ok(false);
+ };
+ let value = base64::decode(value)?;
+ let value = std::str::from_utf8(&value)?;
+ return Ok(value == auth);
+ } else {
+ return Ok(false);
+ }
+ }
+ Ok(true)
+ }
+
fn get_breadcrumb(&self, path: &Path) -> String {
let path = match self.args.path.parent() {
Some(p) => path.strip_prefix(p).unwrap(),
struct SendDirData {
breadcrumb: String,
paths: Vec<PathItem>,
+ readonly: bool,
}
#[derive(Debug, Serialize, Eq, PartialEq, Ord, PartialOrd)]