Compare commits

..

4 Commits

Author SHA1 Message Date
sigoden
870e92e306 chore(release): version v0.14.0 2022-06-07 09:02:43 +08:00
sigoden
261c8b6ee5 feat: add favicon (#27)
Return favicon only if requested, avoid 404 errors

close #16
2022-06-07 08:59:44 +08:00
sigoden
5ce7bde05c fix: send index page with content-type (#26) 2022-06-06 11:20:42 +08:00
sigoden
63a7b530bb feat: support ipv6 (#25) 2022-06-06 10:52:12 +08:00
7 changed files with 79 additions and 70 deletions

View File

@@ -145,7 +145,7 @@ jobs:
uses: docker/build-push-action@v2
with:
push: ${{ needs.release.outputs.rc == 'false' }}
tags: sigoden/duf:latest, sigoden/duf:${{ github.ref_name }}
tags: ${{ github.repository }}:latest, ${{ github.repository }}:${{ github.ref_name }}
publish-crate:
name: Publish to crates.io

View File

@@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file.
## [0.14.0] - 2022-06-07
### Bug Fixes
- Send index page with content-type ([#26](https://github.com/sigoden/duf/issues/26))
### Features
- Support ipv6 ([#25](https://github.com/sigoden/duf/issues/25))
- Add favicon ([#27](https://github.com/sigoden/duf/issues/27))
## [0.13.2] - 2022-06-06
### Bug Fixes

56
Cargo.lock generated
View File

@@ -286,7 +286,7 @@ dependencies = [
[[package]]
name = "duf"
version = "0.13.2"
version = "0.14.0"
dependencies = [
"async-walkdir",
"async_zip",
@@ -343,13 +343,11 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.23"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
dependencies = [
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide",
]
@@ -554,9 +552,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.7"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
dependencies = [
"bytes",
"fnv",
@@ -681,9 +679,9 @@ dependencies = [
[[package]]
name = "lzma-sys"
version = "0.1.17"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb4b7c3eddad11d3af9e86c487607d2d2442d185d848575365c4856ba96d619"
checksum = "e06754c4acf47d49c727d5665ca9fb828851cda315ed3bd51edd148ef78a8772"
dependencies = [
"cc",
"libc",
@@ -720,9 +718,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.5.1"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
@@ -991,9 +989,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "syn"
version = "1.0.95"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
dependencies = [
"proc-macro2",
"quote",
@@ -1039,9 +1037,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.18.2"
version = "1.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
dependencies = [
"bytes",
"libc",
@@ -1058,9 +1056,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "1.7.0"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
dependencies = [
"proc-macro2",
"quote",
@@ -1080,9 +1078,9 @@ dependencies = [
[[package]]
name = "tokio-stream"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9"
dependencies = [
"futures-core",
"pin-project-lite",
@@ -1091,9 +1089,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c"
checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
dependencies = [
"bytes",
"futures-core",
@@ -1117,21 +1115,9 @@ checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.26"
@@ -1377,9 +1363,9 @@ checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
[[package]]
name = "xz2"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c179869f34fc7c01830d3ce7ea2086bc3a07e0d35289b667d0a8bf910258926c"
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
dependencies = [
"lzma-sys",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "duf"
version = "0.13.2"
version = "0.14.0"
edition = "2021"
authors = ["sigoden <sigoden@gmail.com>"]
description = "Duf is a simple file server."

BIN
assets/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,7 +1,7 @@
use clap::crate_description;
use clap::{Arg, ArgMatches};
use rustls::{Certificate, PrivateKey};
use std::net::SocketAddr;
use std::net::{IpAddr, SocketAddr};
use std::path::{Path, PathBuf};
use std::{env, fs, io};
@@ -111,8 +111,7 @@ pub fn matches() -> ArgMatches {
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Args {
pub address: String,
pub port: u16,
pub addr: SocketAddr,
pub path: PathBuf,
pub path_prefix: String,
pub uri_prefix: String,
@@ -133,8 +132,9 @@ impl Args {
/// If a parsing error ocurred, exit the process and print out informative
/// error message to user.
pub fn parse(matches: ArgMatches) -> BoxResult<Args> {
let address = matches.value_of("address").unwrap_or_default().to_owned();
let ip = matches.value_of("address").unwrap_or_default();
let port = matches.value_of_t::<u16>("port")?;
let addr = to_addr(ip, port)?;
let path = Args::parse_path(matches.value_of_os("path").unwrap_or_default())?;
let path_prefix = matches
.value_of("path-prefix")
@@ -166,8 +166,7 @@ impl Args {
};
Ok(Args {
address,
port,
addr,
path,
path_prefix,
uri_prefix,
@@ -197,17 +196,15 @@ impl Args {
})
.map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into())
}
/// Construct socket address from arguments.
pub fn address(&self) -> BoxResult<SocketAddr> {
format!("{}:{}", self.address, self.port)
.parse()
.map_err(|_| format!("Invalid bind address `{}:{}`", self.address, self.port).into())
}
fn to_addr(ip: &str, port: u16) -> BoxResult<SocketAddr> {
let ip: IpAddr = ip.parse()?;
Ok(SocketAddr::new(ip, port))
}
// Load public certificate from file.
pub fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
// Open certificate file.
let certfile =
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;
@@ -222,7 +219,7 @@ pub fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
}
// Load private key from file.
pub fn load_private_key(filename: &str) -> BoxResult<PrivateKey> {
fn load_private_key(filename: &str) -> BoxResult<PrivateKey> {
// Open keyfile.
let keyfile =
fs::File::open(&filename).map_err(|e| format!("Failed to open {}: {}", &filename, e))?;

View File

@@ -25,7 +25,7 @@ use rustls::ServerConfig;
use serde::Serialize;
use std::convert::Infallible;
use std::fs::Metadata;
use std::net::IpAddr;
use std::net::{IpAddr, SocketAddr};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::SystemTime;
@@ -44,6 +44,7 @@ type Response = hyper::Response<Body>;
const INDEX_HTML: &str = include_str!("../assets/index.html");
const INDEX_CSS: &str = include_str!("../assets/index.css");
const INDEX_JS: &str = include_str!("../assets/index.js");
const FAVICON_ICO: &[u8] = include_bytes!("../assets/favicon.ico");
const INDEX_NAME: &str = "index.html";
const BUF_SIZE: usize = 1024 * 16;
@@ -56,7 +57,6 @@ macro_rules! status {
pub async fn serve(args: Args) -> BoxResult<()> {
let args = Arc::new(args);
let socket_addr = args.address()?;
let inner = Arc::new(InnerService::new(args.clone()));
match args.tls.clone() {
Some((certs, key)) => {
@@ -66,7 +66,7 @@ pub async fn serve(args: Args) -> BoxResult<()> {
.with_single_cert(certs, key)?;
let tls_acceptor = TlsAcceptor::from(Arc::new(config));
let arc_acceptor = Arc::new(tls_acceptor);
let listener = TcpListener::bind(&socket_addr).await?;
let listener = TcpListener::bind(&args.addr).await?;
let incoming = tokio_stream::wrappers::TcpListenerStream::new(listener);
let incoming =
hyper::server::accept::from_stream(incoming.filter_map(|socket| async {
@@ -87,11 +87,11 @@ pub async fn serve(args: Args) -> BoxResult<()> {
}))
}
}));
print_listening(args.address.as_str(), args.port, &args.uri_prefix, true);
print_listening(&args.addr, &args.uri_prefix, true);
server.await?;
}
None => {
let server = hyper::Server::try_bind(&socket_addr)?.serve(make_service_fn(move |_| {
let server = hyper::Server::try_bind(&args.addr)?.serve(make_service_fn(move |_| {
let inner = inner.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
@@ -100,7 +100,7 @@ pub async fn serve(args: Args) -> BoxResult<()> {
}))
}
}));
print_listening(args.address.as_str(), args.port, &args.uri_prefix, false);
print_listening(&args.addr, &args.uri_prefix, false);
server.await?;
}
}
@@ -175,6 +175,12 @@ impl InnerService {
status!(res, StatusCode::NOT_FOUND);
return Ok(res);
}
if is_miss && path.ends_with("favicon.ico") {
*res.body_mut() = Body::from(FAVICON_ICO);
res.headers_mut()
.insert("content-type", "image/x-icon".parse().unwrap());
return Ok(res);
}
let headers = req.headers();
@@ -667,7 +673,8 @@ impl InnerService {
),
);
*res.body_mut() = output.into();
res.headers_mut()
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
Ok(())
}
@@ -974,37 +981,45 @@ fn to_content_range(range: &Range, complete_length: u64) -> Option<ContentRange>
})
}
fn print_listening(address: &str, port: u16, prefix: &str, tls: bool) {
fn print_listening(addr: &SocketAddr, prefix: &str, tls: bool) {
let prefix = encode_uri(prefix.trim_end_matches('/'));
let addrs = retrieve_listening_addrs(address);
let addrs = retrieve_listening_addrs(addr);
let protocol = if tls { "https" } else { "http" };
if addrs.len() == 1 {
eprintln!(
"Listening on {}://{}:{}{}",
protocol, addrs[0], port, prefix
);
eprintln!("Listening on {}://{}{}", protocol, addr, prefix);
} else {
eprintln!("Listening on:");
for addr in addrs {
eprintln!(" {}://{}:{}{}", protocol, addr, port, prefix);
eprintln!(" {}://{}{}", protocol, addr, prefix);
}
eprintln!();
}
}
fn retrieve_listening_addrs(address: &str) -> Vec<String> {
if address == "0.0.0.0" {
fn retrieve_listening_addrs(addr: &SocketAddr) -> Vec<SocketAddr> {
let ip = addr.ip();
let port = addr.port();
if ip.is_unspecified() {
if let Ok(interfaces) = get_if_addrs() {
let mut ifaces: Vec<IpAddr> = interfaces
.into_iter()
.map(|v| v.ip())
.filter(|v| v.is_ipv4())
.filter(|v| {
if ip.is_ipv4() {
v.is_ipv4()
} else {
v.is_ipv6()
}
})
.collect();
ifaces.sort();
return ifaces.into_iter().map(|v| v.to_string()).collect();
return ifaces
.into_iter()
.map(|v| SocketAddr::new(v, port))
.collect();
}
}
vec![address.to_owned()]
vec![addr.to_owned()]
}
fn encode_uri(v: &str) -> String {