Skip to content

Commit f8a6a6b

Browse files
committed
Refactor error handling, add socket activation
Socket activation is necessary to make it easier to use systemd to sandbox this service, since it's basically one big SSRF vector if not sandboxed away from localhost and any local services whatsoever. Also adds a hack per rust-lang/cargo#8075 (comment) so you can run `cargo clippy --features clippy`.
1 parent 95a6ace commit f8a6a6b

File tree

4 files changed

+59
-16
lines changed

4 files changed

+59
-16
lines changed

Cargo.lock

+21
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ ical = "0.6.0"
2828
serde = "1.0.114"
2929
serde_json = "1.0.55"
3030
regex = "1.3.9"
31+
listenfd = "0.3.3"
32+
33+
[features]
34+
clippy = []

src/filter.rs

+21-9
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@ use regex::Regex;
22
use serde::de;
33
use std::fmt;
44

5-
type MyError = Box<dyn std::error::Error>;
6-
5+
// this lint is falsely triggering on this, which is *not* interior mutable
6+
#[allow(clippy::declare_interior_mutable_const)]
77
pub const TRUE_FILTER: Filter = Filter {
88
invert: false,
99
operator: FilterOperator::True,
1010
};
1111

12+
#[derive(Debug)]
13+
pub enum FilterErrorKind {
14+
MissingColon,
15+
OperatorParse,
16+
RegexError(regex::Error),
17+
}
18+
19+
#[derive(Debug)]
20+
pub struct FilterError(String, FilterErrorKind);
21+
1222
#[derive(Debug)]
1323
enum FilterOperator {
1424
Equals(String),
@@ -27,15 +37,15 @@ pub struct Filter {
2737

2838
impl FilterOperator {
2939
/// Parses a stringified filter operator into a [`FilterOperator`](enum.FilterOperator.html)
30-
fn parse(s: &str, content: String) -> Result<FilterOperator, MyError> {
40+
fn parse(s: &str, content: String) -> Result<FilterOperator, FilterError> {
3141
match s {
3242
"equals" => Ok(FilterOperator::Equals(content)),
3343
"startsWith" => Ok(FilterOperator::StartsWith(content)),
3444
"endsWith" => Ok(FilterOperator::EndsWith(content)),
3545
"contains" => Ok(FilterOperator::Contains(content)),
3646
"true" => Ok(FilterOperator::True),
37-
"regex" => Ok(FilterOperator::Regex(Regex::new(&content)?)),
38-
_ => Err("unknown filter operator; options are equals, startsWith, endsWith, contains, true, regex".into()),
47+
"regex" => Ok(FilterOperator::Regex(Regex::new(&content).map_err(|e| FilterError(content, FilterErrorKind::RegexError(e)))?)),
48+
_ => Err(FilterError("unknown filter operator; options are equals, startsWith, endsWith, contains, true, regex".to_owned(), FilterErrorKind::OperatorParse)),
3949
}
4050
}
4151

@@ -55,11 +65,13 @@ impl FilterOperator {
5565

5666
impl Filter {
5767
/// Parses a filter into a Filter struct.
58-
fn parse(s: &str) -> Result<Filter, MyError> {
68+
fn parse(s: &str) -> Result<Filter, FilterError> {
5969
let invert = s.starts_with('!');
6070
let s = if invert { &s[1..] } else { s };
6171

62-
let colon = s.find(":").ok_or_else(|| "missing colon in filter")?;
72+
let colon = s
73+
.find(':')
74+
.ok_or_else(|| FilterError(s.to_owned(), FilterErrorKind::MissingColon))?;
6375
let (operator, text) = s.split_at(colon);
6476
// chop off colon
6577
let text = &text[1..];
@@ -98,8 +110,8 @@ where
98110
E: de::Error,
99111
{
100112
let mut filters = Vec::new();
101-
for filt in v.split("~") {
102-
let parsed = Filter::parse(filt).map_err(E::custom)?;
113+
for filt in v.split('~') {
114+
let parsed = Filter::parse(filt).map_err(|e| E::custom(format!("{:?}", e)))?;
103115
filters.push(parsed);
104116
}
105117

src/main.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use actix_web::{
1010
use chrono::{DateTime, Utc};
1111
use chrono_tz::{Tz, UTC};
1212
use filter::Filter;
13+
use listenfd::ListenFd;
1314
use serde::{Deserialize, Serialize};
1415

1516
pub mod env;
@@ -53,7 +54,7 @@ impl std::convert::From<Event> for ics::Event<'_> {
5354

5455
async fn compute_events<'a>(
5556
url: &str,
56-
filters: &'a Vec<Filter>,
57+
filters: &'a [Filter],
5758
) -> Result<impl Iterator<Item = Result<impl Iterator<Item = Result<Event>> + 'a>>> {
5859
let calendars = upstream::get_calendars(url).await?;
5960

@@ -126,7 +127,7 @@ async fn compute_events<'a>(
126127
}))
127128
}
128129

129-
async fn collect_events(url: &str, filters: &Vec<Filter>) -> Result<Vec<Event>> {
130+
async fn collect_events(url: &str, filters: &[Filter]) -> Result<Vec<Event>> {
130131
let iter = compute_events(url, filters).await?;
131132

132133
let mut res = Vec::new();
@@ -186,17 +187,22 @@ async fn main() -> std::io::Result<()> {
186187

187188
let configuration = env::get_conf().unwrap();
188189
let socketaddr = configuration.socketaddr;
190+
let mut listenfd = ListenFd::from_env();
189191

190-
HttpServer::new(move || {
192+
let server = HttpServer::new(move || {
191193
let configuration = configuration.clone();
192194

193195
App::new()
194196
.wrap(Logger::default())
195197
.data(configuration)
196198
.service(web::resource("/v1/json").to(get_json))
197199
.service(web::resource("/v1/ical").to(get_ical))
198-
})
199-
.bind(socketaddr)?
200-
.run()
201-
.await
200+
});
201+
202+
let server = if let Some(listener) = listenfd.take_tcp_listener(0)? {
203+
server.listen(listener)?
204+
} else {
205+
server.bind(socketaddr)?
206+
};
207+
server.run().await
202208
}

0 commit comments

Comments
 (0)