14 Commits
v1.0.4 ... main

Author SHA1 Message Date
dc598b05ff update deps 2023-07-17 18:15:43 +08:00
2381628fc4 Merge pull request #6 from Tim-Paik/dependabot/cargo/h2-0.3.17
Bump h2 from 0.3.15 to 0.3.17
2023-04-14 01:11:16 +08:00
93a2457ad6 Bump h2 from 0.3.15 to 0.3.17
Bumps [h2](https://github.com/hyperium/h2) from 0.3.15 to 0.3.17.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.15...v0.3.17)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-13 17:03:44 +00:00
eabccd2c43 Merge pull request #5 from Tim-Paik/dependabot/cargo/comrak-0.17.0
Bump comrak from 0.14.0 to 0.17.0
2023-03-28 22:49:05 +08:00
49c9a6d128 Bump comrak from 0.14.0 to 0.17.0
Bumps [comrak](https://github.com/kivikakk/comrak) from 0.14.0 to 0.17.0.
- [Release notes](https://github.com/kivikakk/comrak/releases)
- [Changelog](https://github.com/kivikakk/comrak/blob/main/changelog.txt)
- [Commits](https://github.com/kivikakk/comrak/compare/0.14.0...0.17.0)

---
updated-dependencies:
- dependency-name: comrak
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-28 14:42:08 +00:00
d2a1eedfbb Merge pull request #4 from Tim-Paik/dependabot/cargo/tokio-1.25.0
Bump tokio from 1.24.1 to 1.25.0
2023-02-07 11:39:28 +08:00
931ac2707f Bump tokio from 1.24.1 to 1.25.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.24.1 to 1.25.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.24.1...tokio-1.25.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-04 00:57:42 +00:00
9ad17590a1 Merge pull request #3 from Tim-Paik/dependabot/cargo/tokio-1.24.1
Bump tokio from 1.21.2 to 1.24.1
2023-01-08 14:16:35 +08:00
29647d06c1 Bump tokio from 1.21.2 to 1.24.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.21.2 to 1.24.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.21.2...tokio-1.24.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-06 21:51:56 +00:00
d4714cd8f5 Release 1.1.0 2022-11-13 17:27:25 +08:00
91f9993315 update deps 2022-11-13 17:02:39 +08:00
fc809f9bc9 v1.0.5 2022-07-28 15:09:02 +08:00
479f6006a6 update deps 2022-07-28 15:06:33 +08:00
c15c09f07c add readme support 2022-07-28 01:39:51 +08:00
5 changed files with 883 additions and 445 deletions

1069
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,24 +3,25 @@ authors = ["Tim_Paik <timpaikc@outlook.com>"]
description = "simple http server written in rust"
edition = "2018"
name = "srv"
version = "1.0.4"
version = "1.1.0"
[dependencies]
actix-files = "0.6"
actix-web = {version = "4.1", features = ["rustls"]}
actix-web-httpauth = "0.7"
askama = "0.11"
askama_actix = "0.13"
clap = {version = "3.2", features = ["wrap_help", "color", "cargo"]}
env_logger = "0.9"
actix-web = { version = "4.3", features = ["rustls"] }
actix-web-httpauth = "0.8"
askama = "0.12"
askama_actix = "0.14"
clap = { version = "4.3", features = ["derive", "wrap_help", "color", "cargo"] }
comrak = { version = "0.18", default-features = false }
env_logger = "0.10"
log = "0.4"
mime_guess = "2.0"
rustls = "0.20"
rustls-pemfile = "1.0"
serde = {version = "1.0", features = ["derive"]}
serde = { version = "1.0", features = ["derive"] }
sha2 = "0.10"
time = { version = "0.3", features = ["formatting", "parsing"] }
toml = "0.5"
toml = "0.7"
urlencoding = "2.1"
[profile.release]

View File

@ -14,7 +14,7 @@ use actix_web_httpauth::{
middleware::HttpAuthentication,
};
use askama_actix::TemplateToResponse;
use clap::Arg;
use clap::{arg, command, ArgAction};
use env_logger::fmt::Color;
use log::{error, info};
use serde::{Deserialize, Serialize};
@ -22,11 +22,11 @@ use sha2::Digest;
use std::{
borrow::Cow,
env::{set_var, var},
fs::{self, metadata, read_dir},
fs::{self, metadata, read_dir, read_to_string},
io::{self, BufReader, Read, Write},
net::IpAddr,
path::{Path, PathBuf},
process::Command,
process::{Command, Stdio},
str::FromStr,
};
use time::OffsetDateTime;
@ -60,6 +60,7 @@ struct File {
#[derive(Serialize)]
struct IndexContext {
title: String,
readme: String,
paths: Vec<String>,
dirs: Vec<Dir>,
files: Vec<File>,
@ -86,6 +87,7 @@ fn render_index(
let show_dot_files = var("DOTFILES").unwrap_or_else(|_| "false".to_string()) == "true";
let mut context = IndexContext {
title: "".to_string(),
readme: "".to_string(),
paths: vec![],
dirs: vec![],
files: vec![],
@ -98,6 +100,7 @@ fn render_index(
let path = path.into_owned();
context.paths.push(path);
}
let mut readme_str = "".to_string();
match read_dir(&dir.path) {
Err(e) => {
error!(target: "read_dir", "[ERROR] Read dir error: {}", e.to_string());
@ -150,10 +153,47 @@ fn render_index(
filetype,
modified,
});
if path.file_name().to_ascii_lowercase() == "readme.md" {
readme_str = read_to_string(path.path()).unwrap_or_else(|_| "".to_string());
}
}
}
}
}
if var("NOREADME").unwrap_or_else(|_| "false".to_string()) != "true" {
context.readme = comrak::markdown_to_html(
&readme_str,
&comrak::ComrakOptions {
extension: comrak::ComrakExtensionOptions {
strikethrough: true,
tagfilter: true,
table: true,
autolink: true,
tasklist: true,
superscript: true,
header_ids: None,
footnotes: true,
description_lists: true,
front_matter_delimiter: None,
},
parse: comrak::ComrakParseOptions {
smart: false,
default_info_string: None,
relaxed_tasklist_matching: true,
},
render: comrak::ComrakRenderOptions {
hardbreaks: false,
github_pre_lang: false,
width: 1000,
unsafe_: true,
escape: false,
list_style: comrak::ListStyleType::default(),
full_info_string: true,
sourcepos: false,
},
},
);
}
context.title = context.paths.last().unwrap_or(&"/".to_string()).to_string();
context.dirs.sort();
context.files.sort();
@ -202,7 +242,7 @@ async fn main() -> io::Result<()> {
let check_does_dir_exits = |path: &str| match metadata(path) {
Ok(meta) => {
if meta.is_dir() {
Ok(())
Ok(path.to_string())
} else {
Err("Parameter is not a directory".to_owned())
}
@ -212,7 +252,7 @@ async fn main() -> io::Result<()> {
let check_does_file_exits = |path: &str| match metadata(path) {
Ok(metadata) => {
if metadata.is_file() {
Ok(())
Ok(path.to_string())
} else {
Err("Parameter is not a file".to_owned())
}
@ -220,11 +260,11 @@ async fn main() -> io::Result<()> {
Err(e) => Err(e.to_string()),
};
let check_is_ip_addr = |s: &str| match IpAddr::from_str(s) {
Ok(_) => Ok(()),
Ok(_) => Ok(s.to_string()),
Err(e) => Err(e.to_string()),
};
let check_is_port_num = |s: &str| match s.parse::<u16>() {
Ok(_) => Ok(()),
Ok(_) => Ok(s.to_string()),
Err(e) => Err(e.to_string()),
};
let check_is_auth = |s: &str| {
@ -234,81 +274,90 @@ async fn main() -> io::Result<()> {
} else if parts[0].is_empty() {
Err("Username not found".to_owned())
} else {
Ok(())
Ok(s.to_string())
}
};
let matches = clap::command!()
.arg(Arg::new("noindex").long("noindex").help("Disable automatic index page generation"))
.arg(Arg::new("nocache").long("nocache").help("Disable HTTP cache"))
.arg(Arg::new("nocolor").long("nocolor").help("Disable cli colors"))
.arg(Arg::new("cors").long("cors").takes_value(true).min_values(0).max_values(1).help("Enable CORS [with custom value]"))
.arg(Arg::new("spa").long("spa").help("Enable Single-Page Application mode (always serve /index.html when the file is not found)"))
.arg(Arg::new("dotfiles").short('d').long("dotfiles").help("Show dotfiles"))
.arg(Arg::new("open").short('o').long("open").help("Open the page in the default browser"))
.arg(Arg::new("quiet").short('q').long("quiet").help("Disable access log output"))
.arg(Arg::new("quietall").long("quietall").help("Disable all output"))
.arg(Arg::new("ROOT").default_value(".").validator(check_does_dir_exits).help("Root directory"))
.arg(Arg::new("address").short('a').long("address").default_value("0.0.0.0").takes_value(true).validator(check_is_ip_addr).help("IP address to serve on"))
.arg(Arg::new("port").short('p').long("port").default_value("8000").takes_value(true).validator(check_is_port_num).help("Port to serve on"))
.arg(Arg::new("auth").long("auth").takes_value(true).validator(check_is_auth).help("HTTP Auth (username:password)"))
.arg(Arg::new("cert").long("cert").takes_value(true).validator(check_does_file_exits).help("Path of TLS/SSL public key (certificate)"))
.arg(Arg::new("key").long("key").takes_value(true).validator(check_does_file_exits).help("Path of TLS/SSL private key"))
let matches = command!()
.arg(arg!(--noindex "Disable automatic index page generation").required(false))
.arg(arg!(--noreadme "Disable automatic readme rendering").required(false))
.arg(arg!(--nocache "Disable HTTP cache").required(false))
.arg(arg!(--nocolor "Disable cli colors").required(false))
.arg(arg!(--cors [hostname] "Enable CORS [with custom value]").required(false).action(ArgAction::Append))
.arg(arg!(--spa "Enable Single-Page Application mode (always serve /index.html when the file is not found)").required(false))
.arg(arg!(-d --dotfiles "Show dotfiles").required(false))
.arg(arg!(-o --open "Open the page in the default browser").required(false))
.arg(arg!(-q --quiet "Disable access log output").required(false))
.arg(arg!(--quietall "Disable all output").required(false))
.arg(arg!([root] "Root directory").default_value(".").value_parser(check_does_dir_exits))
.arg(arg!(-a --address <ipaddr> "IP address to serve on").default_value("0.0.0.0").value_parser(check_is_ip_addr))
.arg(arg!(-p --port <port> "Port to serve on").default_value("8000").value_parser(check_is_port_num))
.arg(arg!(--auth <pattern> "HTTP Auth (username:password)").required(false).value_parser(check_is_auth))
.arg(arg!(--cert <path> "Path of TLS/SSL public key (certificate)").required(false).value_parser(check_does_file_exits))
.arg(arg!(--key <path> "Path of TLS/SSL private key").required(false).value_parser(check_does_file_exits))
.subcommand(clap::Command::new("doc")
.about("Open cargo doc via local server (Need cargo installation)")
.arg(Arg::new("nocolor").long("nocolor").help("Disable cli colors"))
.arg(Arg::new("noopen").long("noopen").help("Do not open the page in the default browser"))
.arg(Arg::new("log").long("log").help("Enable access log output [default: disabled]"))
.arg(Arg::new("quietall").long("quietall").help("Disable all output"))
.arg(Arg::new("address").short('a').long("address").default_value("0.0.0.0").takes_value(true).validator(check_is_ip_addr).help("IP address to serve on"))
.arg(Arg::new("port").short('p').long("port").default_value("8000").takes_value(true).validator(check_is_port_num).help("Port to serve on"))
.arg(arg!(--nocolor "Disable cli colors"))
.arg(arg!(--noopen "Do not open the page in the default browser"))
.arg(arg!(--log "Enable access log output [default: disabled]"))
.arg(arg!(--quietall "Disable all output"))
.arg(arg!(-a --address <ipaddr> "IP address to serve on").required(false).default_value("0.0.0.0").value_parser(check_is_ip_addr))
.arg(arg!(-p --port <port> "Port to serve on").required(false).default_value("8000").value_parser(check_is_port_num))
)
.get_matches();
set_var(
"ROOT",
display_path(Path::new(matches.value_of("ROOT").unwrap_or("."))),
display_path(Path::new(
matches
.get_one::<String>("root")
.unwrap_or(&".".to_string()),
)),
);
set_var("NOINDEX", matches.is_present("noindex").to_string());
set_var("SPA", matches.is_present("spa").to_string());
set_var("DOTFILES", matches.is_present("dotfiles").to_string());
set_var("NOCACHE", matches.is_present("nocache").to_string());
set_var("NOINDEX", matches.get_flag("noindex").to_string());
set_var("NOREADME", matches.get_flag("noreadme").to_string());
set_var("SPA", matches.get_flag("spa").to_string());
set_var("DOTFILES", matches.get_flag("dotfiles").to_string());
set_var("NOCACHE", matches.get_flag("nocache").to_string());
if matches.is_present("quiet") {
if matches.get_flag("quiet") {
set_var("RUST_LOG", "info,actix_web::middleware::logger=off");
}
if matches.is_present("quietall") {
if matches.get_flag("quietall") {
set_var("RUST_LOG", "off");
}
if matches.is_present("nocolor") {
if matches.get_flag("nocolor") {
set_var("RUST_LOG_STYLE", "never");
}
if let Some(s) = matches.value_of("auth") {
set_var("ENABLE_AUTH", matches.is_present("auth").to_string());
if let Some(s) = matches.get_one::<String>("auth") {
set_var("ENABLE_AUTH", matches.get_flag("auth").to_string());
let parts = s.splitn(2, ':').collect::<Vec<&str>>();
set_var("AUTH_USERNAME", parts[0]);
set_var("AUTH_PASSWORD", hash(parts[1]));
}
if matches.is_present("cors") {
set_var("ENABLE_CORS", matches.is_present("cors").to_string());
match matches.value_of("cors") {
Some(str) => {
set_var("CORS", str);
}
None => {
set_var("CORS", "*");
}
if let Some(mut cors) = matches.get_many::<String>("cors") {
set_var("ENABLE_CORS", "true");
match cors.next() {
Some(value) => set_var("CORS", value),
None => set_var("CORS", "*"),
}
}
let enable_tls = matches.is_present("cert") && matches.is_present("key");
let enable_tls =
matches.get_one::<String>("cert").is_some() && matches.get_one::<String>("key").is_some();
let ip = matches
.value_of("address")
.unwrap_or("127.0.0.1")
.get_one::<String>("address")
.unwrap_or(&"127.0.0.1".to_string())
.to_string();
let addr = format!("{}:{}", ip, matches.value_of("port").unwrap_or("8000"));
let addr = format!(
"{}:{}",
ip,
matches
.get_one::<String>("port")
.unwrap_or(&"8000".to_string())
);
let url = format!(
"{}{}:{}",
if enable_tls {
@ -317,7 +366,9 @@ async fn main() -> io::Result<()> {
"http://".to_string()
},
if ip == "0.0.0.0" { "127.0.0.1" } else { &ip },
matches.value_of("port").unwrap_or("8000")
matches
.get_one::<String>("port")
.unwrap_or(&"8000".to_string())
);
let open_in_browser = |url: &str| {
@ -336,18 +387,18 @@ async fn main() -> io::Result<()> {
}
};
if matches.is_present("open") {
if matches.get_flag("open") {
open_in_browser(&url);
}
if let Some(matches) = matches.subcommand_matches("doc") {
if !matches.is_present("log") {
if !matches.get_flag("log") {
set_var("RUST_LOG", "info,actix_web::middleware::logger=off");
}
if matches.is_present("quietall") {
if matches.get_flag("quietall") {
set_var("RUST_LOG", "off");
}
if matches.is_present("nocolor") {
if matches.get_flag("nocolor") {
set_var("RUST_LOG_STYLE", "never");
}
}
@ -460,17 +511,19 @@ async fn main() -> io::Result<()> {
};
let crate_name = contents.package.name;
info!("[INFO] Generating document (may take a while)");
match Command::new("cargo").arg("doc").output() {
Ok(output) => {
let output = std::str::from_utf8(&output.stderr).unwrap_or("");
if output.starts_with("error: could not find `Cargo.toml` in") {
error!("[ERROR] Cargo.toml Not Found");
return Ok(());
} else if output.starts_with("error: ") {
error!(
"[ERROR] {}",
output.strip_prefix("error: ").unwrap_or(output)
);
match Command::new("cargo")
.arg("doc")
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
{
Ok(status) => {
if !status.success() {
match status.code() {
Some(code) => error!("[ERROR] Cargo exited with status code: {code}"),
None => error!("[ERROR] Cargo terminated by signal"),
}
return Ok(());
}
}
@ -488,17 +541,25 @@ async fn main() -> io::Result<()> {
}
set_var("ROOT", display_path(path));
let ip = matches
.value_of("address")
.unwrap_or("127.0.0.1")
.get_one::<String>("address")
.unwrap_or(&"127.0.0.1".to_string())
.to_string();
let addr = format!("{}:{}", ip, matches.value_of("port").unwrap_or("8000"));
let addr = format!(
"{}:{}",
ip,
matches
.get_one::<String>("port")
.unwrap_or(&"8000".to_string())
);
let url = format!(
"http://{}:{}/{}/index.html",
if ip == "0.0.0.0" { "127.0.0.1" } else { &ip },
matches.value_of("port").unwrap_or("8000"),
matches
.get_one::<String>("port")
.unwrap_or(&"8000".to_string()),
crate_name,
);
if !matches.is_present("noopen") {
if !matches.get_flag("noopen") {
open_in_browser(&url);
}
addr
@ -576,10 +637,10 @@ async fn main() -> io::Result<()> {
});
let server = if enable_tls {
let cert = &mut BufReader::new(
fs::File::open(Path::new(matches.value_of("cert").unwrap())).unwrap(),
fs::File::open(Path::new(matches.get_one::<String>("cert").unwrap())).unwrap(),
);
let key = &mut BufReader::new(
fs::File::open(Path::new(matches.value_of("key").unwrap())).unwrap(),
fs::File::open(Path::new(matches.get_one::<String>("key").unwrap())).unwrap(),
);
let cert = rustls_pemfile::certs(cert)
.unwrap()

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/. #}
# file, You can obtain one at https://mozilla.org/MPL/2.0/. -#}
<!DOCTYPE html>
<html lang="en">
@ -145,7 +145,7 @@
#meta,
#listing {
color: #cacaca;
background: #000000;
background: #0d1117;
}
#header nav span a {
@ -154,7 +154,7 @@
#header {
padding: 1.5rem 5% 1rem;
background-color: #0e0e0e;
background-color: #161b22;
}
#listing table {
@ -313,6 +313,26 @@
<div style="text-align: center; margin: 1rem; color: #cccccc;">Nothing here</div>
{% endif -%}
</div>
{% if readme != "".to_string() -%}
<div id="readme">
{{ readme|safe }}
</div>
{% include "github-markdown.css.html" %}
<style>
#readme {
min-width: 200px;
max-width: 980px;
margin: 10px auto;
padding: 45px;
}
@media (max-width: 767px) {
#readme {
padding: 15px;
}
}
</style>
{% endif -%}
</main>
<script>
(function () {