Skip to content

Commit bea0dc2

Browse files
author
HeapUnderflow
committed
apply review
expiry -> expiration hard fail on invalid expiration values
1 parent 88ce98c commit bea0dc2

8 files changed

Lines changed: 70 additions & 73 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ run-time behavior:
111111
until wastebin responds with 408, by default it is set to 5 seconds.
112112
* `WASTEBIN_MAX_BODY_SIZE` number of bytes to accept for POST requests. Defaults
113113
to 1 MB.
114-
* `WASTEBIN_MAX_PASTE_EXPIRY` maximum allowed lifetime of a paste. Leave empty or set to -1 for no limit. Defaults to no limit.
114+
* `WASTEBIN_MAX_PASTE_EXPIRATION` maximum allowed lifetime of a paste. Leave empty or set to -1 for no limit. Defaults to no limit.
115115
* `WASTEBIN_PASSWORD_SALT` salt used to hash user passwords used for encrypting
116116
pastes.
117117
* `WASTEBIN_SIGNING_KEY` sets the key to sign cookies. If not set, a random key

src/env.rs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const VAR_SIGNING_KEY: &str = "WASTEBIN_SIGNING_KEY";
2727
const VAR_BASE_URL: &str = "WASTEBIN_BASE_URL";
2828
const VAR_PASSWORD_SALT: &str = "WASTEBIN_PASSWORD_SALT";
2929
const VAR_HTTP_TIMEOUT: &str = "WASTEBIN_HTTP_TIMEOUT";
30-
const VAR_MAX_PASTE_EXPIRY: &str = "WASTEBIN_MAX_PASTE_EXPIRY";
30+
const VAR_MAX_PASTE_EXPIRATION: &str = "WASTEBIN_MAX_PASTE_EXPIRATION";
3131

3232
#[derive(thiserror::Error, Debug)]
3333
pub enum Error {
@@ -45,6 +45,13 @@ pub enum Error {
4545
SigningKey(String),
4646
#[error("failed to parse {VAR_HTTP_TIMEOUT}: {0}")]
4747
HttpTimeout(ParseIntError),
48+
#[error("failed to parse {VAR_MAX_PASTE_EXPIRATION}: {0}")]
49+
MaxPasteExpiration(ParseIntError),
50+
#[error(
51+
"{VAR_MAX_PASTE_EXPIRATION} is too large (max {}), pass -1 if you mean no expiry",
52+
u32::MAX
53+
)]
54+
ExpirationTooLarge,
4855
}
4956

5057
pub struct BasePath(String);
@@ -185,21 +192,16 @@ pub fn http_timeout() -> Result<Duration, Error> {
185192
.map_err(Error::HttpTimeout)
186193
}
187194

188-
pub fn max_paste_expiry() -> Option<u32> {
189-
std::env::var(VAR_MAX_PASTE_EXPIRY)
195+
pub fn max_paste_expiration() -> Result<Option<u32>, Error> {
196+
std::env::var(VAR_MAX_PASTE_EXPIRATION)
190197
.ok()
191-
.and_then(|raw_max_exp| {
198+
.and_then(|raw_max_exp| -> Option<Result<u32, Error>> {
192199
match raw_max_exp.parse::<i64>() {
193200
Ok(max_exp) if max_exp == -1 => None,
194-
Ok(max_exp) if max_exp >= u32::MAX as i64 => {
195-
tracing::warn!("the expiry is larger than an u32 ({} seconds, ~136 Years), assuming you mean no timeout", u32::MAX);
196-
None
197-
}
198-
Ok(max_exp) => Some(max_exp as u32),
199-
Err(why) => {
200-
tracing::warn!("unable to parse VAR_MAX_PASTE_EXPIRY `{raw_max_exp}`, defaulting to no expiry: {why}");
201-
None
202-
}
201+
Ok(max_exp) if max_exp >= u32::MAX as i64 => Some(Err(Error::ExpirationTooLarge)),
202+
Ok(max_exp) => Some(Ok(max_exp as u32)),
203+
Err(why) => Some(Err(Error::MaxPasteExpiration(why))),
203204
}
204205
})
206+
.transpose()
205207
}

src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub struct AppState {
3333
cache: Cache,
3434
key: Key,
3535
base_url: Option<Url>,
36-
max_expiry: Option<u32>,
36+
max_expiration: Option<u32>,
3737
}
3838

3939
impl FromRef<AppState> for Key {
@@ -92,7 +92,7 @@ async fn start() -> Result<(), Box<dyn std::error::Error>> {
9292
let max_body_size = env::max_body_size()?;
9393
let base_url = env::base_url()?;
9494
let timeout = env::http_timeout()?;
95-
let max_expiry = env::max_paste_expiry();
95+
let max_expiration = env::max_paste_expiration()?;
9696

9797
let cache = Cache::new(cache_size);
9898
let db = Database::new(method)?;
@@ -101,7 +101,7 @@ async fn start() -> Result<(), Box<dyn std::error::Error>> {
101101
cache,
102102
key,
103103
base_url,
104-
max_expiry,
104+
max_expiration,
105105
};
106106

107107
tracing::debug!("serving on {addr}");

src/pages.rs

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::cell::Cell;
2-
31
use crate::cache::Key as CacheKey;
42
use crate::env;
53
use crate::highlight::Html;
@@ -33,75 +31,78 @@ impl From<crate::Error> for ErrorResponse<'_> {
3331

3432
/// Index page displaying a form for paste insertion and a selection box for languages.
3533
#[derive(Template)]
36-
#[template(path = "index.html")]
34+
#[template(path = "index.html", print="code")]
3735
pub struct Index<'a> {
3836
meta: &'a env::Metadata<'a>,
3937
base_path: &'static env::BasePath,
40-
max_expiry: Option<u32>,
41-
42-
/// SAFETY: calls in the template are always sequential
43-
default_has_been_written: Cell<bool>,
38+
max_expiration: Option<u32>,
4439
}
4540

4641
impl<'a> Default for Index<'a> {
4742
fn default() -> Self {
4843
Self {
4944
meta: env::metadata(),
5045
base_path: env::base_path(),
51-
max_expiry: env::max_paste_expiry(),
52-
default_has_been_written: Cell::new(false),
46+
// exception should already have been handled in main
47+
max_expiration: env::max_paste_expiration().unwrap(),
5348
}
5449
}
5550
}
5651

57-
#[derive(Debug)]
58-
enum Expiry<'a> {
59-
Special(&'a str),
52+
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
53+
enum Expiration {
54+
None,
55+
Burn,
6056
Time(u32),
6157
}
6258

63-
impl<'a> Expiry<'a> {
64-
fn as_str(&self) -> String {
59+
impl std::fmt::Display for Expiration {
60+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6561
match self {
66-
Expiry::Special(s) => s.to_string(),
67-
Expiry::Time(u) => u.to_string(),
62+
Expiration::None => write!(f, ""),
63+
Expiration::Burn => write!(f, "burn"),
64+
Expiration::Time(t) => write!(f, "{}", t),
6865
}
6966
}
7067
}
7168

72-
impl<'a> Index<'a> {
73-
fn expiry(&self, name: &str, time: Expiry<'a>) -> String {
74-
let sel_string = if self.default_has_been_written.get() {
75-
""
76-
} else {
77-
r#" selected"#
78-
};
69+
const EXPIRATION_OPTIONS: [(&'static str, Expiration); 8] = [
70+
("never", Expiration::None),
71+
("10 minutes", Expiration::Time(600)),
72+
("1 hour", Expiration::Time(3600)),
73+
("1 day", Expiration::Time(86400)),
74+
("1 week", Expiration::Time(604800)),
75+
("1 month", Expiration::Time(2592000)),
76+
("1 year", Expiration::Time(31536000)),
77+
("🔥 after reading", Expiration::Burn),
78+
];
7979

80-
match self.max_expiry {
81-
Some(exp) => {
82-
match time {
83-
// never emit an never expire with a limit
84-
Expiry::Special("") => String::new(),
85-
Expiry::Special("burn") => {
86-
self.default_has_been_written.set(true);
87-
format!(r#"<option{} value="burn">{}</option>"#, sel_string, name)
88-
}
89-
Expiry::Special(_) => String::new(),
90-
Expiry::Time(t) if t > exp => String::new(),
91-
Expiry::Time(t) => {
92-
self.default_has_been_written.set(true);
93-
format!(r#"<option{} value="{}">{}</option>"#, sel_string, t, name)
94-
}
80+
impl<'a> Index<'a> {
81+
fn expiry_options(&self) -> String {
82+
let mut option_set = String::new();
83+
let mut wrote_first = false;
84+
85+
option_set.push('\n');
86+
87+
for (opt_name, opt_val) in EXPIRATION_OPTIONS {
88+
if self.max_expiration.is_none()
89+
|| opt_val == Expiration::Burn
90+
|| matches!((self.max_expiration, opt_val), (Some(exp), Expiration::Time(time)) if time <= exp)
91+
{
92+
option_set.push_str("<option");
93+
if !wrote_first {
94+
option_set.push_str(" selected");
95+
wrote_first = true;
9596
}
96-
}
97-
None => {
98-
self.default_has_been_written.set(true);
99-
format!(
100-
r#"<option{} value="{}">{}</option>"#,
101-
sel_string, time, name
102-
)
97+
option_set.push_str(" value=\"");
98+
option_set.push_str(opt_val.to_string().as_ref());
99+
option_set.push_str("\">");
100+
option_set.push_str(opt_name);
101+
option_set.push_str("</option>\n");
103102
}
104103
}
104+
105+
option_set
105106
}
106107
}
107108

src/routes/form.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ pub async fn insert(
7272

7373
let url_with_base = base_path().join(&url);
7474

75-
if let Some(max_exp) = state.max_expiry {
75+
if let Some(max_exp) = state.max_expiration {
7676
entry.expires = entry
7777
.expires
7878
.map_or_else(|| Some(max_exp), |value| Some(value.min(max_exp)));

src/routes/json.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub async fn insert(
4949

5050
let mut entry: write::Entry = entry.into();
5151

52-
if let Some(max_exp) = state.max_expiry {
52+
if let Some(max_exp) = state.max_expiration {
5353
entry.expires = entry
5454
.expires
5555
.map_or_else(|| Some(max_exp), |value| Some(value.min(max_exp)));

src/test_helpers.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub(crate) fn make_app() -> Result<Router, Box<dyn std::error::Error>> {
6363
cache,
6464
key,
6565
base_url,
66-
max_expiry: None,
66+
max_expiration: None,
6767
};
6868

6969
Ok(crate::make_app(4096, Duration::new(30, 0)).with_state(state))

templates/index.html

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,8 @@
7676
<input type="search" id="filter" placeholder="Filter ..." onchange="filterLangs(event);" onkeyup="filterLangs(event)"></input>
7777
</div>
7878
<div class="expiration-list">
79-
<select name="expires" size="7">
80-
{{ Self::expiry(self, "never", Expiry::Special(""))|safe }}
81-
{{ Self::expiry(self, "10 minutes", Expiry::Time(600))|safe }}
82-
{{ Self::expiry(self, "1 hour", Expiry::Time(3600))|safe }}
83-
{{ Self::expiry(self, "1 day", Expiry::Time(86400))|safe }}
84-
{{ Self::expiry(self, "1 week", Expiry::Time(604800))|safe }}
85-
{{ Self::expiry(self, "1 year", Expiry::Time(215308800))|safe }}
86-
{{ Self::expiry(self, "burn", Expiry::Special("burn"))|safe }}
79+
<select name="expires" size="8">
80+
{{- Self::expiry_options(self)|safe }}
8781
</select>
8882
</div>
8983
<div class="password">

0 commit comments

Comments
 (0)