mirror of
https://github.com/Tim-Paik/srv.git
synced 2024-10-13 00:29:43 +00:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
dc598b05ff | |||
2381628fc4 | |||
93a2457ad6 | |||
eabccd2c43 | |||
49c9a6d128 | |||
d2a1eedfbb | |||
931ac2707f | |||
9ad17590a1 | |||
29647d06c1 | |||
d4714cd8f5
|
|||
91f9993315
|
|||
fc809f9bc9
|
|||
479f6006a6
|
|||
c15c09f07c
|
1069
Cargo.lock
generated
1069
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
19
Cargo.toml
19
Cargo.toml
@ -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]
|
||||
|
213
src/main.rs
213
src/main.rs
@ -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()
|
||||
|
1
templates/github-markdown.css.html
Normal file
1
templates/github-markdown.css.html
Normal file
File diff suppressed because one or more lines are too long
@ -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 () {
|
||||
|
Reference in New Issue
Block a user