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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ run-time behavior:
until wastebin responds with 408, by default it is set to 5 seconds.
* `WASTEBIN_MAX_BODY_SIZE` number of bytes to accept for POST requests. Defaults
to 1 MB.
* `WASTEBIN_MAX_PASTE_EXPIRATION` maximum allowed lifetime of a paste in seconds. Leave empty or set to -1 for no limit. Defaults to no limit.
* `WASTEBIN_PASSWORD_SALT` salt used to hash user passwords used for encrypting
pastes.
* `WASTEBIN_SIGNING_KEY` sets the key to sign cookies. If not set, a random key
Expand Down
22 changes: 22 additions & 0 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const VAR_SIGNING_KEY: &str = "WASTEBIN_SIGNING_KEY";
const VAR_BASE_URL: &str = "WASTEBIN_BASE_URL";
const VAR_PASSWORD_SALT: &str = "WASTEBIN_PASSWORD_SALT";
const VAR_HTTP_TIMEOUT: &str = "WASTEBIN_HTTP_TIMEOUT";
const VAR_MAX_PASTE_EXPIRATION: &str = "WASTEBIN_MAX_PASTE_EXPIRATION";

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand All @@ -44,6 +45,13 @@ pub enum Error {
SigningKey(String),
#[error("failed to parse {VAR_HTTP_TIMEOUT}: {0}")]
HttpTimeout(ParseIntError),
#[error("failed to parse {VAR_MAX_PASTE_EXPIRATION}: {0}")]
MaxPasteExpiration(ParseIntError),
#[error(
"{VAR_MAX_PASTE_EXPIRATION} is too large (max {}), pass -1 if you mean no expiry",
u32::MAX
)]
ExpirationTooLarge,
}

pub struct BasePath(String);
Expand Down Expand Up @@ -183,3 +191,17 @@ pub fn http_timeout() -> Result<Duration, Error> {
)
.map_err(Error::HttpTimeout)
}

pub fn max_paste_expiration() -> Result<Option<u32>, Error> {
std::env::var(VAR_MAX_PASTE_EXPIRATION)
.ok()
.and_then(|raw_max_exp| -> Option<Result<u32, Error>> {
match raw_max_exp.parse::<i64>() {
Ok(max_exp) if max_exp == -1 => None,
Ok(max_exp) if max_exp >= u32::MAX as i64 => Some(Err(Error::ExpirationTooLarge)),
Ok(max_exp) => Some(Ok(max_exp as u32)),
Err(why) => Some(Err(Error::MaxPasteExpiration(why))),
}
})
.transpose()
}
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub struct AppState {
cache: Cache,
key: Key,
base_url: Option<Url>,
max_expiration: Option<u32>,
}

impl FromRef<AppState> for Key {
Expand Down Expand Up @@ -91,13 +92,16 @@ async fn start() -> Result<(), Box<dyn std::error::Error>> {
let max_body_size = env::max_body_size()?;
let base_url = env::base_url()?;
let timeout = env::http_timeout()?;
let max_expiration = env::max_paste_expiration()?;

let cache = Cache::new(cache_size);
let db = Database::new(method)?;
let state = AppState {
db,
cache,
key,
base_url,
max_expiration,
};

tracing::debug!("serving on {addr}");
Expand Down
60 changes: 60 additions & 0 deletions src/pages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,77 @@ impl From<crate::Error> for ErrorResponse<'_> {
pub struct Index<'a> {
meta: &'a env::Metadata<'a>,
base_path: &'static env::BasePath,
max_expiration: Option<u32>,
}

impl<'a> Default for Index<'a> {
fn default() -> Self {
Self {
meta: env::metadata(),
base_path: env::base_path(),
// exception should already have been handled in main
max_expiration: env::max_paste_expiration().unwrap(),
}
}
}

#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum Expiration {
None,
Burn,
Time(u32),
}

impl std::fmt::Display for Expiration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expiration::None => write!(f, ""),
Expiration::Burn => write!(f, "burn"),
Expiration::Time(t) => write!(f, "{}", t),
}
}
}

const EXPIRATION_OPTIONS: [(&'static str, Expiration); 8] = [
("never", Expiration::None),
("10 minutes", Expiration::Time(600)),
("1 hour", Expiration::Time(3600)),
("1 day", Expiration::Time(86400)),
("1 week", Expiration::Time(604800)),
("1 month", Expiration::Time(2592000)),
("1 year", Expiration::Time(31536000)),
("🔥 after reading", Expiration::Burn),
];

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;
}
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
}
}

/// Paste view showing the formatted paste as well as a bunch of links.
#[derive(Template)]
#[template(path = "formatted.html")]
Expand Down
6 changes: 6 additions & 0 deletions src/routes/form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ pub async fn insert(

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

if let Some(max_exp) = state.max_expiration {
entry.expires = entry
.expires
.map_or_else(|| Some(max_exp), |value| Some(value.min(max_exp)));
}

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

let jar = jar.add(Cookie::new("uid", uid.to_string()));
Expand Down
8 changes: 7 additions & 1 deletion src/routes/json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ pub async fn insert(
.map_err(Error::from)?
.into();

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

if let Some(max_exp) = state.max_expiration {
entry.expires = entry
.expires
.map_or_else(|| Some(max_exp), |value| Some(value.min(max_exp)));
}

let url = id.to_url_path(&entry);
let path = base_path().join(&url);
Expand Down
1 change: 1 addition & 0 deletions src/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub(crate) fn make_app() -> Result<Router, Box<dyn std::error::Error>> {
cache,
key,
base_url,
max_expiration: None,
};

Ok(crate::make_app(4096, Duration::new(30, 0)).with_state(state))
Expand Down
10 changes: 2 additions & 8 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,8 @@
<input type="search" id="filter" placeholder="Filter ..." onchange="filterLangs(event);" onkeyup="filterLangs(event)"></input>
</div>
<div class="expiration-list">
<select name="expires" size="7">
<option selected="" value="">never</option>
<option value="600">10 minutes</option>
<option value="3600">1 hour</option>
<option value="86400">1 day</option>
<option value="604800">1 week</option>
<option value="215308800">1 year</option>
<option value="burn">🔥 after reading</option>
<select name="expires" size="8">
{{- Self::expiry_options(self)|safe }}
</select>
</div>
<div class="password">
Expand Down