diff --git a/Cargo.lock b/Cargo.lock index e8f3b37..e3a76e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2385,6 +2385,7 @@ dependencies = [ "shuttle-actix-web", "shuttle-runtime", "shuttle-static-folder", + "text-template", "tokio", "url", ] @@ -2778,6 +2779,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "text-template" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bcc3e9514cba67c087126b18600010c7c29b0cb51a6f6bc75e5058c2369bba" + [[package]] name = "thiserror" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index dcdfb9b..83c0711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,7 @@ shuttle-actix-web = { version = "0.16.0", optional = true } shuttle-runtime = { version = "0.16.0", optional = true } shuttle-static-folder = { version = "0.16.0", optional = true } tokio = { version = "1.28.1", optional = true } +text-template = "0.1.0" [dependencies.config] version = "0.13.3" @@ -72,6 +73,7 @@ actix-rt = "2.8.0" [profile.dev] opt-level = 0 debug = true +panic = "abort" [profile.test] opt-level = 0 diff --git a/README.md b/README.md index f50cc62..0feceb1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ [![Docker Builds](https://img.shields.io/github/actions/workflow/status/orhun/rustypaste/docker.yml?style=flat&labelColor=823213&color=2c2c2c&label=docker&logo=Docker&logoColor=white)](https://hub.docker.com/r/orhunp/rustypaste) [![Documentation](https://img.shields.io/docsrs/rustypaste?style=flat&labelColor=823213&color=2c2c2c&logo=Rust&logoColor=white)](https://docs.rs/rustypaste/) -**Rustypaste** is a minimal file upload/pastebin service. +**Rustypaste-pretty** is a minimal file upload/pastebin service with client side highlighting provided by highlight.js. + +Just add `?pretty` to the end of a link to highlight! ```sh $ echo "some text" > awesome.txt diff --git a/config.toml b/config.toml index a4314a5..d3543a5 100644 --- a/config.toml +++ b/config.toml @@ -2,34 +2,18 @@ refresh_rate = "1s" [server] -address = "127.0.0.1:8000" +address="127.0.0.1:8020" #url = "https://rustypaste.shuttleapp.rs" #workers=4 max_content_length = "10MB" upload_path = "./upload" timeout = "30s" expose_version = false +style="monokai" landing_page = """ ┬─┐┬ ┬┌─┐┌┬┐┬ ┬┌─┐┌─┐┌─┐┌┬┐┌─┐ ├┬┘│ │└─┐ │ └┬┘├─┘├─┤└─┐ │ ├┤ ┴└─└─┘└─┘ ┴ ┴ ┴ ┴ ┴└─┘ ┴ └─┘ - -Submit files via HTTP POST here: - curl -F 'file=@example.txt' -This will return the URL of the uploaded file. - -The server administrator might remove any pastes that they do not personally -want to host. - -If you are the server administrator and want to change this page, just go -into your config file and change it! If you change the expiry time, it is -recommended that you do. - -By default, pastes expire every hour. The server admin may or may not have -changed this. - -Check out the GitHub repository at https://github.com/orhun/rustypaste -Command line tool is available at https://github.com/orhun/rustypaste-cli """ [paste] @@ -50,6 +34,11 @@ mime_blacklist = [ "application/java-archive", "application/java-vm", ] + duplicate_files = true # default_expiry = "1h" delete_expired_files = { enabled = true, interval = "1h" } + +[paste.highlight_override] +# For example, to force markdown rather than using highlight.js's autodetection +#"text/markdown" = "markdown" diff --git a/src/config.rs b/src/config.rs index 6bad254..ea06918 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,7 @@ use crate::mime::MimeMatcher; use crate::random::RandomURLConfig; use byte_unit::Byte; use config::{self, ConfigError}; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::time::Duration; @@ -47,6 +48,8 @@ pub struct ServerConfig { pub landing_page: Option, /// Expose version. pub expose_version: Option, + /// Highlight.js style + pub style: Option, } /// Paste configuration. @@ -69,6 +72,9 @@ pub struct PasteConfig { pub default_expiry: Option, /// Delete expired files. pub delete_expired_files: Option, + /// Highlight override. + #[serde(default)] + pub highlight_override: HashMap, } /// Cleanup configuration. @@ -85,6 +91,7 @@ impl Config { /// Parses the config file and returns the values. pub fn parse(path: &Path) -> Result { config::Config::builder() + .set_default("style", "default").unwrap() .add_source(config::File::from(path)) .add_source(config::Environment::default().separator("__")) .build()? diff --git a/src/pretty.html b/src/pretty.html new file mode 100644 index 0000000..1fa8779 --- /dev/null +++ b/src/pretty.html @@ -0,0 +1,23 @@ + + + + ${file} + + + + + +

+		
+ + diff --git a/src/server.rs b/src/server.rs index 6d2f0b7..8085d8f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -6,6 +6,7 @@ use crate::mime as mime_util; use crate::paste::{Paste, PasteType}; use crate::util; use crate::AUTH_TOKEN_ENV; +use text_template::*; use actix_files::NamedFile; use actix_multipart::Multipart; use actix_web::{error, get, post, web, Error, HttpRequest, HttpResponse}; @@ -16,7 +17,9 @@ use serde::Deserialize; use std::convert::TryFrom; use std::env; use std::fs; +use std::str; use std::sync::RwLock; +use std::collections::HashMap; /// Shows the landing page. #[get("/")] @@ -53,6 +56,7 @@ async fn serve( let config = config .read() .map_err(|_| error::ErrorInternalServerError("cannot acquire config"))?; + let path = config.server.upload_path.join(&*file); let mut path = util::glob_match_file(path)?; let mut paste_type = PasteType::File; @@ -80,6 +84,23 @@ async fn serve( mime_util::get_mime_type(&config.paste.mime_override, file.to_string()) .map_err(error::ErrorInternalServerError)? }; + + if request.query_string() == "pretty" { + let mut values = HashMap::new(); + let tmpl_bytes = str::from_utf8(include_bytes!("pretty.html")).unwrap(); + let tmpl = Template::from(tmpl_bytes); + values.insert("file", file.as_str()); + values.insert("style", match &config.server.style { + Some(style) => style.as_str(), + None => "default", + }); + let mime_str = mime_type.to_string(); + let overrides = &config.paste.highlight_override; + values.insert("type", if overrides.contains_key(&mime_str) { overrides[&mime_str].as_str() } else { "" }); + let rendered = tmpl.fill_in(&values); + return Ok(HttpResponse::Ok().content_type(mime::TEXT_HTML).body(rendered.to_string())) + } + let response = NamedFile::open(&path)? .disable_content_disposition() .set_content_type(mime_type)