Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscellaneous tweaks #76

Merged
merged 12 commits into from
Nov 10, 2024
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ tracing-subscriber = "0"
url = "2"
zstd = "0.13"
parking_lot = "0.12.1"
http = "1.1.0"
matze marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
reqwest = { version = "0", default-features = false, features = ["cookies", "json"] }
Expand Down
4 changes: 2 additions & 2 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl Encrypted {
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
let ciphertext = cipher
.encrypt(&nonce, plaintext.0.as_ref())
.map_err(|_| Error::ChaCha20Poly1305)?;
.map_err(|_| Error::ChaCha20Poly1305Encrypt)?;

Ok(Self {
ciphertext,
Expand All @@ -73,7 +73,7 @@ impl Encrypted {
let nonce = XNonce::from_slice(&self.nonce);
let plaintext = cipher
.decrypt(nonce, self.ciphertext.as_ref())
.map_err(|_| Error::ChaCha20Poly1305)?;
.map_err(|_| Error::ChaCha20Poly1305Decrypt)?;
Ok(plaintext)
})
.await?
Expand Down
8 changes: 5 additions & 3 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ pub enum Error {
UrlParsing(#[from] url::ParseError),
#[error("argon2 error: {0}")]
Argon2(#[from] argon2::Error),
#[error("encryption failed")]
ChaCha20Poly1305Encrypt,
#[error("decryption failed")]
ChaCha20Poly1305,
ChaCha20Poly1305Decrypt,
#[error("password not given")]
NoPassword,
}
Expand Down Expand Up @@ -72,9 +74,9 @@ impl From<Error> for StatusCode {
| Error::SyntaxHighlighting(_)
| Error::SyntaxParsing(_)
| Error::Argon2(_)
| Error::ChaCha20Poly1305
| Error::ChaCha20Poly1305Encrypt
| Error::Axum(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::Delete => StatusCode::FORBIDDEN,
Error::Delete | Error::ChaCha20Poly1305Decrypt => StatusCode::FORBIDDEN,
}
}
}
Expand Down
50 changes: 28 additions & 22 deletions src/pages.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::num::NonZero;
use std::num::NonZeroU32;
use std::sync::OnceLock;

use crate::cache::Key as CacheKey;
use crate::env;
Expand Down Expand Up @@ -90,31 +91,36 @@ const EXPIRATION_OPTIONS: [(&str, Expiration); 8] = [
];

impl<'a> Index<'a> {
fn expiry_options(&self) -> String {
let mut option_set = String::new();
let mut wrote_first = false;

option_set.push('\n');

for (opt_name, opt_val) in EXPIRATION_OPTIONS {
if self.max_expiration.is_none()
|| opt_val == Expiration::Burn
|| matches!((self.max_expiration, opt_val), (Some(exp), Expiration::Time(time)) if time <= exp)
{
option_set.push_str("<option");
if !wrote_first {
option_set.push_str(" selected");
wrote_first = true;
fn expiry_options(&self) -> &str {
static EXPIRATION_OPTIONS_HTML: OnceLock<String> = OnceLock::new();

EXPIRATION_OPTIONS_HTML.get_or_init(|| {

let mut option_set = String::new();
let mut wrote_first = false;

option_set.push('\n');

for (opt_name, opt_val) in EXPIRATION_OPTIONS {
if self.max_expiration.is_none()
|| opt_val == Expiration::Burn
|| matches!((self.max_expiration, opt_val), (Some(exp), Expiration::Time(time)) if time <= exp)
{
option_set.push_str("<option");
if !wrote_first {
option_set.push_str(" selected");
wrote_first = true;
}
option_set.push_str(" value=\"");
option_set.push_str(opt_val.to_string().as_ref());
option_set.push_str("\">");
option_set.push_str(opt_name);
option_set.push_str("</option>\n");
}
option_set.push_str(" value=\"");
option_set.push_str(opt_val.to_string().as_ref());
option_set.push_str("\">");
option_set.push_str(opt_name);
option_set.push_str("</option>\n");
}
}

option_set
option_set
})
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/routes/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn favicon() -> impl IntoResponse {
pub fn routes() -> Router<AppState> {
let style_name = &DATA.style.name;
Router::new()
.route("/favicon.png", get(|| async { favicon() }))
.route("/favicon.ico", get(|| async { favicon() }))
.route(&format!("/{style_name}"), get(|| async { style_css() }))
.route("/dark.css", get(|| async { dark_css() }))
.route("/light.css", get(|| async { light_css() }))
Expand Down
11 changes: 9 additions & 2 deletions src/routes/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::id::Id;
use crate::{pages, AppState, Error};
use axum::extract::{Form, State};
use axum::response::Redirect;
use axum_extra::extract::cookie::{Cookie, SignedCookieJar};
use axum_extra::extract::cookie::{Cookie, SameSite, SignedCookieJar};
use rand::Rng;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -43,6 +43,7 @@ pub async fn insert(
state: State<AppState>,
jar: SignedCookieJar,
Form(entry): Form<Entry>,
is_https: bool,
) -> Result<(SignedCookieJar, Redirect), pages::ErrorResponse<'static>> {
let id: Id = tokio::task::spawn_blocking(|| {
let mut rng = rand::thread_rng();
Expand Down Expand Up @@ -82,6 +83,12 @@ pub async fn insert(

state.db.insert(id, entry).await?;

let jar = jar.add(Cookie::new("uid", uid.to_string()));
let cookie = Cookie::build(("uid", uid.to_string()))
.http_only(true)
.secure(is_https)
.same_site(SameSite::Strict)
.build();

let jar = jar.add(cookie);
Ok((jar, Redirect::to(&url_with_base)))
}
40 changes: 38 additions & 2 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub fn routes() -> Router<AppState> {

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use crate::db::write::Entry;
use crate::env::BASE_PATH;
use crate::routes;
Expand Down Expand Up @@ -91,6 +93,19 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn insert_via_form_fail() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new(make_app()?).await;

let mut data = HashMap::new();
data.insert("Hello", "World");

let res = client.post(BASE_PATH.path()).form(&data).send().await?;
assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);

Ok(())
}

#[tokio::test]
async fn burn_after_reading() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new(make_app()?).await;
Expand Down Expand Up @@ -207,6 +222,18 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn insert_via_json_fail() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new(make_app()?).await;

let entry = "Hello World";

let res = client.post(BASE_PATH.path()).json(&entry).send().await?;
assert_eq!(res.status(), StatusCode::UNPROCESSABLE_ENTITY);

Ok(())
}

#[tokio::test]
async fn insert_via_json_encrypted() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new(make_app()?).await;
Expand Down Expand Up @@ -247,8 +274,17 @@ mod tests {
};

let res = client.post(BASE_PATH.path()).form(&data).send().await?;
let uid_cookie = res.cookies().find(|cookie| cookie.name() == "uid");
assert!(uid_cookie.is_some());
let uid_cookie = res.cookies().find(|cookie| cookie.name() == "uid").unwrap();
assert_eq!(uid_cookie.name(), "uid");
assert!(uid_cookie.value().len() > 40);
assert_eq!(uid_cookie.path(), None);
assert!(uid_cookie.http_only());
assert!(uid_cookie.same_site_strict());
assert!(!uid_cookie.secure());
assert_eq!(uid_cookie.domain(), None);
assert_eq!(uid_cookie.expires(), None);
assert_eq!(uid_cookie.max_age(), None);

assert_eq!(res.status(), StatusCode::SEE_OTHER);

let location = res.headers().get("location").unwrap().to_str()?;
Expand Down
15 changes: 14 additions & 1 deletion src/routes/paste.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,12 +195,25 @@ pub async fn insert(
.ok_or_else(|| StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())?;

if content_type == headers::ContentType::form_url_encoded() {
let is_https = headers
.get(http::header::HOST)
.zip(headers.get(http::header::ORIGIN))
.and_then(|(host, origin)| host.to_str().ok().zip(origin.to_str().ok()))
.and_then(|(host, origin)| {
origin
.strip_prefix("https://")
.map(|origin| origin.starts_with(host))
})
.unwrap_or(false);

let entry: Form<form::Entry> = request
.extract()
.await
.map_err(IntoResponse::into_response)?;

Ok(form::insert(state, jar, entry).await.into_response())
Ok(form::insert(state, jar, entry, is_https)
.await
.into_response())
} else if content_type == headers::ContentType::json() {
let entry: Json<json::Entry> = request
.extract()
Expand Down
2 changes: 1 addition & 1 deletion src/themes/ayu-dark.tmTheme
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ a {
<key>name</key>
<string>Entity name</string>
<key>scope</key>
<string>entity.name - (entity.name.section | entity.name.tag | entity.name.label)</string>
<string>entity.name.section | entity.name.tag | entity.name.label</string>
<key>settings</key>
<dict>
<key>foreground</key>
Expand Down
2 changes: 1 addition & 1 deletion src/themes/ayu-light.tmTheme
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ a {
<key>name</key>
<string>Entity name</string>
<key>scope</key>
<string>entity.name - (entity.name.section | entity.name.tag | entity.name.label)</string>
<string>entity.name.section | entity.name.tag | entity.name.label</string>
<key>settings</key>
<dict>
<key>foreground</key>
Expand Down
5 changes: 4 additions & 1 deletion src/themes/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ header {
padding: 0 1em 0 1em;
}

content {
main {
flex-grow: 1;
padding: 1em 2em 1em 2em;
}
Expand Down Expand Up @@ -218,6 +218,7 @@ td.line-number {

.line-number {
padding-right: 1em;
text-align: right;
}

.line-number > a, .line-number > a:visited {
Expand All @@ -231,6 +232,8 @@ td.line-number {
.line {
word-wrap: normal;
white-space: pre;
margin-top: 0px;
margin-bottom: 0px;
}

.center {
Expand Down
12 changes: 7 additions & 5 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="generator" content="wastebin {{ meta.version }}"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<meta name="generator" content="wastebin {{ meta.version }}">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ meta.title }}</title>
<link rel="preload" as="style" href="dark.css">
<link rel="preload" as="style" href="light.css">
<link rel="stylesheet" href="{{ base_path.join(meta.highlight.style.name) }}">
<link rel="icon" href="{{ base_path.join("favicon.png") }}" type="image/png">
<link rel="icon" href="{{ base_path.join("favicon.ico") }}" type="image/png">
matze marked this conversation as resolved.
Show resolved Hide resolved
{% block head %}{% endblock %}
</head>
<body>
Expand All @@ -19,9 +21,9 @@
</ul>
</nav>
</header>
<content>
<main>
{% block content %}{% endblock %}
</content>
</main>
</div>
</body>
</html>
2 changes: 1 addition & 1 deletion templates/formatted.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@
</div>
</div>
<div id="code">
<pre class="code">{{ html|safe }}</pre>
{{ html|safe }}
</div>
{% endblock %}
4 changes: 2 additions & 2 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@
</select>
</div>
<div class="extension-filter">
<input type="search" id="filter" placeholder="Filter ..." onchange="filterLangs(event);" onkeyup="filterLangs(event)"></input>
<input type="search" id="filter" placeholder="Filter ..." onchange="filterLangs(event);" onkeyup="filterLangs(event)">
</div>
<div class="expiration-list">
<select name="expires" size="8">
{{- Self::expiry_options(self)|safe }}
</select>
</div>
<div class="password">
<input type="password" name="password" id="password" placeholder="Password ..."></input>
<input type="password" name="password" id="password" placeholder="Password ...">
</div>
<div class="paste-button">
<button type="submit" title="Paste" class="button">Paste</button>
Expand Down
1 change: 1 addition & 0 deletions templates/paste.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@
{% endif %}
<li><a href="{{ base_path.join(id) }}?dl={{ ext }}"><button>download</button></a></li>
<li><a href="{{ base_path.join(id) }}?fmt=raw"><button>raw</button></a></li>
<li><a href="{{ base_path.join(id) }}?fmt=qr"><button>qr</button></a></li>
{% endblock %}