serve-cli 1.0.4

This is a file serving and file upload service in the local area network (LAN) CLI
Documentation
#![feature(async_closure)]
#![feature(decl_macro)]
use lazy_static::lazy_static;
use structopt::StructOpt;

/// Command line arguments for the program
#[derive(Debug, StructOpt, Clone, PartialEq)]
#[structopt(
    author = "Anonymous",
    about = "Serve files in the specified directory and subdirectories"
)]
struct Arguments {
    /// The port that the server should bind to
    #[structopt(long, short, default_value = "8787")]
    pub port: u16,

    /// Whether additional data should be logged to the console
    #[structopt(long, short)]
    pub verbose: bool,

    /// Deploy on localhost, rather than attempting to bind to the external interface
    #[structopt(long, short)]
    pub localhost: bool,

    /// The root directory that should be served by the program
    #[structopt(default_value = ".")]
    pub folder: String,
}

lazy_static! {
  /// The command line arguments passed into the program
  static ref ARGUMENTS: Arguments = Arguments::from_args();
}

#[allow(warnings)]
pub mod file_show_server{
  use std::borrow::Cow;
  use std::fs;
  use std::net::{IpAddr, SocketAddr};
  use std::path::PathBuf;
  use rocket::http::hyper::Request;
  use rocket::response::content;
  use rocket::{get, Response, routes, Config, catch, catchers, Responder, post, UriDisplayPath};
  use rocket::http::ContentType;
  use rocket_contrib::json::{Json as JSON, JsonValue};
  use rocket::Data;
  use tokio::io::AsyncReadExt;
  use uuid::Uuid;
  use std::io::{self, Write};
  use rocket::http::Status;
  use rocket::request::{FromRequest, Outcome};
  use crate::ARGUMENTS;
  use crate::file_show_routes::routes;
  use log::info;

  pub async fn start(){
    let ip_addr: IpAddr =  match local_ipaddress::get() {
      Some(x)=>{
          if ARGUMENTS.localhost{
              IpAddr::from([127, 0, 0, 1])
          }else{
          x.parse().expect("Found local IP Address is invalid")
        }},
        None=>IpAddr::from([127, 0, 0, 1])
    };

    let socket_addr = SocketAddr::from((ip_addr, ARGUMENTS.port));
    info!("Server listening on http://{}", socket_addr);

    warp::serve(routes()).bind(socket_addr).await;
  }
}

pub mod file_upload_server{
use log::info;
use rocket::{Data, post, routes, Rocket, get, response::content::Json};
use rocket_contrib::serve::StaticFiles;
use std::{fs, path::PathBuf, net::{IpAddr, SocketAddr}};

#[allow(warnings)]
#[post("/", data = "<data>")]
fn upload(data: Data) -> Result<Json<&'static str>, std::io::Error> {
    let path = "./stream.data";
    let pathbuf = PathBuf::from(path);
    if pathbuf.exists() {
        fs::remove_file(pathbuf.clone()).unwrap();
    }
    let mut file = fs::File::create(path)?;

    data.stream_to_file(path)
        .map(|n| format!("Wrote {} bytes to stream.data", n)).unwrap();

    if let Ok(buf) = fs::read(path) {
        if !buf.starts_with(&[0x2D,0x2D,0x2D,0x2D,0x2D,0x2D]){
        if let Some(ex) = get_file_extension(&buf) {
            fs::copy(path, format!("./stream.{}", ex)).unwrap();
        } else {
            fs::copy(path, format!("./stream")).unwrap();
        }
        }else{
          use doe::*;
        let mut buf = buf.clone();
        let remove_last = buf.split_at(buf.len()-46).0.to_vec();
        let remove_first_contet = remove_last.split_at(97).1;
        let (head,other) = remove_first_contet.split_at(200);
        let s = vec_element_to_string!(head).join(":");
        let file_name = vec_element_clone!(split_to_vec!(String::from_utf8_lossy(head),"\""),0);
        let s = split_to_vec!(s,":13:10:13:10:").last().unwrap().to_string();
        let mut s_vec = split_to_vec!(s,":").into_iter().filter(|s|!s.is_empty()).map(|s|s.trim().to_string().parse::<u8>().unwrap()).collect::<Vec<u8>>();
        s_vec.extend(other.iter());
        std::fs::write(file_name, s_vec).unwrap();
        }
    }
    if pathbuf.exists() {
        fs::remove_file(pathbuf).unwrap();
    }
    Ok(Json("{\"status\":\"Ok\"}"))
}

  #[allow(warnings)]
  use rocket::response::{content, Content};
  use rocket::http::ContentType;

  #[get("/")]
  fn index() -> Content<&'static str> {
      let content_type = ContentType::HTML;
      let body = include_str!("./html/index.html");
      Content(content_type, body)
  }

  #[allow(warnings)]
  fn get_file_extension(data: &[u8]) -> Option<&'static str> {
    let ext = match data[..] {
        // Windows PE 可执行文件
        [b'M', b'Z', ..] => Some("exe"),
        // Linux ELF 可执行文件
        [0x7f, b'E', b'L', b'F', ..] => Some("elf"),
        // PNG 图像文件
        [b'\x89', b'P', b'N', b'G', ..] => Some("png"),
        // HTML 文档
        [b'<', b'!', b'D', b'O', b'C', b'T', ..] => Some("html"),
        // PDF 文件
        [b'%', b'P', b'D', b'F', ..] => Some("pdf"),
        // ZIP 压缩文件
        [b'P', b'K', 3, 4, ..] => Some("zip"),
        // 7z 压缩文件
        [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c, ..] => Some("7z"),
        // RAR 压缩文件
        [b'R', b'a', b'r', b'!', ..] => Some("rar"),
        // GZIP 压缩文件
        [b'\x1f', b'\x8b', 8, 0, ..] => Some("gz"),
        // BZIP2 压缩文件
        [b'B', b'Z', b'h', ..] => Some("bz2"),
        // CPIO 归档文件
        [b'I', b's', b'c', b'(', ..] => Some("cpio"),
        // TAR 归档文件
        [b'u', b's', b't', b'a', b'r', ..] => Some("tar"),
        // CHM 帮助文件
        [b'I', b'T', b'S', b'F', ..] => Some("chm"),
        // MP3 音频文件
        [b'I', b'D', b'3', ..] => Some("mp3"),
        // AVI 视频文件
        [b'R', b'I', b'F', b'F', ..] => Some("avi"),
        // JPEG/JFIF 图像文件
        [b'\xff', b'\xd8', b'\xff', ..] => Some("jpg"),
        // BMP 图像文件
        [b'B', b'M', ..] => Some("bmp"),
        // ICO 图标文件
        [0, 1, ..] => Some("ico"),
        // WAV 音频文件
        [b'R', b'I', b'F', b'F', ..] => Some("wav"),
        // Ogg 容器
        [b'O', b'g', b'g', b'S', ..] => Some("ogg"),
        // FLAC 音频文件
        [b'f', b'L', b'a', b'C', ..] => Some("flac"),
        // Impress 演示文稿
        [b'I', b'M', b'P', b'S', ..] => Some("impress"),
        // MIDI 文件
        [b'M', b'T', b'h', b'd', ..] => Some("midi"),
        // TIFF 图像文件
        [b'M', b'M', 0, 42, ..] => Some("tiff"),
        // CR2 图像文件
        [b'I', b'I', 0x2a, 0, 0x10, 0, 0, 0, b'C', b'R', ..] => Some("cr2"),
        // NEF 图像文件
        [b'M', b'M', 0, 0x2a, ..] => Some("nef"),
        // WebP 图像文件
        [b'W', b'E', b'B', b'P', ..] => Some("webp"),
        // Cab 归档文件
        [b'M', b'S', b'C', b'F', ..] => Some("cab"),
        // Microsoft Office Open XML 文档
        [b'P', b'K', 3, 4, ..] => Some("xlsx"),
        // XZ 压缩格式
        [b'\xfd', b'7', b'z', b'X', b'Z', 0, ..] => Some("xz"),
        // iCalendar 文档
        [b'B', b'E', b'G', b'I', b'N', b':', b'V', b'C', ..] => Some("ics"),
        // vCard 文档
        [b'B', b'E', b'G', b'I', b'N', b':', b'V', b'C', b'A', b'R', b'D', ..] => Some("vcf"),
        // SQLite 数据库文件
        [b'S', b'Q', b'L', b'i', b't', b'e', b' ', b'f', b'o', b'r', b'm', b'a', b't', b' ', b'3', ..] => Some("sqlite"),
        // GIF 图像文件
        [b'G', b'I', b'F', b'8', b'7', b'a', ..] |
        [b'G', b'I', b'F', b'8', b'9', b'a', ..] => Some("gif"),

        _ => None,
    };
    ext
}
  pub async fn start() {
    let ip_addr: IpAddr =  match local_ipaddress::get() {
      Some(x)=>{
          x.parse().expect("Found local IP Address is invalid")
        },
        None=>IpAddr::from([127, 0, 0, 1])
    };
    use rocket::config::{Config, Environment};

    let socket_addr = SocketAddr::from((ip_addr, 8585));
    info!("Server listening on http://{}", socket_addr);
    let config = Config::build(Environment::Production)
    .address(socket_addr.ip().to_string())  // 设置应用程序监听地址
    .port(socket_addr.port())            // 设置应用程序监听端口
    .finalize()
    .unwrap();

    Rocket::custom(config)
    .mount("/", StaticFiles::from("static"))
    .mount("/", routes![upload,index])
    .launch();
  }

}
#[allow(warnings)]
pub mod start_all_server{
  use log::{info, LevelFilter};
  use std::net::{IpAddr, SocketAddr};
  use structopt::StructOpt;
  use crate::ARGUMENTS;

  fn initialize_logger() {
    let level = if ARGUMENTS.verbose {
        LevelFilter::Debug
    } else {
        LevelFilter::Info
    };
    env_logger::builder()
        .format_module_path(false)
        .filter(Some("serve"), level)
        .init()
  }
  pub async fn start() {
    use tokio::signal::ctrl_c;
    initialize_logger();

    let handle1 =  tokio::spawn(async move {
      crate::file_show_server::start().await;
    });
    let handle2 = tokio::spawn(async move {
      crate::file_upload_server::start().await;
    });

    ctrl_c().await.expect("Unalbe to get Ctrl+C signal");
    info!("Ctrl+C received. Shutting down");
    handle1.abort();
    handle2.abort();
    std::process::exit(1);

    handle1.await.unwrap_or(());
    handle2.await.unwrap_or(());
  }

}

pub mod file_show_routes{
  use build_html::*;
  use log::debug;
  use std::fs::read_dir;
  use std::path::{Path, PathBuf};
  use warp::filters::BoxedFilter;
  use warp::path::FullPath;
  use warp::reject::not_found;
  use warp::reply::{html, Reply};
  use warp::Filter;
  use crate::ARGUMENTS;

/// The set of routes used by the program
pub fn routes() -> BoxedFilter<(impl Reply,)> {
    let logging = warp::log::custom(|info| {
        debug!("Request: '{}',\tStatus: '{}'", info.path(), info.status())
    });
    let handle_files = warp::fs::dir(&ARGUMENTS.folder);
    let handle_directories = warp::get()
        .and(warp::path::full())
        .and_then(path_to_html)
        .map(html);

    handle_files.or(handle_directories).with(logging).boxed()
}

/// Converts the URL route of a folder to an HTML string of the contents
async fn path_to_html(route: FullPath) -> Result<String, warp::reject::Rejection> {
    let path = PathBuf::from(&ARGUMENTS.folder).join(&route.as_str()[1..]);
    let content = HtmlPage::new()
        .with_style(include_str!("styles.css"))
        .with_container(
            Container::new(ContainerType::Main)
                .with_attributes([("class", "border-box")])
                .with_preformatted_attr(route.as_str(), [("id", "header")])
                .with_container(links_container(path.as_path(), &route).ok_or_else(not_found)?),
        )
        .to_html_string();

    Ok(content)
}

/// Get the container that the links will be contained within
fn links_container(path: &Path, route: &FullPath) -> Option<Container> {
    let content_attrs = [("class", "content")];
    let mut links = Container::new(ContainerType::Div).with_attributes([("id", "wrapper")]);

    if route.as_str() != "/" {
        let parent = path
            .parent()
            .and_then(|path| path.strip_prefix(&ARGUMENTS.folder).ok())
            .and_then(Path::to_str)
            .map(|s| format!("/{}", s))?;
        links.add_link_attr(parent, "..", content_attrs);
    }
    let mut entries: Vec<(String, String, &'static str)> = read_dir(&path)
        .ok()?
        .filter_map(|res| res.ok().map(|x| x.path()))
        .filter_map(format_path)
        .collect();
    entries.sort_by_cached_key(|(_, name, _)| name.to_string());
    for (path, name, icon) in entries {
        let link_text = format!("{}<p class=\"text\">{}</p>", icon, name);
        links.add_link_attr(path, link_text, content_attrs);
    }
    Some(links)
}

/// Converts the provided `PathBuf` into the partial path off of the root, and the filename
fn format_path(path: PathBuf) -> Option<(String, String, &'static str)> {
    let net_path = format!("/{}", path.strip_prefix(&ARGUMENTS.folder).ok()?.to_str()?);
    let file_name = path.file_name()?.to_str()?.into();
    let icon = if path.is_dir() {
        include_str!("./folder_icon.svg")
    } else {
        include_str!("./file_icon.svg")
    };
    Some((net_path, file_name, icon))
}
}