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
24 changes: 19 additions & 5 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ static MIGRATIONS: LazyLock<Migrations> = LazyLock::new(|| {
),
M::up(include_str!("migrations/0005-drop-text-column.sql")),
M::up(include_str!("migrations/0006-add-nonce-column.sql")),
M::up(include_str!("migrations/0007-add-title-column.sql")),
])
});

Expand Down Expand Up @@ -86,6 +87,8 @@ pub mod write {
pub uid: Option<i64>,
/// Optional password to encrypt the entry
pub password: Option<String>,
/// Title
pub title: Option<String>,
}

/// A compressed entry to be inserted.
Expand Down Expand Up @@ -163,6 +166,8 @@ pub mod read {
pub uid: Option<i64>,
/// Nonce for this entry
pub nonce: Option<Vec<u8>>,
/// Title
pub title: Option<String>,
}

/// Potentially decrypted but still compressed entry
Expand All @@ -173,6 +178,8 @@ pub mod read {
must_be_deleted: bool,
/// User identifier that inserted the entry
uid: Option<i64>,
/// Title
title: Option<String>,
}

/// An entry read from the database.
Expand All @@ -183,6 +190,8 @@ pub mod read {
pub must_be_deleted: bool,
/// User identifier that inserted the entry
pub uid: Option<i64>,
/// Title
pub title: Option<String>,
}

impl DatabaseEntry {
Expand All @@ -196,6 +205,7 @@ pub mod read {
data: self.data,
must_be_deleted: self.must_be_deleted,
uid: self.uid,
title: self.title,
}),
(Some(nonce), Some(password)) => {
let encrypted = Encrypted::new(self.data, nonce);
Expand All @@ -204,6 +214,7 @@ pub mod read {
data: decrypted,
must_be_deleted: self.must_be_deleted,
uid: self.uid,
title: self.title,
})
}
}
Expand All @@ -225,6 +236,7 @@ pub mod read {
text,
uid: self.uid,
must_be_deleted: self.must_be_deleted,
title: self.title,
})
}
}
Expand Down Expand Up @@ -255,18 +267,19 @@ impl Database {

spawn_blocking(move || match entry.expires {
None => conn.lock().execute(
"INSERT INTO entries (id, uid, data, burn_after_reading, nonce) VALUES (?1, ?2, ?3, ?4, ?5)",
params![id, entry.uid, data, entry.burn_after_reading, nonce],
"INSERT INTO entries (id, uid, data, burn_after_reading, nonce, title) VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
params![id, entry.uid, data, entry.burn_after_reading, nonce, entry.title],
),
Some(expires) => conn.lock().execute(
"INSERT INTO entries (id, uid, data, burn_after_reading, nonce, expires) VALUES (?1, ?2, ?3, ?4, ?5, datetime('now', ?6))",
"INSERT INTO entries (id, uid, data, burn_after_reading, nonce, expires, title) VALUES (?1, ?2, ?3, ?4, ?5, datetime('now', ?6), ?7)",
params![
id,
entry.uid,
data,
entry.burn_after_reading,
nonce,
format!("{expires} seconds")
format!("{expires} seconds"),
entry.title,
],
),
})
Expand All @@ -282,7 +295,7 @@ impl Database {

let entry = spawn_blocking(move || {
conn.lock().query_row(
"SELECT data, burn_after_reading, uid, nonce, expires < datetime('now') FROM entries WHERE id=?1",
"SELECT data, burn_after_reading, uid, nonce, expires < datetime('now'), title FROM entries WHERE id=?1",
params![id_as_u32],
|row| {
Ok(read::DatabaseEntry {
Expand All @@ -291,6 +304,7 @@ impl Database {
uid: row.get(2)?,
nonce: row.get(3)?,
expired: row.get::<_, Option<bool>>(4)?.unwrap_or(false),
title: row.get::<_, Option<String>>(5)?,
})
},
)
Expand Down
1 change: 1 addition & 0 deletions src/migrations/0007-add-title-column.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE entries ADD COLUMN title TEXT;
8 changes: 6 additions & 2 deletions src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,12 @@ pub struct Paste<'a> {
ext: String,
can_delete: bool,
html: String,
title: String,
}

impl Paste<'_> {
/// Construct new paste view from cache `key` and paste `html`.
pub fn new(key: CacheKey, html: Html, can_delete: bool) -> Self {
pub fn new(key: CacheKey, html: Html, can_delete: bool, title: String) -> Self {
let html = html.into_inner();

Self {
Expand All @@ -148,6 +149,7 @@ impl Paste<'_> {
ext: key.ext,
can_delete,
html,
title,
}
}
}
Expand Down Expand Up @@ -202,18 +204,20 @@ pub struct Qr<'a> {
ext: String,
can_delete: bool,
code: qrcodegen::QrCode,
title: String,
}

impl Qr<'_> {
/// Construct new QR code view from `code`.
pub fn new(code: qrcodegen::QrCode, key: CacheKey) -> Self {
pub fn new(code: qrcodegen::QrCode, key: CacheKey, title: String) -> Self {
Self {
meta: &env::METADATA,
base_path: &env::BASE_PATH,
id: key.id(),
ext: key.ext,
code,
can_delete: false,
title,
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/routes/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ pub struct Entry {
pub extension: Option<String>,
pub expires: String,
pub password: String,
pub title: String,
}

impl From<Entry> for write::Entry {
fn from(entry: Entry) -> Self {
let burn_after_reading = Some(entry.expires == "burn");
let password = (!entry.password.is_empty()).then_some(entry.password);
let title = (!entry.title.is_empty()).then_some(entry.title);

let expires = match entry.expires.parse::<NonZeroU32>() {
Err(_) => None,
Expand All @@ -35,6 +37,7 @@ impl From<Entry> for write::Entry {
burn_after_reading,
uid: None,
password,
title,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/routes/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct Entry {
pub expires: Option<NonZeroU32>,
pub burn_after_reading: Option<bool>,
pub password: Option<String>,
pub title: Option<String>,
}

#[derive(Deserialize, Serialize)]
Expand All @@ -33,6 +34,7 @@ impl From<Entry> for write::Entry {
burn_after_reading: entry.burn_after_reading,
uid: None,
password: entry.password,
title: entry.title,
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ mod tests {
extension: Some("rs".to_string()),
expires: "0".to_string(),
password: "".to_string(),
title: "".to_string(),
};

let res = client.post(BASE_PATH.path()).form(&data).send().await?;
Expand Down Expand Up @@ -115,6 +116,7 @@ mod tests {
extension: None,
expires: "burn".to_string(),
password: "".to_string(),
title: "".to_string(),
};

let res = client.post(BASE_PATH.path()).form(&data).send().await?;
Expand Down Expand Up @@ -154,6 +156,7 @@ mod tests {
extension: None,
expires: "burn".to_string(),
password: password.to_string(),
title: "".to_string(),
};

let res = client.post(BASE_PATH.path()).form(&data).send().await?;
Expand Down Expand Up @@ -271,6 +274,7 @@ mod tests {
extension: None,
expires: "0".to_string(),
password: "".to_string(),
title: "".to_string(),
};

let res = client.post(BASE_PATH.path()).form(&data).send().await?;
Expand Down Expand Up @@ -311,6 +315,7 @@ mod tests {
extension: None,
expires: "0".to_string(),
password: "".to_string(),
title: "".to_string(),
};

let res = client.post(BASE_PATH.path()).form(&data).send().await?;
Expand Down
19 changes: 15 additions & 4 deletions src/routes/paste.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@ async fn get_qr(
state: AppState,
key: CacheKey,
headers: HeaderMap,
title: String,
) -> Result<pages::Qr<'static>, pages::ErrorResponse<'static>> {
let id = key.id();
let qr_code = tokio::task::spawn_blocking(move || qr_code_from(state, &headers, &id))
.await
.map_err(Error::from)??;

Ok(pages::Qr::new(qr_code, key))
Ok(pages::Qr::new(qr_code, key, title))
}

fn get_download(
Expand Down Expand Up @@ -116,21 +117,25 @@ async fn get_html(

if let Some(html) = state.cache.get(&key) {
tracing::trace!(?key, "found cached item");
return Ok(pages::Paste::new(key, html, can_delete).into_response());
return Ok(
pages::Paste::new(key, html, can_delete, entry.title.unwrap_or_default())
.into_response(),
);
}

// TODO: turn this upside-down, i.e. cache it but only return a cached version if we were able
// to decrypt the content. Highlighting is probably still much slower than decryption.
let can_be_cached = !entry.must_be_deleted;
let ext = key.ext.clone();
let title = entry.title.clone().unwrap_or_default();
let html = Html::from(entry, ext).await?;

if can_be_cached && !is_protected {
tracing::trace!(?key, "cache item");
state.cache.put(key.clone(), html.clone());
}

Ok(pages::Paste::new(key, html, can_delete).into_response())
Ok(pages::Paste::new(key, html, can_delete, title).into_response())
}

pub async fn get(
Expand Down Expand Up @@ -161,7 +166,13 @@ pub async fn get(

match query.fmt {
Some(Format::Raw) => return Ok(entry.text.into_response()),
Some(Format::Qr) => return Ok(get_qr(state, key, headers).await.into_response()),
Some(Format::Qr) => {
return Ok(
get_qr(state, key, headers, entry.title.clone().unwrap_or_default())
.await
.into_response(),
)
}
None => (),
}

Expand Down
35 changes: 30 additions & 5 deletions src/themes/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
border-bottom: 1px solid #d9d7d7;
border-right: 1px solid #d9d7d7;
}

h1 {
color: #555;
}
}

@media screen and (max-width: 720px) {
Expand Down Expand Up @@ -58,7 +62,7 @@ body {

header {
display: flex;
justify-content: flex-end;
justify-content: space-between;
align-items: center;
padding: 0 1em 0 1em;
user-select: none;
Expand All @@ -74,14 +78,17 @@ main {
}

#nav-title {
margin-right: auto;
margin-top: 16px;
margin-bottom: 16px;
/* to cut off very long titles */
overflow: hidden;
display: flex;
flex-direction: row;
align-items: baseline;
}

header ul {
margin: 0;
padding: 0;
display: flex;
}

header li {
Expand All @@ -108,6 +115,24 @@ header .navigation:hover {
height: 100%;
}

h1 {
display: inline-block;
font-weight: normal;
font-size: 1.125rem;

margin-top: 0px;
margin-bottom: 0px;
padding-left: 16px;
padding-right: 16px;

max-width: 80vw;
text-overflow: ellipsis;
overflow: hidden;
vertical-align: text-bottom;
text-wrap-mode: nowrap;
user-select: text;
}

button {
font-family: "JetBrains Mono", monospace;
font-size: 1.125rem;
Expand Down Expand Up @@ -205,7 +230,7 @@ a, a:visited, a:hover {
grid-row: 1/2;
}

.expiration-list, .password, .paste-button {
.expiration-list, .password, .paste-button, .title {
margin-top: 2em;
}

Expand Down
4 changes: 2 additions & 2 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="generator" content="wastebin {{ meta.version }}">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ meta.title }}</title>
<title>{{ meta.title }}{% block title_content %}{% endblock %}</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) }}">
Expand All @@ -15,7 +15,7 @@
{% block body_top %}{% endblock %}
<div id="main-container">
<header>
<span id="nav-title"><a href="{{ base_path.path() }}" class="navigation">home</a></span>
<span id="nav-title"><a href="{{ base_path.path() }}" class="navigation">home</a>{% block title %}{% endblock %}</span>
<nav>
<ul>
{% block nav %}{% endblock %}
Expand Down
3 changes: 3 additions & 0 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
<div class="password">
<input type="password" name="password" id="password" placeholder="Password ...">
</div>
<div class="title">
<input type="text" name="title" id="title" placeholder="Title ...">
</div>
<div class="paste-button">
<button type="submit" title="Paste" class="button">Paste</button>
</div>
Expand Down
Loading
Loading