feat: use digest auth (#14)
* feat: switch to digest auth * implement digest auth * cargo fmt * no lock
This commit is contained in:
parent
05155aa532
commit
2f40313a54
7 changed files with 316 additions and 24 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -297,6 +297,8 @@ dependencies = [
|
||||||
"get_if_addrs",
|
"get_if_addrs",
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"lazy_static",
|
||||||
|
"md5",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"rustls",
|
"rustls",
|
||||||
|
@ -307,6 +309,7 @@ dependencies = [
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -484,6 +487,17 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
@ -656,6 +670,12 @@ dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md5"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -770,6 +790,12 @@ version = "0.3.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.39"
|
version = "1.0.39"
|
||||||
|
@ -788,6 +814,36 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.20"
|
version = "0.16.20"
|
||||||
|
@ -1098,6 +1154,16 @@ version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6d5d669b51467dcf7b2f1a796ce0f955f05f01cafda6c19d6e95f730df29238"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
|
|
@ -31,6 +31,9 @@ mime_guess = "2.0.4"
|
||||||
get_if_addrs = "0.5.3"
|
get_if_addrs = "0.5.3"
|
||||||
rustls = { version = "0.20", default-features = false, features = ["tls12"] }
|
rustls = { version = "0.20", default-features = false, features = ["tls12"] }
|
||||||
rustls-pemfile = "1"
|
rustls-pemfile = "1"
|
||||||
|
md5 = "0.7.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
uuid = { version = "1.1.1", features = ["v4", "fast-rng"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
|
@ -13,8 +13,8 @@ Duf is a simple file server. Support static serve, search, upload, webdav...
|
||||||
- Download folder as zip file
|
- Download folder as zip file
|
||||||
- Upload files and folders (Drag & Drop)
|
- Upload files and folders (Drag & Drop)
|
||||||
- Search files
|
- Search files
|
||||||
- Basic authentication
|
|
||||||
- Partial responses (Parallel/Resume download)
|
- Partial responses (Parallel/Resume download)
|
||||||
|
- Authentication
|
||||||
- Support https
|
- Support https
|
||||||
- Support webdav
|
- Support webdav
|
||||||
- Easy to use with curl
|
- Easy to use with curl
|
||||||
|
|
|
@ -5,6 +5,7 @@ use std::net::SocketAddr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{env, fs, io};
|
use std::{env, fs, io};
|
||||||
|
|
||||||
|
use crate::auth::parse_auth;
|
||||||
use crate::BoxResult;
|
use crate::BoxResult;
|
||||||
|
|
||||||
const ABOUT: &str = concat!("\n", crate_description!()); // Add extra newline.
|
const ABOUT: &str = concat!("\n", crate_description!()); // Add extra newline.
|
||||||
|
@ -115,7 +116,7 @@ pub struct Args {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub path_prefix: String,
|
pub path_prefix: String,
|
||||||
pub uri_prefix: String,
|
pub uri_prefix: String,
|
||||||
pub auth: Option<String>,
|
pub auth: Option<(String, String)>,
|
||||||
pub no_auth_access: bool,
|
pub no_auth_access: bool,
|
||||||
pub allow_upload: bool,
|
pub allow_upload: bool,
|
||||||
pub allow_delete: bool,
|
pub allow_delete: bool,
|
||||||
|
@ -145,7 +146,10 @@ impl Args {
|
||||||
format!("/{}/", &path_prefix)
|
format!("/{}/", &path_prefix)
|
||||||
};
|
};
|
||||||
let cors = matches.is_present("cors");
|
let cors = matches.is_present("cors");
|
||||||
let auth = matches.value_of("auth").map(|v| v.to_owned());
|
let auth = match matches.value_of("auth") {
|
||||||
|
Some(auth) => Some(parse_auth(auth)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
let no_auth_access = matches.is_present("no-auth-access");
|
let no_auth_access = matches.is_present("no-auth-access");
|
||||||
let allow_upload = matches.is_present("allow-all") || matches.is_present("allow-upload");
|
let allow_upload = matches.is_present("allow-all") || matches.is_present("allow-upload");
|
||||||
let allow_delete = matches.is_present("allow-all") || matches.is_present("allow-delete");
|
let allow_delete = matches.is_present("allow-all") || matches.is_present("allow-delete");
|
||||||
|
|
209
src/auth.rs
Normal file
209
src/auth.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
use headers::HeaderValue;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use md5::Context;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::BoxResult;
|
||||||
|
|
||||||
|
const REALM: &str = "DUF";
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref NONCESTARTHASH: Context = {
|
||||||
|
let mut h = Context::new();
|
||||||
|
h.consume(Uuid::new_v4().as_bytes());
|
||||||
|
h.consume(std::process::id().to_be_bytes());
|
||||||
|
h
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_www_auth(stale: bool) -> String {
|
||||||
|
let str_stale = if stale { "stale=true," } else { "" };
|
||||||
|
format!(
|
||||||
|
"Digest realm=\"{}\",nonce=\"{}\",{}qop=\"auth\",algorithm=\"MD5\"",
|
||||||
|
REALM,
|
||||||
|
create_nonce(),
|
||||||
|
str_stale
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_auth(auth: &str) -> BoxResult<(String, String)> {
|
||||||
|
let p: Vec<&str> = auth.trim().split(':').collect();
|
||||||
|
let err = "Invalid auth value";
|
||||||
|
if p.len() != 2 {
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
let user = p[0];
|
||||||
|
let pass = p[1];
|
||||||
|
let mut h = Context::new();
|
||||||
|
h.consume(format!("{}:{}:{}", user, REALM, pass).as_bytes());
|
||||||
|
Ok((user.to_owned(), format!("{:x}", h.compute())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn valid_digest(
|
||||||
|
header_value: &HeaderValue,
|
||||||
|
method: &str,
|
||||||
|
auth_user: &str,
|
||||||
|
auth_pass: &str,
|
||||||
|
) -> Option<()> {
|
||||||
|
let digest_value = strip_prefix(header_value.as_bytes(), b"Digest ")?;
|
||||||
|
let user_vals = to_headermap(digest_value).ok()?;
|
||||||
|
if let (Some(username), Some(nonce), Some(user_response)) = (
|
||||||
|
user_vals
|
||||||
|
.get(b"username".as_ref())
|
||||||
|
.and_then(|b| std::str::from_utf8(*b).ok()),
|
||||||
|
user_vals.get(b"nonce".as_ref()),
|
||||||
|
user_vals.get(b"response".as_ref()),
|
||||||
|
) {
|
||||||
|
match validate_nonce(nonce) {
|
||||||
|
Ok(true) => {}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
if auth_user != username {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut ha = Context::new();
|
||||||
|
ha.consume(method);
|
||||||
|
ha.consume(b":");
|
||||||
|
if let Some(uri) = user_vals.get(b"uri".as_ref()) {
|
||||||
|
ha.consume(uri);
|
||||||
|
}
|
||||||
|
let ha = format!("{:x}", ha.compute());
|
||||||
|
let mut correct_response = None;
|
||||||
|
if let Some(qop) = user_vals.get(b"qop".as_ref()) {
|
||||||
|
if qop == &b"auth".as_ref() || qop == &b"auth-int".as_ref() {
|
||||||
|
correct_response = Some({
|
||||||
|
let mut c = Context::new();
|
||||||
|
c.consume(&auth_pass);
|
||||||
|
c.consume(b":");
|
||||||
|
c.consume(nonce);
|
||||||
|
c.consume(b":");
|
||||||
|
if let Some(nc) = user_vals.get(b"nc".as_ref()) {
|
||||||
|
c.consume(nc);
|
||||||
|
}
|
||||||
|
c.consume(b":");
|
||||||
|
if let Some(cnonce) = user_vals.get(b"cnonce".as_ref()) {
|
||||||
|
c.consume(cnonce);
|
||||||
|
}
|
||||||
|
c.consume(b":");
|
||||||
|
c.consume(qop);
|
||||||
|
c.consume(b":");
|
||||||
|
c.consume(&*ha);
|
||||||
|
format!("{:x}", c.compute())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let correct_response = match correct_response {
|
||||||
|
Some(r) => r,
|
||||||
|
None => {
|
||||||
|
let mut c = Context::new();
|
||||||
|
c.consume(&auth_pass);
|
||||||
|
c.consume(b":");
|
||||||
|
c.consume(nonce);
|
||||||
|
c.consume(b":");
|
||||||
|
c.consume(&*ha);
|
||||||
|
format!("{:x}", c.compute())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if correct_response.as_bytes() == *user_response {
|
||||||
|
// grant access
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a nonce is still valid.
|
||||||
|
/// Return an error if it was never valid
|
||||||
|
fn validate_nonce(nonce: &[u8]) -> Result<bool, ()> {
|
||||||
|
if nonce.len() != 34 {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
//parse hex
|
||||||
|
if let Ok(n) = std::str::from_utf8(nonce) {
|
||||||
|
//get time
|
||||||
|
if let Ok(secs_nonce) = u32::from_str_radix(&n[..8], 16) {
|
||||||
|
//check time
|
||||||
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||||
|
let secs_now = now.as_secs() as u32;
|
||||||
|
|
||||||
|
if let Some(dur) = secs_now.checked_sub(secs_nonce) {
|
||||||
|
//check hash
|
||||||
|
let mut h = NONCESTARTHASH.clone();
|
||||||
|
h.consume(secs_nonce.to_be_bytes());
|
||||||
|
let h = format!("{:x}", h.compute());
|
||||||
|
if h[..26] == n[8..34] {
|
||||||
|
return Ok(dur < 300); // from the last 5min
|
||||||
|
//Authentication-Info ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_prefix<'a>(search: &'a [u8], prefix: &[u8]) -> Option<&'a [u8]> {
|
||||||
|
let l = prefix.len();
|
||||||
|
if search.len() < l {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if &search[..l] == prefix {
|
||||||
|
Some(&search[l..])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_headermap(header: &[u8]) -> Result<HashMap<&[u8], &[u8]>, ()> {
|
||||||
|
let mut sep = Vec::new();
|
||||||
|
let mut asign = Vec::new();
|
||||||
|
let mut i: usize = 0;
|
||||||
|
let mut esc = false;
|
||||||
|
for c in header {
|
||||||
|
match (c, esc) {
|
||||||
|
(b'=', false) => asign.push(i),
|
||||||
|
(b',', false) => sep.push(i),
|
||||||
|
(b'"', false) => esc = true,
|
||||||
|
(b'"', true) => esc = false,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
sep.push(i); // same len for both Vecs
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
let mut ret = HashMap::new();
|
||||||
|
for (&k, &a) in sep.iter().zip(asign.iter()) {
|
||||||
|
while header[i] == b' ' {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if a <= i || k <= 1 + a {
|
||||||
|
//keys and vals must contain one char
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
let key = &header[i..a];
|
||||||
|
let val = if header[1 + a] == b'"' && header[k - 1] == b'"' {
|
||||||
|
//escaped
|
||||||
|
&header[2 + a..k - 1]
|
||||||
|
} else {
|
||||||
|
//not escaped
|
||||||
|
&header[1 + a..k]
|
||||||
|
};
|
||||||
|
i = 1 + k;
|
||||||
|
ret.insert(key, val);
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_nonce() -> String {
|
||||||
|
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||||||
|
let secs = now.as_secs() as u32;
|
||||||
|
let mut h = NONCESTARTHASH.clone();
|
||||||
|
h.consume(secs.to_be_bytes());
|
||||||
|
|
||||||
|
let n = format!("{:08x}{:032x}", secs, h.compute());
|
||||||
|
n[..34].to_string()
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
mod args;
|
mod args;
|
||||||
|
mod auth;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
|
pub type BoxResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::auth::{generate_www_auth, valid_digest};
|
||||||
use crate::{Args, BoxResult};
|
use crate::{Args, BoxResult};
|
||||||
|
|
||||||
use async_walkdir::WalkDir;
|
use async_walkdir::WalkDir;
|
||||||
|
@ -34,6 +35,7 @@ use tokio::{fs, io};
|
||||||
use tokio_rustls::TlsAcceptor;
|
use tokio_rustls::TlsAcceptor;
|
||||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||||
use tokio_util::io::{ReaderStream, StreamReader};
|
use tokio_util::io::{ReaderStream, StreamReader};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
type Request = hyper::Request<Body>;
|
type Request = hyper::Request<Body>;
|
||||||
type Response = hyper::Response<Body>;
|
type Response = hyper::Response<Body>;
|
||||||
|
@ -364,7 +366,10 @@ impl InnerService {
|
||||||
|
|
||||||
async fn handle_zip_dir(&self, path: &Path, res: &mut Response) -> BoxResult<()> {
|
async fn handle_zip_dir(&self, path: &Path, res: &mut Response) -> BoxResult<()> {
|
||||||
let (mut writer, reader) = tokio::io::duplex(BUF_SIZE);
|
let (mut writer, reader) = tokio::io::duplex(BUF_SIZE);
|
||||||
let filename = path.file_name().unwrap().to_str().unwrap();
|
let filename = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|v| v.to_str())
|
||||||
|
.ok_or_else(|| format!("Failed to get name of `{}`", path.display()))?;
|
||||||
let path = path.to_owned();
|
let path = path.to_owned();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = zip_dir(&mut writer, &path).await {
|
if let Err(e) = zip_dir(&mut writer, &path).await {
|
||||||
|
@ -509,7 +514,7 @@ impl InnerService {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => 1,
|
None => 0,
|
||||||
};
|
};
|
||||||
let mut paths = vec![self.to_pathitem(path, &self.args.path).await?.unwrap()];
|
let mut paths = vec![self.to_pathitem(path, &self.args.path).await?.unwrap()];
|
||||||
if depth > 0 {
|
if depth > 0 {
|
||||||
|
@ -598,22 +603,26 @@ impl InnerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_lock(&self, req_path: &str, res: &mut Response) -> BoxResult<()> {
|
async fn handle_lock(&self, req_path: &str, res: &mut Response) -> BoxResult<()> {
|
||||||
let now = Utc::now().timestamp();
|
let token = if self.args.auth.is_none() {
|
||||||
|
Utc::now().timestamp().to_string()
|
||||||
|
} else {
|
||||||
|
format!("opaquelocktoken:{}", Uuid::new_v4())
|
||||||
|
};
|
||||||
|
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
"content-type",
|
"content-type",
|
||||||
"application/xml; charset=utf-8".parse().unwrap(),
|
"application/xml; charset=utf-8".parse().unwrap(),
|
||||||
);
|
);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert("lock-token", format!("<{}>", now).parse().unwrap());
|
.insert("lock-token", format!("<{}>", token).parse().unwrap());
|
||||||
|
|
||||||
*res.body_mut() = Body::from(format!(
|
*res.body_mut() = Body::from(format!(
|
||||||
r#"<?xml version="1.0" encoding="utf-8"?>
|
r#"<?xml version="1.0" encoding="utf-8"?>
|
||||||
<D:prop xmlns:D="DAV:"><D:lockdiscovery><D:activelock>
|
<D:prop xmlns:D="DAV:"><D:lockdiscovery><D:activelock>
|
||||||
<D:locktype><D:write/></D:locktype>
|
|
||||||
<D:lockscope><D:exclusive/></D:lockscope>
|
|
||||||
<D:locktoken><D:href>{}</D:href></D:locktoken>
|
<D:locktoken><D:href>{}</D:href></D:locktoken>
|
||||||
<D:lockroot><D:href>{}</D:href></D:lockroot>
|
<D:lockroot><D:href>{}</D:href></D:lockroot>
|
||||||
</D:activelock></D:lockdiscovery></D:prop>"#,
|
</D:activelock></D:lockdiscovery></D:prop>"#,
|
||||||
now, req_path
|
token, req_path
|
||||||
));
|
));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -656,29 +665,29 @@ impl InnerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_guard(&self, req: &Request, res: &mut Response) -> bool {
|
fn auth_guard(&self, req: &Request, res: &mut Response) -> bool {
|
||||||
|
let method = req.method();
|
||||||
let pass = {
|
let pass = {
|
||||||
match &self.args.auth {
|
match &self.args.auth {
|
||||||
None => true,
|
None => true,
|
||||||
Some(auth) => match req.headers().get(AUTHORIZATION) {
|
Some((user, pass)) => match req.headers().get(AUTHORIZATION) {
|
||||||
Some(value) => match value.to_str().ok().map(|v| {
|
Some(value) => {
|
||||||
let mut it = v.split(' ');
|
valid_digest(value, method.as_str(), user.as_str(), pass.as_str()).is_some()
|
||||||
(it.next(), it.next())
|
}
|
||||||
}) {
|
None => {
|
||||||
Some((Some("Basic"), Some(tail))) => base64::decode(tail)
|
self.args.no_auth_access
|
||||||
.ok()
|
&& (method == Method::GET
|
||||||
.and_then(|v| String::from_utf8(v).ok())
|
|| method == Method::OPTIONS
|
||||||
.map(|v| v.as_str() == auth)
|
|| method == Method::HEAD
|
||||||
.unwrap_or_default(),
|
|| method.as_str() == "PROPFIND")
|
||||||
_ => false,
|
}
|
||||||
},
|
|
||||||
None => self.args.no_auth_access && req.method() == Method::GET,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !pass {
|
if !pass {
|
||||||
|
let value = generate_www_auth(false);
|
||||||
status!(res, StatusCode::UNAUTHORIZED);
|
status!(res, StatusCode::UNAUTHORIZED);
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(WWW_AUTHENTICATE, HeaderValue::from_static("Basic"));
|
.insert(WWW_AUTHENTICATE, value.parse().unwrap());
|
||||||
}
|
}
|
||||||
pass
|
pass
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue