Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Path<String>> for Key {
Expand Down
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}")]
Expand Down Expand Up @@ -68,7 +70,8 @@ impl From<Error> for StatusCode {
| Error::TimeFormatting(_)
| Error::Migration(_)
| Error::SyntaxHighlighting(_)
| Error::SyntaxParsing(_) => StatusCode::INTERNAL_SERVER_ERROR,
| Error::SyntaxParsing(_)
| Error::Axum(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
Expand Down
30 changes: 29 additions & 1 deletion src/rest.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -57,9 +60,34 @@ async fn raw(Path(id): Path<String>, layer: Extension<Layer>) -> Result<String,
Ok(layer.get_raw(Id::try_from(id.as_str())?).await?)
}

async fn download(
Path((id, extension)): Path<(String, String)>,
layer: Extension<Layer>,
) -> Result<Response<String>, 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)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it should be possible to use Response::from_parts together with TypedHeaders to set the headers in a typesafe manner.

Copy link
Copy Markdown
Contributor Author

@yannickfunk yannickfunk Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried using TypedHeaders but it doesn't seem very ergonomic for Response building. Also, the header for which the Typing is the most important ContentDisposition does not support attachement (see https://docs.rs/headers/latest/src/headers/common/content_disposition.rs.html#56).

I now validated the inputs and put constraints on the static strings. Let me know if this ok.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay, I will think about it myself a bit. But strange that this old issue is still not resolved.

.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))
}
3 changes: 3 additions & 0 deletions src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct Paste<'a> {
title: &'a str,
id: String,
formatted: String,
extension: String,
}

#[derive(Template)]
Expand Down Expand Up @@ -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,
})
}

Expand Down
4 changes: 3 additions & 1 deletion templates/paste.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
<pre class="code">{{ formatted|safe }}</pre>
</div>
<div class="paste-box">
<a class="punctuation definition tag" href="/api/entries/{{ id }}"/>raw</a> • <a class="punctuation definition tag" href="/">new</a>
<a class="punctuation definition tag" href="/api/download/{{ id }}/{{ extension }}">⤓</a>
• <a class="punctuation definition tag" href="/api/entries/{{ id }}"/>raw</a>
• <a class="punctuation definition tag" href="/">new</a>
</div>
</div>
{% endblock %}