diff --git a/src/cache.rs b/src/cache.rs index 34786528..8bef9d64 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -29,6 +29,10 @@ impl Key { pub fn id(&self) -> String { self.id.to_string() } + + pub fn extension(&self) -> String { + self.ext.clone() + } } impl TryFrom> for Key { diff --git a/src/main.rs b/src/main.rs index a8ad150d..0bf6e7be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,8 @@ mod web; #[derive(thiserror::Error, Debug)] pub enum Error { + #[error("axum http error: {0}")] + Axum(#[from] axum::http::Error), #[error("sqlite error: {0}")] Sqlite(#[from] rusqlite::Error), #[error("migrations error: {0}")] @@ -68,7 +70,8 @@ impl From for StatusCode { | Error::TimeFormatting(_) | Error::Migration(_) | Error::SyntaxHighlighting(_) - | Error::SyntaxParsing(_) => StatusCode::INTERNAL_SERVER_ERROR, + | Error::SyntaxParsing(_) + | Error::Axum(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } diff --git a/src/rest.rs b/src/rest.rs index b2cc43d7..5a2f204e 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -1,8 +1,11 @@ use crate::cache::Layer; +use crate::highlight::DATA; use crate::id::Id; use crate::{Entry, Error, Router}; +use askama_axum::Response; use axum::extract::Path; -use axum::http::StatusCode; +use axum::headers::HeaderValue; +use axum::http::{header, StatusCode}; use axum::routing::{get, post}; use axum::{Extension, Json}; use rand::Rng; @@ -57,9 +60,34 @@ async fn raw(Path(id): Path, layer: Extension) -> Result, + layer: Extension, +) -> Result, ErrorResponse> { + let raw_string = raw(Path(id.clone()), layer.clone()).await?; + let content_type = "text; charset=utf-8"; + + // validate Id and extension + Id::try_from(id.as_str())?; + let _ = DATA + .syntax_set + .find_syntax_by_extension(extension.as_str()) + .ok_or(Error::IllegalCharacters)?; + + let filename = format!("{}.{}", id, extension); + let content_disposition = format!("attachment; filename=\"{}\"", filename); + + Ok(Response::builder() + .header(header::CONTENT_TYPE, HeaderValue::from_static(content_type)) + .header(header::CONTENT_DISPOSITION, content_disposition) + .body(raw_string) + .map_err(Error::from)?) +} + pub fn routes() -> Router { Router::new() .route("/api/health", get(health)) .route("/api/entries", post(insert)) .route("/api/entries/:id", get(raw)) + .route("/api/download/:id/:extension", get(download)) } diff --git a/src/web.rs b/src/web.rs index 0e00460a..61df5f04 100644 --- a/src/web.rs +++ b/src/web.rs @@ -56,6 +56,7 @@ struct Paste<'a> { title: &'a str, id: String, formatted: String, + extension: String, } #[derive(Template)] @@ -125,12 +126,14 @@ async fn show( let title = &TITLE; let key = Key::try_from(id_with_opt_ext)?; let id = key.id(); + let extension = key.extension(); let formatted = layer.get_formatted(key).await?; Ok(Paste { title, id, formatted, + extension, }) } diff --git a/templates/paste.html b/templates/paste.html index dda04e6f..7cb58709 100644 --- a/templates/paste.html +++ b/templates/paste.html @@ -24,7 +24,9 @@
{{ formatted|safe }}
- rawnew + + • raw + • new
{% endblock %}