Skip to content

Commit 7e483f7

Browse files
committed
fix(config): root_dir not defined in config file panics
1 parent ee331f1 commit 7e483f7

File tree

10 files changed

+126
-62
lines changed

10 files changed

+126
-62
lines changed

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,50 @@
22

33
<Empty>
44

5+
<a name="v0.3.0"></a>
6+
## v0.3.0
7+
8+
> Requires Rust: rustc 1.49.0 (e1884a8e3 2020-12-29)
9+
10+
#### Features
11+
12+
* Support for Cross-Origin Resource Sharing
13+
14+
* Using the `--cors` flag when running the HTTP Server will now provide a
15+
CORS configuration which allows requests from any origin
16+
17+
* The server configuration file supports a fully configurable CORS field now
18+
19+
```toml
20+
[cors]
21+
allow_credentials = false
22+
allow_headers = ["content-type", "authorization", "content-length"]
23+
allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"]
24+
allow_origin = "example.com"
25+
expose_headers = ["*", "authorization"]
26+
max_age = 600
27+
request_headers = ["x-app-version"]
28+
request_method = "GET"
29+
```
30+
31+
#### Improvements
32+
33+
* Codebase refactor for `server` module, introducing middleware support for
34+
services to programatically build a server instance from the provided `Config`
35+
36+
* Replace `full` feature flags on `tokio` and `hyper` with sepecific features,
37+
to reduce crate load on compile time. This improve build times and crate size.
38+
39+
#### Fixes
40+
41+
* Fix issue where the `root_dir` is taken _as is_ from the CLI
42+
arguments resulting on `./` instead of the current working
43+
directory for the `FileExplorer`
44+
45+
* Fix issue where loading config without `root_dir` defined panics
46+
and uses the current working directory as default `root_dir` as is
47+
done for CLI
48+
549
<a name="v0.2.2"></a>
650
## v0.2.2 (2021-04-22)
751

fixtures/config.toml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33

44
host = "127.0.0.1"
55
port = 7878
6-
verbose = false
7-
root_dir = "./"
6+
# verbose = false
7+
# root_dir = "./"
88

99
# [tls]
1010
# cert = "cert.pem"
1111
# key = "key.pem"
1212

13-
[cors]
14-
allow_credentials = false
15-
allow_headers = ["content-type", "authorization", "content-length"]
16-
allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"]
17-
allow_origin = "example.com"
18-
expose_headers = ["*", "authorization"]
19-
max_age = 600
20-
request_headers = ["x-app-version"]
21-
request_method = "GET"
13+
# [cors]
14+
# allow_credentials = false
15+
# allow_headers = ["content-type", "authorization", "content-length"]
16+
# allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"]
17+
# allow_origin = "example.com"
18+
# expose_headers = ["*", "authorization"]
19+
# max_age = 600
20+
# request_headers = ["x-app-version"]
21+
# request_method = "GET"

src/bin/main.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
use http_server::make_server;
2+
use std::process::exit;
23

34
#[tokio::main]
45
async fn main() {
5-
let server = make_server().unwrap();
6-
7-
server.run().await;
6+
match make_server() {
7+
Ok(server) => {
8+
server.run().await;
9+
}
10+
Err(error) => {
11+
eprint!("{:?}", error);
12+
exit(1);
13+
}
14+
}
815
}

src/cli.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,7 @@ impl Default for Cli {
6868
}
6969

7070
mod tests {
71-
use std::str::FromStr;
72-
71+
#[allow(unused_imports)]
7372
use super::*;
7473

7574
#[test]

src/config/cors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use anyhow::{Error, Result};
22
use serde::Deserialize;
33
use std::convert::TryFrom;
4-
use std::time::Duration;
54

65
/// CORS (Cross Origin Resource Sharing) configuration for the HTTP/S
76
/// server.
@@ -236,6 +235,7 @@ impl TryFrom<CorsConfigFile> for CorsConfig {
236235
}
237236

238237
mod tests {
238+
#[allow(unused_imports)]
239239
use super::*;
240240

241241
#[test]

src/config/file.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use anyhow::{Error, Result};
2-
use serde::Deserialize;
2+
use serde::{Deserialize, Deserializer};
33
use std::fs;
44
use std::net::IpAddr;
55
use std::path::PathBuf;
6+
use std::str::FromStr;
67

78
use super::cors::CorsConfigFile;
89
use super::tls::TlsConfigFile;
@@ -12,6 +13,8 @@ pub struct ConfigFile {
1213
pub host: IpAddr,
1314
pub port: u16,
1415
pub verbose: Option<bool>,
16+
#[serde(default = "current_working_dir")]
17+
#[serde(deserialize_with = "canonicalize_some")]
1518
pub root_dir: Option<PathBuf>,
1619
pub tls: Option<TlsConfigFile>,
1720
pub cors: Option<CorsConfigFile>,
@@ -36,6 +39,21 @@ impl ConfigFile {
3639
}
3740
}
3841

42+
fn canonicalize_some<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
43+
where
44+
D: Deserializer<'de>,
45+
{
46+
let value: &str = Deserialize::deserialize(deserializer)?;
47+
let path = PathBuf::from_str(value).unwrap();
48+
let canon = fs::canonicalize(path).unwrap();
49+
50+
Ok(Some(canon))
51+
}
52+
53+
fn current_working_dir() -> Option<PathBuf> {
54+
std::env::current_dir().ok()
55+
}
56+
3957
#[cfg(test)]
4058
mod tests {
4159
use std::net::Ipv4Addr;
@@ -51,17 +69,19 @@ mod tests {
5169
host = "192.168.0.1"
5270
port = 7878
5371
verbose = true
54-
root_dir = "~/Desktop"
72+
root_dir = "./fixtures"
5573
"#;
5674
let host = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1));
5775
let port = 7878;
58-
let root_dir = PathBuf::from_str("~/Desktop").unwrap();
5976
let config = ConfigFile::parse_toml(file_contents).unwrap();
77+
let mut root_dir = PathBuf::from(std::env::current_dir().unwrap());
78+
79+
root_dir.push("./fixtures");
6080

6181
assert_eq!(config.host, host);
6282
assert_eq!(config.port, port);
63-
assert_eq!(config.root_dir.unwrap(), root_dir);
6483
assert_eq!(config.verbose, Some(true));
84+
assert_eq!(config.root_dir, Some(root_dir));
6585
}
6686

6787
#[test]
@@ -81,7 +101,6 @@ mod tests {
81101
host = "192.168.0.1"
82102
port = 7878
83103
verbose = false
84-
root_dir = "~/Desktop"
85104
86105
[tls]
87106
cert = "cert_123.pem"
@@ -90,7 +109,7 @@ mod tests {
90109
"#;
91110
let host = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1));
92111
let port = 7878;
93-
let root_dir = PathBuf::from_str("~/Desktop").unwrap();
112+
let root_dir = Some(std::env::current_dir().unwrap());
94113
let tls = TlsConfigFile {
95114
cert: PathBuf::from_str("cert_123.pem").unwrap(),
96115
key: PathBuf::from_str("key_123.pem").unwrap(),
@@ -100,7 +119,7 @@ mod tests {
100119

101120
assert_eq!(config.host, host);
102121
assert_eq!(config.port, port);
103-
assert_eq!(config.root_dir.unwrap(), root_dir);
122+
assert_eq!(config.root_dir, root_dir);
104123
assert_eq!(config.tls.unwrap(), tls);
105124
assert_eq!(config.verbose, Some(false));
106125
}
@@ -110,7 +129,6 @@ mod tests {
110129
let file_contents = r#"
111130
host = "192.168.0.1"
112131
port = 7878
113-
root_dir = "~/Desktop"
114132
115133
[tls]
116134
cert = "cert_123.pem"
@@ -119,7 +137,7 @@ mod tests {
119137
"#;
120138
let host = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1));
121139
let port = 7878;
122-
let root_dir = PathBuf::from_str("~/Desktop").unwrap();
140+
let root_dir = Some(std::env::current_dir().unwrap());
123141
let tls = TlsConfigFile {
124142
cert: PathBuf::from_str("cert_123.pem").unwrap(),
125143
key: PathBuf::from_str("key_123.pem").unwrap(),
@@ -129,7 +147,7 @@ mod tests {
129147

130148
assert_eq!(config.host, host);
131149
assert_eq!(config.port, port);
132-
assert_eq!(config.root_dir.unwrap(), root_dir);
150+
assert_eq!(config.root_dir, root_dir);
133151
assert_eq!(config.tls.unwrap(), tls);
134152
}
135153

@@ -168,9 +186,11 @@ mod tests {
168186
request_method: None,
169187
};
170188
let config = ConfigFile::parse_toml(file_contents).unwrap();
189+
let root_dir = Some(std::env::current_dir().unwrap());
171190

172191
assert_eq!(config.host, host);
173192
assert_eq!(config.port, port);
193+
assert_eq!(config.root_dir, root_dir);
174194
assert_eq!(config.cors.unwrap(), cors);
175195
}
176196

@@ -192,6 +212,7 @@ mod tests {
192212
"#;
193213
let host = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
194214
let port = 8080;
215+
let root_dir = Some(std::env::current_dir().unwrap());
195216
let cors = CorsConfigFile {
196217
allow_credentials: true,
197218
allow_headers: Some(vec![
@@ -216,6 +237,7 @@ mod tests {
216237

217238
assert_eq!(config.host, host);
218239
assert_eq!(config.port, port);
240+
assert_eq!(config.root_dir, root_dir);
219241
assert_eq!(config.cors.unwrap(), cors);
220242
}
221243
}

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ fn resolve_config(cli_arguments: cli::Cli) -> Result<Config> {
1919
}
2020

2121
// Otherwise configuration is build from CLI arguments
22-
Config::try_from(cli_arguments).with_context(|| anyhow::Error::msg("OK"))
22+
Config::try_from(cli_arguments)
23+
.with_context(|| anyhow::Error::msg("Failed to parse arguments from stdin"))
2324
}
2425

2526
pub fn make_server() -> Result<Server> {

src/server/handler/file_explorer.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,16 @@ pub fn make_file_explorer_handler(file_explorer: Arc<FileExplorer>) -> Handler {
2323

2424
Box::pin(async move {
2525
if request.method() == Method::GET {
26-
return file_explorer.resolve(request).await;
26+
return file_explorer
27+
.resolve(request)
28+
.await
29+
.map_err(|e| {
30+
HttpResponseBuilder::new()
31+
.status(StatusCode::INTERNAL_SERVER_ERROR)
32+
.body(Body::from(e.to_string()))
33+
.expect("Unable to build response")
34+
})
35+
.unwrap();
2736
}
2837

2938
HttpResponseBuilder::new()
@@ -172,42 +181,42 @@ impl<'a> FileExplorer {
172181
///
173182
/// If the matched path resolves to a file, attempts to render it if the
174183
/// MIME type is supported, otherwise returns the binary (downloadable file)
175-
pub async fn resolve(&self, req: Request<Body>) -> Response<Body> {
184+
pub async fn resolve(&self, req: Request<Body>) -> Result<Response<Body>> {
176185
match resolve(&self.root_dir, &req).await.unwrap() {
177-
ResolveResult::MethodNotMatched => HttpResponseBuilder::new()
186+
ResolveResult::MethodNotMatched => Ok(HttpResponseBuilder::new()
178187
.status(StatusCode::BAD_REQUEST)
179188
.body(Body::empty())
180-
.expect("Failed to build response"),
181-
ResolveResult::UriNotMatched => HttpResponseBuilder::new()
189+
.expect("Failed to build response")),
190+
ResolveResult::UriNotMatched => Ok(HttpResponseBuilder::new()
182191
.status(StatusCode::BAD_REQUEST)
183192
.body(Body::empty())
184-
.expect("Failed to build response"),
193+
.expect("Failed to build response")),
185194
ResolveResult::NotFound => {
186195
if req.uri() == "/" {
187196
let directory_path = self.make_absolute_path_from_request(&req).unwrap();
188197

189-
return self.render_directory_index(directory_path).await.unwrap();
198+
return self.render_directory_index(directory_path).await;
190199
}
191200

192-
HttpResponseBuilder::new()
201+
Ok(HttpResponseBuilder::new()
193202
.status(StatusCode::NOT_FOUND)
194203
.body(Body::empty())
195-
.expect("Failed to build response")
204+
.expect("Failed to build response"))
196205
}
197-
ResolveResult::PermissionDenied => HttpResponseBuilder::new()
206+
ResolveResult::PermissionDenied => Ok(HttpResponseBuilder::new()
198207
.status(StatusCode::FORBIDDEN)
199208
.body(Body::empty())
200-
.expect("Failed to build response"),
209+
.expect("Failed to build response")),
201210
ResolveResult::IsDirectory => {
202211
let directory_path = self.make_absolute_path_from_request(&req).unwrap();
203212

204-
return self.render_directory_index(directory_path).await.unwrap();
213+
return self.render_directory_index(directory_path).await;
205214
}
206-
ResolveResult::Found(file, metadata, mime) => FileResponseBuilder::new()
215+
ResolveResult::Found(file, metadata, mime) => Ok(FileResponseBuilder::new()
207216
.request(&req)
208217
.cache_headers(self.cache_headers)
209218
.build(ResolveResult::Found(file, metadata, mime))
210-
.expect("Failed to build response"),
219+
.expect("Failed to build response")),
211220
}
212221
}
213222

src/server/middleware/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
pub mod make_cors_middleware;
2-
pub mod with_cors_allow_all;
32

43
use anyhow::Error;
54
use futures::Future;
@@ -27,6 +26,7 @@ pub struct Middleware {
2726
impl Middleware {
2827
/// Appends a middleware function to run before handling the
2928
/// HTTP Request
29+
#[allow(dead_code)]
3030
pub fn before(&mut self, middleware: MiddlewareBefore) {
3131
self.before.push(middleware);
3232
}
@@ -74,7 +74,7 @@ impl TryFrom<Config> for Middleware {
7474
let mut middleware = Middleware::default();
7575

7676
if config.cors().is_some() {
77-
let func = make_cors_middleware(config.clone());
77+
let func = make_cors_middleware(config);
7878

7979
middleware.after(func);
8080
}

src/server/middleware/with_cors_allow_all.rs

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)